I am using eth gem to interact with a Smart Contract in EVM.
The Smart Contract has a function whose ABI in JSON format is this:
{
"inputs": [
{"internalType": "address", "name": "_owner", "type": "address"}
],
"name": "getUserSuperChainAccount",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "smartAccount",
"type": "address"
},
{"internalType": "string", "name": "superChainID", "type": "string"},
{"internalType": "uint256", "name": "points", "type": "uint256"},
{"internalType": "uint16", "name": "level", "type": "uint16"},
{
"components": [
{
"internalType": "uint48",
"name": "background",
"type": "uint48"
},
{"internalType": "uint48", "name": "body", "type": "uint48"},
{"internalType": "uint48", "name": "accessory", "type": "uint48"},
{"internalType": "uint48", "name": "head", "type": "uint48"},
{"internalType": "uint48", "name": "glasses", "type": "uint48"}
],
"internalType": "struct NounMetadata",
"name": "noun",
"type": "tuple"
}
],
"internalType": "struct ISuperChainModule.Account",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
}
I am calling this function and I can confirm that it returns the correct response:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a746d1f8880503fee173ba4ab255c8223ba8f3ad000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000aa00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000006e00000000000000000000000000000000000000000000000000000000000000be000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000147275646e6576736b792e70726f73706572697479000000000000000000000000"
In order to parse the result, I am writing Ruby code like this:
types = ["tuple(address,string,uint256,uint16,tuple(uint48,uint48,uint48,uint48,uint48))"]
data = Eth::Abi.decode(types, response["result"])
The last line is failing with the error:
undefined method `none?' for nil:NilClass (NoMethodError)
elsif base_type == "tuple" && components.none?(&:dynamic?)
^^^^^^
So, it seems that the types
argument is not set as it should.
Can anyone help?
While I agree that the string parsing issue should definitely be considered a bug.
In reviewing the spec tests I noticed there does not appear to be any testing for nested tuples.
What I did identify is that decode
uses Eth::Abi::Type::parse
and those spec tests use the same structure as your referenced JSON document.
So rather than relying on the flawed String
parsing for "type" (also in Eth::Abi::Type::parse
) we can instead use the JSON you have to pass the base type and its components directly to Eth::Abi::Type::parse
which outputs the expected results:
Your JSON
json_doc = <<JSON
{
"inputs": [
{"internalType": "address", "name": "_owner", "type": "address"}
],
"name": "getUserSuperChainAccount",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "smartAccount",
"type": "address"
},
{"internalType": "string", "name": "superChainID", "type": "string"},
{"internalType": "uint256", "name": "points", "type": "uint256"},
{"internalType": "uint16", "name": "level", "type": "uint16"},
{
"components": [
{
"internalType": "uint48",
"name": "background",
"type": "uint48"
},
{"internalType": "uint48", "name": "body", "type": "uint48"},
{"internalType": "uint48", "name": "accessory", "type": "uint48"},
{"internalType": "uint48", "name": "head", "type": "uint48"},
{"internalType": "uint48", "name": "glasses", "type": "uint48"}
],
"internalType": "struct NounMetadata",
"name": "noun",
"type": "tuple"
}
],
"internalType": "struct ISuperChainModule.Account",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
}
JSON
Your Response Object
response = {"result" => "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a746d1f8880503fee173ba4ab255c8223ba8f3ad000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000aa00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000006e00000000000000000000000000000000000000000000000000000000000000be000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000147275646e6576736b792e70726f73706572697479000000000000000000000000"}
Working Code
require 'eth'
require 'json'
types = JSON.parse(json_doc)["outputs"].map do |type|
Eth::Abi::Type.parse(type["type"],type["components"])
end
Eth::Abi.decode(types, response["result"])
#=> [
# {"smartAccount"=>"0xa746d1f8880503fee173ba4ab255c8223ba8f3ad",
# "superChainID"=>"rudnevsky.prosperity",
# "points"=>170,
# "level"=>2,
# "noun"=>{
# "background"=>1,
# "body"=>29,
# "accessory"=>110,
# "head"=>190,
# "glasses"=>14}
# }]