q9f / eth.rb

a straightforward library to build, sign, and broadcast ethereum transactions anywhere you can run ruby.
https://q9f.github.io/eth.rb
Apache License 2.0
196 stars 85 forks source link

calling methods with "tuple" type return gives error #273

Open vibern0 opened 2 months ago

vibern0 commented 2 months ago

I'm trying to get data for an EAS attestation, but i can't because the result is a tuple and it's giving error.

The ABI

{
"inputs": [{ "internalType": "bytes32", "name": "uid", "type": "bytes32" }],
"name": "getAttestation",
"outputs": [
    {
    "components": [
        { "internalType": "bytes32", "name": "uid", "type": "bytes32" },
        { "internalType": "bytes32", "name": "schema", "type": "bytes32" },
        { "internalType": "uint64", "name": "time", "type": "uint64" },
        { "internalType": "uint64", "name": "expirationTime", "type": "uint64" },
        { "internalType": "uint64", "name": "revocationTime", "type": "uint64" },
        { "internalType": "bytes32", "name": "refUID", "type": "bytes32" },
        { "internalType": "address", "name": "recipient", "type": "address" },
        { "internalType": "address", "name": "attester", "type": "address" },
        { "internalType": "bool", "name": "revocable", "type": "bool" },
        { "internalType": "bytes", "name": "data", "type": "bytes" }
    ],
    "internalType": "struct Attestation",
    "name": "",
    "type": "tuple"
    }
],
"stateMutability": "view",
"type": "function"
}

the error

NoMethodError: undefined method `none?` for nil:NilClass

          elsif base_type == "tuple" && components.none?(&:dynamic?)
                                                  ^^^^^^
from ~/.rbenv/versions/3.1.5/lib/ruby/gems/3.1.0/gems/eth-0.5.11/lib/eth/abi/type.rb:118:in `size'
q9f commented 1 month ago

Can you give me an address or test data that makes this fail?

Can you confirm this also happens with latest main branch? Just clone the repository, run bin/console and create your client / contract there.

bshyong commented 1 month ago

@q9f I see this happening too; I think it's because in the ABI file, the components are nested.

Here's an example.

Eth::Contract.from_abi using the ABI below

and calling contract with address "0xbb505c54d71e9e599cb8435b4f0ceec05fc71cbd" on Base.

eg. client.call(contract_instance, "getReserveStatus", [64]

client.call_raw which is ultimately is called gets types like this types = func.outputs.map { |i| i.type }

but from the ABI, func.outputs is an array with a single hash [ { components: "" } ]

https://github.com/q9f/eth.rb/blob/4182fa7993a9a6c6648e065dcf6748fb8d738895/lib/eth/client.rb#L455

  {
    "inputs": [
      {
        "internalType": "uint256[]",
        "name": "reserveIdArr",
        "type": "uint256[]"
      }
    ],
    "name": "getReserveStatus",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint256",
            "name": "reserveId",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "underlyingTokenAddress",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "eTokenAddress",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "stakingAddress",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "totalLiquidity",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "totalBorrows",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "exchangeRate",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "borrowingRate",
            "type": "uint256"
          }
        ],
        "internalType": "struct ILendingPool.ReserveStatus[]",
        "name": "statusArr",
        "type": "tuple[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
q9f commented 1 month ago

Thank you. I used this ABI to ensure the Abi::Type parser works correctly, it has all types, names, and components correctly.

# name
"statusArr"
# type
<Eth::Abi::Type:0x000062dd59c2e4f0 @base_type="tuple", @dimensions=[0], @name=nil, @sub_type="">
# data
{"components"=>
  [{"internalType"=>"uint256", "name"=>"reserveId", "type"=>"uint256"},
   {"internalType"=>"address", "name"=>"underlyingTokenAddress", "type"=>"address"},
   {"internalType"=>"address", "name"=>"eTokenAddress", "type"=>"address"},
   {"internalType"=>"address", "name"=>"stakingAddress", "type"=>"address"},
   {"internalType"=>"uint256", "name"=>"totalLiquidity", "type"=>"uint256"},
   {"internalType"=>"uint256", "name"=>"totalBorrows", "type"=>"uint256"},
   {"internalType"=>"uint256", "name"=>"exchangeRate", "type"=>"uint256"},
   {"internalType"=>"uint256", "name"=>"borrowingRate", "type"=>"uint256"}],
 "internalType"=>"struct ILendingPool.ReserveStatus[]",
 "name"=>"statusArr",
 "type"=>"tuple[]"}

The problem is that while decoding the result, the type somewhere loses the components, thus the error. I suspect that a recent bug fix did not cover all cases. I just need to find out where it fails.

So, good news (for me) is the module works correctly, it just is not invoked correctly (in a yet unknown location).

I'm sorry if this takes a bit as this specific code was added by a contributor and I'm still getting warm with the mechanics.