diff --git a/lib/eth/abi.rb b/lib/eth/abi.rb index ebeba300..8bcbbab7 100644 --- a/lib/eth/abi.rb +++ b/lib/eth/abi.rb @@ -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 @@ -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}" diff --git a/lib/eth/contract/function.rb b/lib/eth/contract/function.rb index e867f375..060fa283 100644 --- a/lib/eth/contract/function.rb +++ b/lib/eth/contract/function.rb @@ -43,7 +43,7 @@ def initialize(data) # @param inputs [Array] 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. diff --git a/lib/eth/contract/function_input.rb b/lib/eth/contract/function_input.rb index b11e774b..c96b6d3d 100644 --- a/lib/eth/contract/function_input.rb +++ b/lib/eth/contract/function_input.rb @@ -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 diff --git a/spec/eth/abi_spec.rb b/spec/eth/abi_spec.rb index c5fdf60f..0c302b33 100644 --- a/spec/eth/abi_spec.rb +++ b/spec/eth/abi_spec.rb @@ -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"]