diff --git a/lib/eth/abi/decoder.rb b/lib/eth/abi/decoder.rb index 5f104c7f..27911a60 100644 --- a/lib/eth/abi/decoder.rb +++ b/lib/eth/abi/decoder.rb @@ -51,6 +51,25 @@ def type(type, arg) type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32]) end end + elsif type.base_type == 'tuple' + offset = 0 + data = {} + type.components.each do |c| + if c.dynamic? + pointer = Util.deserialize_big_endian_to_int arg[offset, 32] # Pointer to the size of the array's element + data_len = Util.deserialize_big_endian_to_int arg[pointer, 32] # length of the element + + data[c.name] = type(c, arg[pointer, Util.ceil32(data_len) + 32]) + # puts data + offset += 32 + else + size = c.size + data[c.name] = type(c, arg[offset, size]) + offset += size + # puts data + end + end + data elsif type.dynamic? l = Util.deserialize_big_endian_to_int arg[0, 32] nested_sub = type.nested_sub diff --git a/lib/eth/abi/event.rb b/lib/eth/abi/event.rb index bf8b5b93..d0af31d8 100644 --- a/lib/eth/abi/event.rb +++ b/lib/eth/abi/event.rb @@ -116,9 +116,20 @@ def decode_logs(interfaces, logs) def decode_log(inputs, data, topics, anonymous = false) topic_inputs, data_inputs = inputs.partition { |i| i["indexed"] } - topic_types = topic_inputs.map { |i| i["type"] } - data_types = data_inputs.map { |i| i["type"] } - + topic_types = topic_inputs.map do |i| + if i['type'] == 'tuple' + Type.parse(i['type'], i['components'], i['name']) + else + i['type'] + end + end + data_types = data_inputs.map do |i| + if i['type'] == 'tuple' + Type.parse(i['type'], i['components'], i['name']) + else + i['type'] + end + end # If event is anonymous, all topics are arguments. Otherwise, the first # topic will be the event signature. if anonymous == false diff --git a/lib/eth/abi/type.rb b/lib/eth/abi/type.rb index 62b862cd..94ea4013 100644 --- a/lib/eth/abi/type.rb +++ b/lib/eth/abi/type.rb @@ -71,7 +71,15 @@ def initialize(base_type, sub_type, dimensions, components = nil, component_name # @return [Eth::Abi::Type] a parsed Type object. # @raise [ParseError] if it fails to parse the type. def parse(type, components = nil, component_name = nil) - return type if type.is_a?(Type) + if type.is_a?(Type) + @base_type = type.base_type + @sub_type = type.sub_type + @dimensions = type.dimensions + @components = type.components + @name = type.name + return + end + _, base_type, sub_type, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a # type dimension can only be numeric diff --git a/lib/eth/contract/event.rb b/lib/eth/contract/event.rb index 727b83db..0106b3e8 100644 --- a/lib/eth/contract/event.rb +++ b/lib/eth/contract/event.rb @@ -26,7 +26,9 @@ class Contract::Event # @param data [Hash] contract event data. def initialize(data) @name = data["name"] - @input_types = data["inputs"].collect { |x| x["type"] } + @input_types = data["inputs"].collect do |x| + type_name x + end @inputs = data["inputs"].collect { |x| x["name"] } @event_string = "#{@name}(#{@input_types.join(",")})" @signature = Digest::Keccak.hexdigest(@event_string, 256) @@ -38,5 +40,16 @@ def initialize(data) def set_address(address) @address = address.nil? ? nil : Eth::Address.new(address).address end + + private + def type_name(x) + type = x["type"] + case type + when "tuple" + "(#{x['components'].collect { |c| type_name(c) }.join(',')})" + else + type + end + end end end diff --git a/spec/eth/abi/event_spec.rb b/spec/eth/abi/event_spec.rb index fe22ba4d..19ed435f 100644 --- a/spec/eth/abi/event_spec.rb +++ b/spec/eth/abi/event_spec.rb @@ -247,6 +247,7 @@ expect(signature).to eq "Transfer(address,address,uint256)" end + it "generates transfer function signature" do abi = erc20_abi.find { |i| i["type"] == "function" && i["name"] == "transfer" } signature = Abi::Event.signature(abi) diff --git a/spec/eth/contract/event_spec.rb b/spec/eth/contract/event_spec.rb index 043f0032..f5e78c91 100644 --- a/spec/eth/contract/event_spec.rb +++ b/spec/eth/contract/event_spec.rb @@ -13,5 +13,85 @@ expect(contract.events[1].signature).to eq("1f3a0e41bf4d8306f04763663bf025d1824a391571ce3a07186a195f8c4cfd3c") expect(contract.events[1].event_string).to eq("killed()") end + + it "generates signature for event with tuple params" do + event = Eth::Contract::Event.new({ + "anonymous" => false, + "inputs" => [ + { + "components" => [ + { + "internalType" => "uint256", + "name" => "topicId", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "proposalId", + "type" => "uint256" + }, + { + "internalType" => "string", + "name" => "name", + "type" => "string" + }, + { + "internalType" => "string", + "name" => "symbol", + "type" => "string" + }, + { + "internalType" => "uint256", + "name" => "duration", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "totalSupply", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "miniStakeValue", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "maxStakeValue", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "maxParticipants", + "type" => "uint256" + }, + { + "internalType" => "uint256", + "name" => "whitelistIndex", + "type" => "uint256" + }, + { + "internalType" => "address", + "name" => "proposer", + "type" => "address" + }, + { + "internalType" => "bool", + "name" => "useWhitelist", + "type" => "bool" + } + ], + "indexed" => false, + "internalType" => "struct VoteContract.ProposalCreatedParams", + "name" => "params", + "type" => "tuple" + } + ], + "name" => "ProposalCreated", + "type" => "event" + }) + expect(event.event_string).to eq('ProposalCreated((uint256,uint256,string,string,uint256,uint256,uint256,uint256,uint256,uint256,address,bool))') + expect(event.signature).to eq('4449031b77cbe261580701c097fb63211e768f685581e616330dfff20493536c') + end end end