diff --git a/Gemfile b/Gemfile index fd412b1d..851287f1 100644 --- a/Gemfile +++ b/Gemfile @@ -6,10 +6,10 @@ group :test, :development do gem "bundler", "~> 2.2" gem "codecov", "~> 0.6" gem "pry", "~> 0.14" - gem "rake", "~> 13.0" - gem "rdoc", "~> 6.4" - gem "rspec", "~> 3.11" - gem "rufo", "~> 0.13" + gem "rake", "~> 13.2" + gem "rdoc", "~> 6.7" + gem "rspec", "~> 3.13" + gem "rufo", "~> 0.18" gem "simplecov", "~> 0.21" gem "yard", "~> 0.9" end diff --git a/lib/eth/abi.rb b/lib/eth/abi.rb index 145341af..45edf94f 100644 --- a/lib/eth/abi.rb +++ b/lib/eth/abi.rb @@ -38,30 +38,55 @@ class ValueOutOfBounds < StandardError; end # # @param types [Array] types to be ABI-encoded. # @param args [Array] values to be ABI-encoded. + # @param packed [Boolean] use custom packed encoding (default: false). # @return [String] the encoded ABI data. - def encode(types, args) + def encode(types, args, packed = false) + types = [types] unless types.instance_of? Array + args = [args] unless args.instance_of? Array # parse all types parsed_types = types.map { |t| Type === t ? t : Type.parse(t) } # prepare the "head" head_size = (0...args.size) - .map { |i| parsed_types[i].size or 32 } + .map { |i| + if packed + parsed_types[i].sub_type.to_i / 8 + else + parsed_types[i].size or 32 + end + } .reduce(0, &:+) head, tail = "", "" # encode types and arguments args.each_with_index do |arg, i| - if parsed_types[i].dynamic? - head += Abi::Encoder.type(Type.size_type, head_size + tail.size) - tail += Abi::Encoder.type(parsed_types[i], arg) + if packed + head += Abi::Encoder.type(parsed_types[i], arg, packed) + elsif parsed_types[i].dynamic? + head += Abi::Encoder.type(Type.size_type, head_size + tail.size, packed) + tail += Abi::Encoder.type(parsed_types[i], arg, packed) else - head += Abi::Encoder.type(parsed_types[i], arg) + head += Abi::Encoder.type(parsed_types[i], arg, packed) end end + if tail.size == 0 && packed + tail = head + end + # return the encoded ABI blob - "#{head}#{tail}" + packed ? "#{tail}" : "#{head}#{tail}" + end + + # Encodes a custom, packed Application Binary Interface (packed ABI) data. + # It accepts multiple arguments and encodes according to the Solidity specification. + # + # @param types [Array] types to be ABI-encoded. + # @param args [Array] values to be ABI-encoded. + # @return [String] the packed encoded ABI data. + def encode_packed(types, args) + encode(types, args, true) end # Decodes Application Binary Interface (ABI) data. It accepts multiple @@ -120,6 +145,15 @@ def decode(types, data) # return the decoded ABI types and data parsed_types.zip(outputs).map { |(type, out)| Abi::Decoder.type(type, out) } end + + # Since the encoding is ambiguous, there is no decoding function. + # + # @param types [Array] the ABI to be decoded. + # @param data [String] ABI data to be decoded. + # @raise [DecodingError] if you try to decode packed ABI data. + def decode_packed(types, data) + raise DecodingError, "Since the encoding is ambiguous, there is no decoding function." + end end end diff --git a/lib/eth/abi/decoder.rb b/lib/eth/abi/decoder.rb index 171e2263..01673831 100644 --- a/lib/eth/abi/decoder.rb +++ b/lib/eth/abi/decoder.rb @@ -102,7 +102,7 @@ def primitive_type(type, data) when "address" # decoded address with 0x-prefix - "0x#{Util.bin_to_hex data[12..-1]}" + Address.new(Util.bin_to_hex data[12..-1]).to_s.downcase when "string", "bytes" if type.sub_type.empty? size = Util.deserialize_big_endian_to_int data[0, 32] diff --git a/lib/eth/abi/encoder.rb b/lib/eth/abi/encoder.rb index f2ee06d8..e822024f 100644 --- a/lib/eth/abi/encoder.rb +++ b/lib/eth/abi/encoder.rb @@ -28,26 +28,28 @@ module Encoder # # @param type [Eth::Abi::Type] type to be encoded. # @param arg [String|Number] value to be encoded. + # @param packed [Boolean] use custom packed encoding. # @return [String] the encoded type. # @raise [EncodingError] if value does not match type. - def type(type, arg) + def type(type, arg, packed = false) 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 - size = type Type.size_type, arg.size + size = type(Type.size_type, arg.size, packed) padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) + return arg if packed "#{size}#{arg}#{padding}" elsif type.base_type == "tuple" && type.dimensions.size == 1 && type.dimensions[0] != 0 result = "" result += struct_offsets(type.nested_sub, arg) - result += arg.map { |x| type(type.nested_sub, x) }.join + result += arg.map { |x| type(type.nested_sub, x, packed) }.join result elsif type.dynamic? && arg.is_a?(Array) # encodes dynamic-sized arrays head, tail = "", "" - head += type(Type.size_type, arg.size) + head += type(Type.size_type, arg.size, packed) unless packed nested_sub = type.nested_sub nested_sub_size = type.nested_sub.size @@ -63,25 +65,25 @@ def type(type, arg) offset += total_bytes_length + 32 end - head += type(Type.size_type, offset) + head += type(Type.size_type, offset, packed) end elsif nested_sub.base_type == "tuple" && nested_sub.dynamic? head += struct_offsets(nested_sub, arg) end arg.size.times do |i| - head += type nested_sub, arg[i] + head += type(nested_sub, arg[i], packed) end "#{head}#{tail}" else if type.dimensions.empty? # encode a primitive type - primitive_type type, arg + primitive_type type, arg, packed else # encode static-size arrays - arg.map { |x| type(type.nested_sub, x) }.join + arg.map { |x| type(type.nested_sub, x, packed) }.join end end end @@ -90,30 +92,31 @@ def type(type, arg) # # @param type [Eth::Abi::Type] type to be encoded. # @param arg [String|Number] value to be encoded. + # @param packed [Boolean] use custom packed encoding. # @return [String] the encoded primitive type. # @raise [EncodingError] if value does not match type. # @raise [ValueOutOfBounds] if value is out of bounds for type. # @raise [EncodingError] if encoding fails for type. - def primitive_type(type, arg) + def primitive_type(type, arg, packed = false) case type.base_type when "uint" - uint arg, type + uint arg, type, packed when "bool" - bool arg + bool arg, packed when "int" - int arg, type - when "ureal", "ufixed" + int arg, type, packed + when "ureal", "ufixed" # TODO: Q9F ufixed arg, type - when "real", "fixed" + when "real", "fixed" # TODO: Q9F fixed arg, type when "string", "bytes" - bytes arg, type - when "tuple" + bytes arg, type, packed + when "tuple" # TODO: Q9F tuple arg, type when "hash" - hash arg, type + hash arg, type, packed when "address" - address arg + address arg, packed else raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}" end @@ -122,29 +125,39 @@ def primitive_type(type, arg) private # Properly encodes unsigned integers. - def uint(arg, type) + def uint(arg, type, packed) raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN real_size = type.sub_type.to_i i = arg.to_i raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size - Util.zpad_int i + if packed + len = real_size / 8 + return Util.zpad_int(i, len) + else + return Util.zpad_int(i) + end end # Properly encodes signed integers. - def int(arg, type) + def int(arg, type, packed) raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN real_size = type.sub_type.to_i i = arg.to_i raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1) - Util.zpad_int(i % 2 ** 256) + if packed + len = real_size / 8 + return Util.zpad_int(i % 2 ** real_size, len) + else + return Util.zpad_int(i % 2 ** 256) + end end # Properly encodes booleans. - def bool(arg) + def bool(arg, packed) raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass - Util.zpad_int(arg ? 1 : 0) + Util.zpad_int(arg ? 1 : 0, packed ? 1 : 32) end # Properly encodes unsigned fixed-point numbers. @@ -165,10 +178,13 @@ def fixed(arg, type) end # Properly encodes byte-strings. - def bytes(arg, type) + def bytes(arg, type, packed) raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String arg = handle_hex_string arg, type + # no padding or size handling for packed encoding + return arg if packed + if type.sub_type.empty? size = Util.zpad_int arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) @@ -235,20 +251,23 @@ def struct_offsets(type, arg) end # Properly encodes hash-strings. - def hash(arg, type) + def hash(arg, type, packed) size = type.sub_type.to_i raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32 if arg.is_a? Integer # hash from integer + return Util.int_to_big_endian arg if packed Util.zpad_int arg elsif arg.size == size # hash from encoded hash + return arg if packed Util.zpad arg, 32 elsif arg.size == size * 2 # hash from hexadecimal hash + return Util.hex_to_bin arg if packed Util.zpad_hex arg else raise EncodingError, "Could not parse hash: #{arg}" @@ -256,26 +275,31 @@ def hash(arg, type) end # Properly encodes addresses. - def address(arg) + def address(arg, packed) if arg.is_a? Address # from checksummed address with 0x prefix + return arg.to_s[2..-1].downcase if packed Util.zpad_hex arg.to_s[2..-1] elsif arg.is_a? Integer # address from integer + return Util.int_to_big_endian arg if packed Util.zpad_int arg elsif arg.size == 20 # address from encoded address + return arg if packed Util.zpad arg, 32 elsif arg.size == 40 # address from hexadecimal address + return Util.hex_to_bin arg if packed Util.zpad_hex arg elsif arg.size == 42 and arg[0, 2] == "0x" # address from hexadecimal address with 0x prefix + return Util.hex_to_bin arg if packed Util.zpad_hex arg[2..-1] else raise EncodingError, "Could not parse address: #{arg}" diff --git a/spec/eth/abi/encoder_spec.rb b/spec/eth/abi/encoder_spec.rb index 910e0d56..4f5ef2c1 100644 --- a/spec/eth/abi/encoder_spec.rb +++ b/spec/eth/abi/encoder_spec.rb @@ -97,4 +97,224 @@ expect { Abi::Encoder.primitive_type(t_address, "0x8cb9d52661513ac5490483c79ac715f5dd572bfb0xbd76086b38f2660fcaa65781ff5998f5c18e766d") }.to raise_error Abi::EncodingError expect { Abi::Encoder.primitive_type(Abi::Type.new("foo", 32, []), 12354235345634646546346346345) }.to raise_error Abi::EncodingError end + + describe "packed encoding" do + it "encodes packed types" do + expect(Util.bin_to_hex Abi.encode_packed(["uint8[]"], [[1, 2, 3]])).to eq "010203" + expect(Util.bin_to_hex Abi.encode_packed(["uint16[]"], [[1, 2, 3]])).to eq "000100020003" + expect(Util.bin_to_hex Abi.encode_packed(["uint32"], [17])).to eq "00000011" + expect(Util.bin_to_hex Abi.encode_packed(["uint64"], [17])).to eq "0000000000000011" + expect(Util.bin_to_hex Abi.encode_packed(["bool[]"], [[true, false]])).to eq "0100" + expect(Util.bin_to_hex Abi.encode_packed(["bool"], [true])).to eq "01" + expect(Util.bin_to_hex Abi.encode_packed(["int32[]"], [[1, 2, 3]])).to eq "000000010000000200000003" + expect(Util.bin_to_hex Abi.encode_packed(["int64[]"], [[1, 2, 3]])).to eq "000000000000000100000000000000020000000000000003" + expect(Util.bin_to_hex Abi.encode_packed(["int64"], [17])).to eq "0000000000000011" + expect(Util.bin_to_hex Abi.encode_packed(["int128"], [17])).to eq "00000000000000000000000000000011" + expect(Util.bin_to_hex Abi.encode_packed(["bytes1"], ["0x42"])).to eq "42" + expect(Util.bin_to_hex Abi.encode_packed(["bytes"], ["dave".b])).to eq "64617665" + expect(Util.bin_to_hex Abi.encode_packed(["string"], ["dave"])).to eq "64617665" + expect(Util.bin_to_hex Abi.encode_packed(["string"], ["Hello, World"])).to eq "48656c6c6f2c20576f726c64" + expect(Abi.encode_packed(["address"], ["\xff" * 20])).to eq "\xff" * 20 + expect(Abi.encode_packed(["address"], ["ff" * 20])).to eq "\xff" * 20 + expect(Abi.encode_packed(["address"], ["0x" + "ff" * 20])).to eq "\xff" * 20 + expect(Abi.encode_packed(["address"], [Address.new("0x" + "ff" * 20)])).to eq "ff" * 20 + expect(Util.bin_to_hex Abi.encode_packed(["hash32"], ["8<\xAE\xB6pn\x00\xE2\fr\x05XH\x88\xBAW\xBFV\xEA\xFFMDe\xA8<\x9C{\e!GH\xA6"])).to eq "383caeb6706e00e20c7205584888ba57bf56eaff4d4465a83c9c7b1b214748a6" + expect(Util.bin_to_hex Abi.encode_packed(["hash20"], ["H\x88\xBAW\xBFV\xEA\xFFMDe\xA8<\x9C{\e!GH\xA6"])).to eq "4888ba57bf56eaff4d4465a83c9c7b1b214748a6" + end + + context "wuminzhe's tests" do + # ref https://github.com/wuminzhe/abi_coder_rb/blob/701af2315cfc94a94872beb6c639ece400fca589/spec/packed_encoding_spec.rb + + it "bool" do + type = "bool" + value = true + data = "01" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "bytes" do + type = "bytes" + value = "dave".b + data = "64617665" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "types4" do + type = "bytes4" + value = "dave".b + data = "64617665" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "string" do + type = "string" + value = "dave" + data = "64617665" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "address1" do + type = "address" + value = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826" + data = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "address2" do + type = "address" + value = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826" + data = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "address3" do + type = "address" + value = 0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826 + data = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "uint32" do + type = "uint32" + value = 17 + data = "00000011" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + it "int64" do + type = "int64" + value = 17 + data = "0000000000000011" + + expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + end + + # it "(uint64)" do + # type = "(uint64)" + # value = [17] + # data = "0000000000000011" + + # expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + # end + + # it "(int32,uint64)" do + # type = "(int32,uint64)" + # value = [17, 17] + # # data = "000000110000000000000011" + + # expect do + # Abi.encode_packed(type, value) + # end.to raise_error("AbiCoderRb::Tuple with multi inner types is not supported in packed mode") + # end + + it "int32,uint64" do + types = %w[int32 uint64] + values = [17, 17] + data = "000000110000000000000011" + + expect(Util.bin_to_hex Abi.encode_packed(types, values)).to eq data + end + + # it "uint16[]" do + # type = "uint16[]" + # value = [1, 2] + # data = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" + + # expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + # end + + # it "bool[]" do + # type = "bool[]" + # value = [true, false] + # data = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" + + # expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + # end + + # it "(uint16[])" do + # type = "(uint16[])" + # value = [[1, 2]] + # data = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" + + # expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + # end + + # it "uint16[2]" do + # type = "uint16[2]" + # value = [1, 2] + # data = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002" + + # expect(Util.bin_to_hex Abi.encode_packed(type, value)).to eq data + # end + + # it "bytes[2]" do + # type = "bytes[2]" + # value = ["dave".b, "dave".b] + + # expect do + # Abi.encode_packed(type, value) + # end.to raise_error("AbiCoderRb::FixedArray with dynamic inner type is not supported in packed mode") + # end + + it "encode_packeds packed types" do + # expect( + # Abi.encode_packed("uint8[]", [1, 2, 3]) + # ).to eq( + # hex("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + # ) + + # expect( + # Abi.encode_packed("uint16[]", [1, 2, 3]) + # ).to eq( + # hex("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + # ) + + expect( + Abi.encode_packed("uint32", 17) + ).to eq Util.hex_to_bin "00000011" + + expect( + Abi.encode_packed("uint64", 17) + ).to eq Util.hex_to_bin "0000000000000011" + + # expect( + # Abi.encode_packed("bool[]", [true, false]) + # ).to eq( + # hex("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000") + # ) + + expect( + Abi.encode_packed("bool", true) + ).to eq Util.hex_to_bin "01" + + # expect( + # Abi.encode_packed("int32[]", [1, 2, 3]) + # ).to eq( + # hex("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + # ) + + # expect( + # Abi.encode_packed("int64[]", [1, 2, 3]) + # ).to eq( + # hex("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + # ) + + expect( + Abi.encode_packed("int64", 17) + ).to eq Util.hex_to_bin "0000000000000011" + + expect( + Abi.encode_packed("int128", 17) + ).to eq Util.hex_to_bin "00000000000000000000000000000011" + end + end + end end diff --git a/spec/eth/abi_spec.rb b/spec/eth/abi_spec.rb index cf188649..108024d4 100644 --- a/spec/eth/abi_spec.rb +++ b/spec/eth/abi_spec.rb @@ -311,6 +311,24 @@ end end + describe ".encode_packed .decode_packed" do + it "encodes the solidity docs example" do + # ref https://docs.soliditylang.org/en/v0.8.26/abi-spec.html#non-standard-packed-mode + + expect(Util.bin_to_hex Abi.encode_packed(["int16"], [-1])).to eq "ffff" + expect(Util.bin_to_hex Abi.encode_packed(["bytes1"], ["42".b])).to eq "42" + expect(Util.bin_to_hex Abi.encode_packed(["uint16"], [0x03])).to eq "0003" + expect(Util.bin_to_hex Abi.encode_packed(["string"], ["Hello, world!"])).to eq "48656c6c6f2c20776f726c6421" + expect(Util.bin_to_hex Abi.encode_packed(["int16", "bytes1", "uint16", "string"], [-1, "42", 0x03, "Hello, world!"])).to eq "ffff42000348656c6c6f2c20776f726c6421" + end + + it "won't decode packed abi" do + expect { + Abi.decode_packed(["int16", "bytes1", "uint16", "string"], "ffff42000348656c6c6f2c20776f726c6421") + }.to raise_error Abi::DecodingError, "Since the encoding is ambiguous, there is no decoding function." + end + end + describe "abicoder tests" do # https://github.com/rubycocos/blockchain/blob/ccef43a600e0832fb5e662bb0840656c974c0dc5/abicoder/test/test_spec.rb def assert(data, types, args) @@ -389,6 +407,107 @@ def assert(data, types, args) end end + describe "dynamic encoding" do + it "can encode array of string" do + encoded = 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 = 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 = Util.bin_to_hex(described_class.encode(["uint256[]", "string[]"], [[123, 456], ["hello", "world"]])) + expected = "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000568656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000" + expect(encoded).to eq(expected) + + encoded = 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 + + it "can encode array of dynamic structs" do + encoded = Util.bin_to_hex(described_class.encode([ + Abi::Type.parse("tuple[3]", [ + { + "type" => "tuple", + "name" => "nuu", + "components" => [ + "type" => "tuple", + "name" => "foo", + "components" => [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ], + ], + }, + ]), + Abi::Type.parse("tuple[]", [ + { + "type" => "uint256", + "name" => "id", + }, + { + "type" => "uint256", + "name" => "data", + }, + ]), + Abi::Type.parse("tuple[]", [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ]), + Abi::Type.parse("tuple[]", [ + { + "type" => "tuple", + "name" => "nuu", + "components" => [ + "type" => "tuple", + "name" => "foo", + "components" => [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ], + ], + }, + ]), + Abi::Type.parse("tuple[3]", [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ]), + ], [ + [ + { "nuu" => { "foo" => { "id" => "4", "name" => "nestedFoo" } } }, + { "nuu" => { "foo" => { "id" => "", "name" => "" } } }, + { "nuu" => { "foo" => { "id" => "4", "name" => "nestedFoo" } } }, + ], + [ + { "id" => 123, "data" => 123 }, + { "id" => 12, "data" => 33 }, + { "id" => 0, "data" => 0 }, + ], + [ + { "id" => "id", "name" => "name" }, + ], + [ + { "nuu" => { "foo" => { "id" => "4", "name" => "nestedFoo" } } }, + { "nuu" => { "foo" => { "id" => "4", "name" => "nestedFoo" } } }, + { "nuu" => { "foo" => { "id" => "", "name" => "" } } }, + ], + [ + { "id" => "id", "name" => "name" }, + { "id" => "id", "name" => "name" }, + { "id" => "id", "name" => "name" }, + ], + ])) + expected = "00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000008e000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096e6573746564466f6f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096e6573746564466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096e6573746564466f6f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096e6573746564466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046e616d6500000000000000000000000000000000000000000000000000000000" + expect(encoded).to eq(expected) + end + end + describe "edge cases" do it "test negative number" do types = ["int24"]