diff --git a/lib/eth/abi/type.rb b/lib/eth/abi/type.rb index 2320e5e4..17175771 100644 --- a/lib/eth/abi/type.rb +++ b/lib/eth/abi/type.rb @@ -210,7 +210,6 @@ def validate_base_type(base_type, sub_type) # booleans cannot have any suffix raise ParseError, "Bool cannot have suffix" unless sub_type.empty? else - # we cannot parse arbitrary types such as 'decimal' or 'hex' raise ParseError, "Unknown base type" end diff --git a/lib/eth/eip712.rb b/lib/eth/eip712.rb index 2c26761f..21d08da9 100644 --- a/lib/eth/eip712.rb +++ b/lib/eth/eip712.rb @@ -47,7 +47,7 @@ def type_dependencies(primary_type, types, result = []) # recursively look for further nested dependencies types[primary_type.to_sym].each do |t| - dependency = type_dependencies t[:type], types, result + _ = type_dependencies t[:type], types, result end return result end @@ -98,12 +98,14 @@ def hash_type(primary_type, types) end # Recursively ABI-encodes all data and types according to EIP-712. + # Defaults to packed solidity encoding for complex data, e.g., arrays. # # @param primary_type [String] the primary type which we want to encode. # @param data [Array] the data in the data structure we want to encode. # @param types [Array] all existing types in the data structure. + # @param packed [Bool] set true to return packed encoding (default: `false`). # @return [String] an ABI-encoded representation of the data and the types. - def encode_data(primary_type, data, types) + def encode_data(primary_type, data, types, packed = false) # first data field is the type hash encoded_types = ["bytes32"] @@ -113,7 +115,7 @@ def encode_data(primary_type, data, types) types[primary_type.to_sym].each do |field| value = data[field[:name].to_sym] type = field[:type] - raise NotImplementedError, "Arrays currently unimplemented for EIP-712." if type.end_with? "]" + packed = true if type.end_with? "]" if type == "string" encoded_types.push "bytes32" encoded_values.push Util.keccak256 value @@ -132,7 +134,7 @@ def encode_data(primary_type, data, types) end # all data is abi-encoded - return Abi.encode encoded_types, encoded_values + return Abi.encode encoded_types, encoded_values, packed end # Recursively ABI-encodes and hashes all data and types. diff --git a/spec/eth/abi_spec.rb b/spec/eth/abi_spec.rb index 48b85ab6..56018203 100644 --- a/spec/eth/abi_spec.rb +++ b/spec/eth/abi_spec.rb @@ -329,6 +329,30 @@ end end + describe ".solidity_packed" do + it "can encode packed abi" do + # https://ethereum.stackexchange.com/questions/72199/testing-sha256abi-encodepacked-argument + types_0 = ["bytes1", "bytes2"] + values_0 = ["a", "bc"] + packed_0 = Abi.solidity_packed(types_0, values_0) + expect(packed_0).to eq "abc" + types_1 = ["bytes2", "bytes1"] + values_1 = ["ab", "c"] + packed_1 = Abi.solidity_packed(types_1, values_1) + expect(packed_1).to eq "abc" + hash = Util.hex_to_bin "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45" + expect(Util.keccak256 packed_1).to eq hash + expect(packed_1).to eq packed_0 + expect(Util.keccak256 packed_1).to eq Util.keccak256 packed_0 + + types = ["string"] + values = ["Hello World!"] + packed = Abi.solidity_packed(types, values) + hash = Util.keccak256 packed + expect(hash).to eq Util.hex_to_bin "0x3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0" + end + end + describe "abicoder tests" do # https://github.com/rubycocos/blockchain/blob/ccef43a600e0832fb5e662bb0840656c974c0dc5/abicoder/test/test_spec.rb def assert(data, types, args) diff --git a/spec/eth/eip712_spec.rb b/spec/eth/eip712_spec.rb index 896eba6c..4e42facb 100644 --- a/spec/eth/eip712_spec.rb +++ b/spec/eth/eip712_spec.rb @@ -169,5 +169,90 @@ sig_string = "8255c17ce6be5fb6ee3430784a52a5163c63fc87e2dcae32251d9c49ba849fad7067454b0d7e694698c02e552fd7af283dcaadc754d58ecba978856de8742e361b" expect(key.sign_typed_data data_string).to eq sig_string end + + it "can abi-encode complex nested data" do + # ref https://github.com/q9f/eth.rb/issues/127#issuecomment-1447441576 + key = Key.new(priv: "0x8e589ba6280400cfa426229684f7c2ac9ebf132f7ad658a82ed57553a0a9dee8") + data = { + :types => { + :OrderComponents => [ + { :name => "offerer", :type => "address" }, + { :name => "zone", :type => "address" }, + { :name => "offer", :type => "OfferItem[]" }, + { :name => "consideration", :type => "ConsiderationItem[]" }, + { :name => "orderType", :type => "uint8" }, + { :name => "startTime", :type => "uint256" }, + { :name => "endTime", :type => "uint256" }, + { :name => "zoneHash", :type => "bytes32" }, + { :name => "salt", :type => "uint256" }, + { :name => "conduitKey", :type => "bytes32" }, + { :name => "counter", :type => "uint256" }, + ], + :OfferItem => [ + { :name => "itemType", :type => "uint8" }, + { :name => "token", :type => "address" }, + { :name => "identifierOrCriteria", :type => "uint256" }, + { :name => "startAmount", :type => "uint256" }, + { :name => "endAmount", :type => "uint256" }, + ], + :ConsiderationItem => [ + { :name => "itemType", :type => "uint8" }, + { :name => "token", :type => "address" }, + { :name => "identifierOrCriteria", :type => "uint256" }, + { :name => "startAmount", :type => "uint256" }, + { :name => "endAmount", :type => "uint256" }, + { :name => "recipient", :type => "address" }, + ], + :EIP712Domain => [ + { :name => "name", :type => "string" }, + { :name => "version", :type => "string" }, + { :name => "chainId", :type => "uint256" }, + { :name => "verifyingContract", :type => "address" }, + ], + }, + :domain => { + :name => "Seaport", + :version => "1.1", + :chainId => 1, + :verifyingContract => "0x00000000006c3852cbef3e08e8df289169ede581", + }, + :primaryType => "OrderComponents", + :message => { + :offerer => "0x0000000000000000000000000000000000000001", + :zone => "0x0000000000000000000000000000000000000000", + :zoneHash => "0x0000000000000000000000000000000000000000000000000000000000000000", + :offer => [ + { + :itemType => 2, + :token => "0x0000000000000000000000000000000000000002", + :identifierOrCriteria => 2, + :startAmount => 1, + :endAmount => 1, + }, + ], + :consideration => [ + { + :itemType => 0, + :identifierOrCriteria => 0, + :startAmount => 9750000000000000000, + :endAmount => 9750000000000000000, + :recipient => "0x0000000000000000000000000000000000000003", + }, + { :itemType => 0, + :identifierOrCriteria => 0, + :startAmount => 250000000000000000, + :endAmount => 250000000000000000, + :recipient => "0x0000000000000000000000000000000000000004" }, + ], + :salt => 12686911856931635052326433555881236148, + :conduitKey => "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", + :nonce => 0, + }, + } + + sig_bytes = "ddefbbe703f59949a87ece451321924bb9297100dda63e1f39559b72db3ec9e83dae2056c25b52ddb8bd53ab536e84d2e4f70d98219ed14e46b021a59aefb4eb1c" + pending("https://github.com/q9f/eth.rb/issues/127") + expect(key.sign_typed_data data).to eq sig_bytes + end end end