Skip to content

Commit

Permalink
Support tuple params in EventLog (#276)
Browse files Browse the repository at this point in the history
* support tuple with dynamic fields

* fix the topic hash generation for tuple params

* add spec for event signature

---------

Co-authored-by: Afri <[email protected]>
  • Loading branch information
ShiningRay and q9f authored Jun 23, 2024
1 parent 634b5b9 commit 502aa91
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 5 deletions.
19 changes: 19 additions & 0 deletions lib/eth/abi/decoder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions lib/eth/abi/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion lib/eth/abi/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion lib/eth/contract/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
1 change: 1 addition & 0 deletions spec/eth/abi/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
80 changes: 80 additions & 0 deletions spec/eth/contract/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 502aa91

Please sign in to comment.