diff --git a/lib/eth/abi.rb b/lib/eth/abi.rb index 4b763e89..6dfbc6cc 100644 --- a/lib/eth/abi.rb +++ b/lib/eth/abi.rb @@ -45,7 +45,7 @@ class ValueOutOfBounds < StandardError; end def encode(types, args) # parse all types - parsed_types = types.map { |t| Type.parse(t) } + parsed_types = types.map { |t| Type === t ? t : Type.parse(t) } # prepare the "head" head_size = (0...args.size) @@ -81,12 +81,16 @@ def encode_type(type, arg) size = encode_type Type.size_type, arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) return "#{size}#{arg}#{padding}" - elsif type.dynamic? - raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array + elsif type.base_type == "tuple" && type.dimensions.size == 1 && type.dimensions[0] != 0 + result = "" + result += encode_struct_offsets(type.nested_sub, arg) + result += arg.map { |x| encode_type(type.nested_sub, x) }.join + result + elsif type.dynamic? && arg.is_a?(Array) # encodes dynamic-sized arrays head, tail = "", "" - head += encode_type Type.size_type, arg.size + head += encode_type(Type.size_type, arg.size) nested_sub = type.nested_sub nested_sub_size = type.nested_sub.size @@ -102,8 +106,10 @@ def encode_type(type, arg) offset += total_bytes_length + 32 end - head += encode_type Type.size_type, offset + head += encode_type(Type.size_type, offset) end + elsif nested_sub.base_type == "tuple" && nested_sub.dynamic? + head += encode_struct_offsets(nested_sub, arg) end arg.size.times do |i| @@ -145,6 +151,8 @@ def encode_primitive_type(type, arg) return encode_fixed arg, type when "string", "bytes" return encode_bytes arg, type + when "tuple" + return encode_tuple arg, type when "hash" return encode_hash arg, type when "address" @@ -381,6 +389,56 @@ def encode_bytes(arg, type) end end + # Properly encodes tuples. + def encode_tuple(arg, type) + raise EncodingError, "Expecting Hash: #{arg}" unless arg.instance_of? Hash + raise EncodingError, "Expecting #{type.components.size} elements: #{arg}" unless arg.size == type.components.size + + static_size = 0 + type.components.each_with_index do |component, i| + if type.components[i].dynamic? + static_size += 32 + else + static_size += Util.ceil32(type.components[i].size || 0) + end + end + + dynamic_offset = static_size + offsets_and_static_values = [] + dynamic_values = [] + + type.components.each_with_index do |component, i| + component_type = type.components[i] + if component_type.dynamic? + offsets_and_static_values << encode_type(Type.size_type, dynamic_offset) + dynamic_value = encode_type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name]) + dynamic_values << dynamic_value + dynamic_offset += dynamic_value.size + else + offsets_and_static_values << encode_type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name]) + end + end + + offsets_and_static_values.join + dynamic_values.join + end + + # Properly encode struct offsets. + def encode_struct_offsets(type, arg) + result = "" + offset = arg.size + tails_encoding = arg.map { |a| encode_type(type, a) } + arg.size.times do |i| + if i == 0 + offset *= 32 + else + offset += tails_encoding[i - 1].size + end + offset_string = encode_type(Type.size_type, offset) + result += offset_string + end + result + end + # Properly encodes hash-strings. def encode_hash(arg, type) size = type.sub_type.to_i diff --git a/lib/eth/abi/type.rb b/lib/eth/abi/type.rb index 024ffc0e..51becb67 100644 --- a/lib/eth/abi/type.rb +++ b/lib/eth/abi/type.rb @@ -35,18 +35,28 @@ class ParseError < StandardError; end # The dimension attribute, e.g., `[10]` for an array of size 10. attr :dimensions + # The components of a tuple type. + attr :components + + # The name of tuple component. + attr :name + # Create a new Type object for base types, sub types, and dimensions. # Should not be used; use {Type.parse} instead. # # @param base_type [String] the base-type attribute. # @param sub_type [String] the sub-type attribute. # @param dimensions [Array] the dimension attribute. + # @param components [Array] the components attribute. + # @param component_name [String] the tuple component's name. # @return [Eth::Abi::Type] an ABI type object. - def initialize(base_type, sub_type, dimensions) + def initialize(base_type, sub_type, dimensions, components = nil, component_name = nil) sub_type = sub_type.to_s @base_type = base_type @sub_type = sub_type @dimensions = dimensions + @components = components + @name = component_name end # Converts the self.parse method into a constructor. @@ -56,9 +66,12 @@ def initialize(base_type, sub_type, dimensions) # Creates a new Type upon success (using konstructor). # # @param type [String] a common Solidity type. + # @param components [Array] the components attribute. + # @param component_name [String] the tuple component's name. # @return [Eth::Abi::Type] a parsed Type object. # @raise [ParseError] if it fails to parse the type. - def parse(type) + def parse(type, components = nil, component_name = nil) + return type if type.is_a?(Type) _, base_type, sub_type, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a # type dimension can only be numeric @@ -73,6 +86,8 @@ def parse(type) @base_type = base_type @sub_type = sub_type @dimensions = dims.map { |x| x[1...-1].to_i } + @components = components.map { |component| Eth::Abi::Type.parse(component["type"], component.dig("components"), component.dig("name")) } unless components.nil? + @name = component_name end # Creates a new uint256 type used for size. @@ -98,15 +113,13 @@ def ==(another_type) def size s = nil if dimensions.empty? - unless ["string", "bytes"].include?(base_type) and sub_type.empty? + if !(["string", "bytes", "tuple"].include?(base_type) and sub_type.empty?) s = 32 + elsif base_type == "tuple" && components.none?(&:dynamic?) + s = components.sum(&:size) end - else - unless dimensions.last == 0 - unless nested_sub.dynamic? - s = dimensions.last * nested_sub.size - end - end + elsif dimensions.last != 0 && !nested_sub.dynamic? + s = dimensions.last * nested_sub.size end @size ||= s end @@ -122,7 +135,24 @@ def dynamic? # # @return [Eth::Abi::Type] nested sub-type. def nested_sub - @nested_sub ||= self.class.new(base_type, sub_type, dimensions[0...-1]) + @nested_sub ||= self.class.new(base_type, sub_type, dimensions[0...-1], components, name) + end + + # Allows exporting the type as string. + # + # @return [String] the type string. + def to_s + if base_type == "tuple" + "(" + components.map(&:to_s).join(",") + ")" + (dimensions.size > 0 ? dimensions.map { |x| "[#{x == 0 ? "" : x}]" }.join : "") + elsif dimensions.empty? + if %w[string bytes].include?(base_type) && sub_type.empty? + base_type + else + "#{base_type}#{sub_type}" + end + else + "#{base_type}#{sub_type}#{dimensions.map { |x| "[#{x == 0 ? "" : x}]" }.join}" + end end private @@ -138,6 +168,10 @@ def validate_base_type(base_type, sub_type) # bytes can be no longer than 32 bytes raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub_type.empty? || sub_type.to_i <= 32 + when "tuple" + + # tuples can not have any suffix + raise ParseError, "Tuple type must have no suffix or numerical suffix" unless sub_type.empty? when "uint", "int" # integers must have a numerical suffix diff --git a/lib/eth/client.rb b/lib/eth/client.rb index 39bfb27c..f67b30fe 100644 --- a/lib/eth/client.rb +++ b/lib/eth/client.rb @@ -439,7 +439,7 @@ def call_raw(contract, func, *args, **kwargs) # Encodes function call payloads. def call_payload(fun, args) - types = fun.inputs.map { |i| i.type } + types = fun.inputs.map(&:parsed_type) encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args)) Util.prefix_hex(fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str)) end diff --git a/lib/eth/contract/function.rb b/lib/eth/contract/function.rb index d89add3d..6d3c317c 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.raw_type }.join(",")})" + "#{name}(#{inputs.map { |x| x.parsed_type.to_s }.join(",")})" end # Encodes a function signature. diff --git a/lib/eth/contract/function_input.rb b/lib/eth/contract/function_input.rb index aba23781..c9088adb 100644 --- a/lib/eth/contract/function_input.rb +++ b/lib/eth/contract/function_input.rb @@ -26,7 +26,7 @@ class Contract::FunctionInput # @param data [Hash] contract abi data. def initialize(data) @raw_type = data["type"] - @type = Eth::Abi::Type.parse(data["type"]) + @type = Eth::Abi::Type.parse(data["type"], data["components"]) @name = data["name"] end @@ -34,5 +34,10 @@ def initialize(data) def type @type.base_type + @type.sub_type + @type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("") end + + # Returns parsed types. + def parsed_type + @type + end end end diff --git a/spec/eth/abi/type_spec.rb b/spec/eth/abi/type_spec.rb index 30ebfa4b..6b61975d 100644 --- a/spec/eth/abi/type_spec.rb +++ b/spec/eth/abi/type_spec.rb @@ -11,6 +11,9 @@ expect(Abi::Type.new "bytes", "32", []).to eq Abi::Type.parse("bytes32") expect(Abi::Type.new "uint", 256, [10]).to eq Abi::Type.parse("uint256[10]") expect(Abi::Type.new "fixed", "128x128", [1, 2, 3, 0]).to eq Abi::Type.parse("fixed128x128[1][2][3][]") + expect(Abi::Type.new "tuple", nil, []).to eq Abi::Type.parse("tuple") + expect(Abi::Type.new "tuple", nil, [0]).to eq Abi::Type.parse("tuple[]") + expect(Abi::Type.new "tuple", nil, [10]).to eq Abi::Type.parse("tuple[10]") end end @@ -51,6 +54,27 @@ end end + describe ".dynamic?" do + it "can tell if a type is dynamic" do + expect(Abi::Type.parse("string").dynamic?).to eq(true) + expect(Abi::Type.parse("bytes").dynamic?).to eq(true) + expect(Abi::Type.parse("uint256[]").dynamic?).to eq(true) + expect(Abi::Type.parse("uint256[4][]").dynamic?).to eq(true) + + expect(Abi::Type.parse("bytes32").dynamic?).to eq(false) + expect(Abi::Type.parse("uint256").dynamic?).to eq(false) + expect(Abi::Type.parse("fixed128x128").dynamic?).to eq(false) + expect(Abi::Type.parse("bool").dynamic?).to eq(false) + + expect(Abi::Type.parse("uint256[2]").dynamic?).to eq(false) + expect(Abi::Type.parse("address[2][2]").dynamic?).to eq(false) + expect(Abi::Type.parse("ufixed192x64[2][2][2][2][2]").dynamic?).to eq(false) + + expect(Abi::Type.parse("tuple[]", [{ "type" => "bytes8" }]).dynamic?).to eq(true) + expect(Abi::Type.parse("tuple[2]", [{ "type" => "bytes8" }]).dynamic?).to eq(false) + end + end + describe ".size .nested_sub" do it "can compute the type size" do @@ -68,6 +92,9 @@ expect(Abi::Type.parse("uint256[2]").size).to eq 64 expect(Abi::Type.parse("address[2][2]").size).to eq 128 expect(Abi::Type.parse("ufixed192x64[2][2][2][2][2]").size).to eq 1024 + + expect(Abi::Type.parse("tuple[]", [{ "type" => "uint256" }]).size).to eq(nil) + expect(Abi::Type.parse("tuple[10]", [{ "type" => "uint256" }]).size).to eq(320) end it "can nest sub types" do @@ -78,4 +105,84 @@ expect(Abi::Type.parse("uint256[2][2]").nested_sub.dimensions).to eq [2] end end + + describe ".to_s" do + it "serializes type into raw type string" do + expect(Abi::Type.parse("string").to_s).to eq("string") + expect(Abi::Type.parse("bytes").to_s).to eq("bytes") + expect(Abi::Type.parse("bytes32").to_s).to eq("bytes32") + expect(Abi::Type.parse("string[]").to_s).to eq("string[]") + expect(Abi::Type.parse("string[10]").to_s).to eq("string[10]") + expect(Abi::Type.parse("uint256").to_s).to eq("uint256") + expect(Abi::Type.parse("uint256[2]").to_s).to eq("uint256[2]") + expect(Abi::Type.parse("uint256[2][]").to_s).to eq("uint256[2][]") + expect(Abi::Type.parse("uint256[2][2]").to_s).to eq("uint256[2][2]") + expect(Abi::Type.parse("tuple[]", [ + { + "type" => "string", + }, + { + "type" => "bytes", + }, + ]).to_s).to eq("(string,bytes)[]") + expect(Abi::Type.parse("tuple[10]", [ + { + "type" => "string", + }, + { + "type" => "bytes", + }, + ]).to_s).to eq("(string,bytes)[10]") + expect(Abi::Type.parse("tuple", [ + { + "type" => "string", + }, + { + "type" => "string", + }, + { + "components" => [ + { + "type" => "uint256", + }, + { + "type" => "string", + }, + { + "components" => [ + { + "type" => "string", + }, + { + "type" => "bytes", + }, + ], + "type" => "tuple", + }, + ], + "type" => "tuple[]", + }, + { + "type" => "uint256", + }, + { + "type" => "string[]", + }, + { + "type" => "bytes[10]", + }, + { + "components" => [ + { + "type" => "string", + }, + { + "type" => "bytes", + }, + ], + "type" => "tuple", + }, + ]).to_s).to eq("(string,string,(uint256,string,(string,bytes))[],uint256,string[],bytes[10],(string,bytes))") + end + end end diff --git a/spec/eth/abi_spec.rb b/spec/eth/abi_spec.rb index 0c302b33..b37738cf 100644 --- a/spec/eth/abi_spec.rb +++ b/spec/eth/abi_spec.rb @@ -31,6 +31,83 @@ expected = "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000568656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000472756279000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008657468657265756d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008" expect(encoded).to eq(expected) end + + it "can encode array of dynamic structs" do + encoded = Eth::Util.bin_to_hex(described_class.encode([ + Eth::Abi::Type.parse("tuple[3]", [ + { + "type" => "tuple", + "name" => "nuu", + "components" => [ + "type" => "tuple", + "name" => "foo", + "components" => [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ], + ], + }, + ]), + Eth::Abi::Type.parse("tuple[]", [ + { + "type" => "uint256", + "name" => "id", + }, + { + "type" => "uint256", + "name" => "data", + }, + ]), + Eth::Abi::Type.parse("tuple[]", [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ]), + Eth::Abi::Type.parse("tuple[]", [ + { + "type" => "tuple", + "name" => "nuu", + "components" => [ + "type" => "tuple", + "name" => "foo", + "components" => [ + { "type" => "string", "name" => "id" }, + { "type" => "string", "name" => "name" }, + ], + ], + }, + ]), + Eth::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 it "can encode abi" do diff --git a/spec/eth/contract/function_spec.rb b/spec/eth/contract/function_spec.rb index 9ee29dfc..35fcd347 100644 --- a/spec/eth/contract/function_spec.rb +++ b/spec/eth/contract/function_spec.rb @@ -31,4 +31,26 @@ signature = Contract::Function.calc_signature(functions[0].name, functions[0].inputs) expect(Contract::Function.encoded_function_signature(signature)).to eq("dd62ed3e") end + + context "with tuple params" do + let(:erc20_abi_file) { File.read "spec/fixtures/abi/Tuple.json" } + let(:abi) { JSON.parse erc20_abi_file } + subject(:functions) { abi.select { |x| x["type"] == "function" }.map { |fun| Eth::Contract::Function.new(fun) } } + + it "calculates signature with tuple params" do + signature = Contract::Function.calc_signature(functions[0].name, functions[0].inputs) + expect(Contract::Function.encoded_function_signature(signature)).to eq("b68f14a0") + end + end + + context "with complex tuple params" do + let(:erc20_abi_file) { File.read "spec/fixtures/abi/Tuple2.json" } + let(:abi) { JSON.parse erc20_abi_file } + subject(:functions) { abi.select { |x| x["type"] == "function" }.map { |fun| Eth::Contract::Function.new(fun) } } + + it "calculates signature with tuple params" do + signature = Contract::Function.calc_signature(functions[0].name, functions[0].inputs) + expect(Contract::Function.encoded_function_signature(signature)).to eq("1b9faea1") + end + end end diff --git a/spec/eth/contract_spec.rb b/spec/eth/contract_spec.rb index cc568adf..ed493614 100644 --- a/spec/eth/contract_spec.rb +++ b/spec/eth/contract_spec.rb @@ -65,5 +65,43 @@ mortal = Contract.from_file(file: file, contract_index: 1) expect(mortal).to be_instance_of(Eth::Contract::Mortal) end + + it "supports contract with tuples" do + file = "spec/fixtures/abi/Tuple.json" + abi = JSON.parse(File.read(file)) + tuples = Contract.from_abi(name: "Tuple", address: "0x0000000000000000000000000000000000000000", abi: abi) + expect(tuples.functions[0].inputs[0].type).to eq("tuple") + expect(tuples.functions[0].inputs[0].parsed_type.components.size).to eq(7) + expect(tuples.functions[0].inputs[0].parsed_type.components[0].base_type).to eq("string") + expect(tuples.functions[0].inputs[0].parsed_type.components[0].dimensions).to eq([]) + + expect(tuples.functions[0].inputs[0].parsed_type.components[1].base_type).to eq("string") + expect(tuples.functions[0].inputs[0].parsed_type.components[1].dimensions).to eq([]) + + expect(tuples.functions[0].inputs[0].parsed_type.components[2].base_type).to eq("tuple") + expect(tuples.functions[0].inputs[0].parsed_type.components[2].dimensions).to eq([0]) + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components.size).to eq(3) + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components[0].base_type).to eq("uint") + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components[1].base_type).to eq("string") + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components[2].base_type).to eq("tuple") + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components[2].components.size).to eq(2) + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components[2].components[0].base_type).to eq("string") + expect(tuples.functions[0].inputs[0].parsed_type.components[2].components[2].components[1].base_type).to eq("bytes") + + expect(tuples.functions[0].inputs[0].parsed_type.components[3].base_type).to eq("uint") + expect(tuples.functions[0].inputs[0].parsed_type.components[3].sub_type).to eq("256") + + expect(tuples.functions[0].inputs[0].parsed_type.components[4].base_type).to eq("string") + expect(tuples.functions[0].inputs[0].parsed_type.components[4].dimensions).to eq([0]) + + expect(tuples.functions[0].inputs[0].parsed_type.components[5].base_type).to eq("bytes") + expect(tuples.functions[0].inputs[0].parsed_type.components[5].dimensions).to eq([10]) + + expect(tuples.functions[0].inputs[0].parsed_type.components[6].base_type).to eq("tuple") + expect(tuples.functions[0].inputs[0].parsed_type.components[6].dimensions).to eq([]) + expect(tuples.functions[0].inputs[0].parsed_type.components[6].components.size).to eq(2) + expect(tuples.functions[0].inputs[0].parsed_type.components[6].components[0].base_type).to eq("string") + expect(tuples.functions[0].inputs[0].parsed_type.components[6].components[1].base_type).to eq("bytes") + end end end diff --git a/spec/fixtures/abi/Tuple.json b/spec/fixtures/abi/Tuple.json new file mode 100644 index 00000000..a0315640 --- /dev/null +++ b/spec/fixtures/abi/Tuple.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "var1", + "type": "string" + }, + { + "internalType": "string", + "name": "var2", + "type": "string" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "var1", + "type": "uint256" + }, + { + "internalType": "string", + "name": "var2", + "type": "string" + }, + { + "components": [ + { + "internalType": "string", + "name": "var1", + "type": "string" + }, + { + "internalType": "bytes", + "name": "var2", + "type": "bytes" + } + ], + "internalType": "struct Tuple.Tuple3", + "name": "var3", + "type": "tuple" + } + ], + "internalType": "struct Tuple.Tuple2[]", + "name": "var3", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "var4", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "var5", + "type": "string[]" + }, + { + "internalType": "bytes[10]", + "name": "var6", + "type": "bytes[10]" + }, + { + "components": [ + { + "internalType": "string", + "name": "var1", + "type": "string" + }, + { + "internalType": "bytes", + "name": "var2", + "type": "bytes" + } + ], + "internalType": "struct Tuple.Tuple3", + "name": "var7", + "type": "tuple" + } + ], + "internalType": "struct Tuple.Tuple1", + "name": "param1", + "type": "tuple" + } + ], + "name": "func1", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/spec/fixtures/abi/Tuple2.json b/spec/fixtures/abi/Tuple2.json new file mode 100644 index 00000000..5a88714c --- /dev/null +++ b/spec/fixtures/abi/Tuple2.json @@ -0,0 +1,123 @@ +[ + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "internalType": "struct Tuple2.Foo", + "name": "foo", + "type": "tuple" + } + ], + "internalType": "struct Tuple2.Nuu", + "name": "nuu", + "type": "tuple" + } + ], + "internalType": "struct Tuple2.Nar[3]", + "name": "var1", + "type": "tuple[3]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + } + ], + "internalType": "struct Tuple2.Bar[]", + "name": "var2", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "internalType": "struct Tuple2.Foo[]", + "name": "var3", + "type": "tuple[]" + }, + { + "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "internalType": "struct Tuple2.Foo", + "name": "foo", + "type": "tuple" + } + ], + "internalType": "struct Tuple2.Nuu", + "name": "nuu", + "type": "tuple" + } + ], + "internalType": "struct Tuple2.Nar[]", + "name": "var4", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "internalType": "struct Tuple2.Foo[3]", + "name": "var5", + "type": "tuple[3]" + } + ], + "name": "idNarBarFooNarFooArrays", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/spec/fixtures/contracts/tuple.sol b/spec/fixtures/contracts/tuple.sol new file mode 100644 index 00000000..0ab51065 --- /dev/null +++ b/spec/fixtures/contracts/tuple.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8; + +contract Tuple { + struct Tuple1 { + string var1; + string var2; + Tuple2[] var3; + uint256 var4; + string[] var5; + bytes[10] var6; + Tuple3 var7; + } + + struct Tuple2 { + uint var1; + string var2; + Tuple3 var3; + } + + struct Tuple3 { + string var1; + bytes var2; + } + + function func1(Tuple1 calldata param1) public {} +} diff --git a/spec/fixtures/contracts/tuple2.sol b/spec/fixtures/contracts/tuple2.sol new file mode 100644 index 00000000..5ee79812 --- /dev/null +++ b/spec/fixtures/contracts/tuple2.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8; + +contract Tuple2 { + struct Nar { + Nuu nuu; + } + + struct Nuu { + Foo foo; + } + + struct Foo { + string id; + string name; + } + + struct Bar { + uint256 id; + uint256 data; + } + + function idNarBarFooNarFooArrays(Nar[3] calldata var1, Bar[] calldata var2, Foo[] calldata var3, Nar[] calldata var4, Foo[3] calldata var5) public { + } +}