Skip to content

Commit

Permalink
eth/abi: support dynamic array encoding (#122)
Browse files Browse the repository at this point in the history
* eth/abi: support dynamic array encoding

* eth/abi: support dynamic array encoding

* Update spec/eth/abi_spec.rb

Co-authored-by: Afri <58883403+q9f@users.noreply.github.com>

* fix rspec

* format abi_spec

Co-authored-by: Afri <58883403+q9f@users.noreply.github.com>
  • Loading branch information
peter-chung-xfers and q9f authored Jul 1, 2022
1 parent 3b62e30 commit 1f8ff75
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 6 deletions.
22 changes: 18 additions & 4 deletions lib/eth/abi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def encode(types, args)
# @return [String] the encoded type.
# @raise [EncodingError] if value does not match type.
def encode_type(type, arg)
if %w(string bytes).include? type.base_type and type.sub_type.empty?
if %w(string bytes).include? type.base_type and type.sub_type.empty? and type.dimensions.empty?
raise EncodingError, "Argument must be a String" unless arg.instance_of? String

# encodes strings and bytes
Expand All @@ -89,10 +89,24 @@ def encode_type(type, arg)
head += encode_type Type.size_type, arg.size
nested_sub = type.nested_sub
nested_sub_size = type.nested_sub.size
arg.size.times do |i|

# ref https://github.com/ethereum/tests/issues/691
raise NotImplementedError, "Encoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic?
# calculate offsets
if %w(string bytes).include?(type.base_type) && type.sub_type.empty?
offset = 0
arg.size.times do |i|
if i == 0
offset = arg.size * 32
else
number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor
total_bytes_length = number_of_words * 32
offset += total_bytes_length + 32
end

head += encode_type Type.size_type, offset
end
end

arg.size.times do |i|
head += encode_type nested_sub, arg[i]
end
return "#{head}#{tail}"
Expand Down
2 changes: 1 addition & 1 deletion lib/eth/contract/function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def initialize(data)
# @param inputs [Array<Eth::Contract::FunctionInput>] function input class list.
# @return [String] function string.
def self.calc_signature(name, inputs)
"#{name}(#{inputs.collect { |x| x.type }.join(",")})"
"#{name}(#{inputs.collect { |x| x.raw_type }.join(",")})"
end

# Encodes a function signature.
Expand Down
3 changes: 2 additions & 1 deletion lib/eth/contract/function_input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ module Eth

# Provide classes for contract function input.
class Contract::FunctionInput
attr_accessor :type, :name
attr_accessor :type, :raw_type, :name

# Constructor of the {Eth::Contract::FunctionInput} class.
#
# @param data [Hash] contract abi data.
def initialize(data)
@raw_type = data["type"]
@type = Eth::Abi::Type.parse(data["type"])
@name = data["name"]
end
Expand Down
24 changes: 24 additions & 0 deletions spec/eth/abi_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@
let(:basic_abi_tests_file) { File.read "spec/fixtures/ethereum/tests/ABITests/basic_abi_tests.json" }
subject(:basic_abi_tests) { JSON.parse basic_abi_tests_file }

describe "dynamic encode" do
it "can encode array of string" do
encoded = Eth::Util.bin_to_hex(described_class.encode(["string[]"], [["hello", "world"]]))
expected = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000568656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000"
expect(encoded).to eq(expected)
end

it "can encode array of uint256" do
encoded = Eth::Util.bin_to_hex(described_class.encode(["uint256[]"], [[123, 456]]))
expected = "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8"
expect(encoded).to eq(expected)
end

it "can encode mix of array of uint256 and string" do
encoded = Eth::Util.bin_to_hex(described_class.encode(["uint256[]", "string[]"], [[123, 456], ["hello", "world"]]))
expected = "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000568656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000"
expect(encoded).to eq(expected)

encoded = Eth::Util.bin_to_hex(described_class.encode(["uint256[]", "string[]", "string[]", "uint8[]"], [[123, 456], ["hello", "world"], ["ruby", "ethereum"], [8]]))
expected = "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000568656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000472756279000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008657468657265756d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008"
expect(encoded).to eq(expected)
end
end

it "can encode abi" do
basic_abi_tests.each do |test|
types = test.last["types"]
Expand Down

0 comments on commit 1f8ff75

Please sign in to comment.