0

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?

1

2 Answers 2

0

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}
# }]
Sign up to request clarification or add additional context in comments.

Comments

0

You can decode your struct by declaring the types as a single tuple matching all fields (including the nested struct):

# match your Solidity struct:
# (address smartAccount, string superChainId, uint256 points, uint16 level,
#  Noun noun) where Noun is (uint48 background, uint48 body, uint48 accessory, uint48 head, uint48 glasses)
types = [
  "tuple(address,string,uint256,uint16,tuple(uint48,uint48,uint48,uint48,uint48))"
]

# decode returns an Array whose first element is your struct as a flat Array
struct_array, = Eth::Abi.decode(types, response["result"])

# destructure the top‐level struct
smart_account, super_chain_id, points, level, noun = struct_array

# destructure the nested Noun struct
background, body, accessory, head, glasses = noun

# now use your values:
puts "Account: #{smart_account}"
puts "Chain ID: #{super_chain_id}"
puts "Points: #{points}"
puts "Level: #{level}"
puts "Noun parts: #{background}, #{body}, #{accessory}, #{head}, #{glasses}"

1 Comment

Thanks. But have you tried that out? It is giving me the same error like the one I am reporting in my question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.