Search code examples
ethereumsmartcontractsrpcabi

How to decode an ETH contract output hex as string?


When I make an eth_call to the Usdt smart contract on Eth MainNet, I get a 96-byte hex output.

0000000000000000000000000000000000000000000000000000000000000020 // What is this?
000000000000000000000000000000000000000000000000000000000000000a // Size of the output
5465746865722055534400000000000000000000000000000000000000000000 // Output ("Tether USD")

I understand that the 3rd 32 bytes contain the actual string output with right padding, and the 2nd 32 bytes contain the output size in bytes with left padding. What do the 1st 32 bytes contain?

Rpc Call

{"jsonrpc":"2.0","method":"eth_call","params":[{"To":"0xdAC17F958D2ee523a2206206994597C13D831ec7","Data":"0x06fdde03"},"latest"],"id":1}

Solution

  • The first 32byte slot is an offset that points to the length slot, which is immediately followed by slot(s) containing the actual param value.

    The offset is useful in cases when a function returns multiple dynamic-length arrays (a string is represented as a dynamic-length byte array), like in this example:

    pragma solidity ^0.8;
    
    contract MyContract {
        function foo() external pure returns (string memory, string memory) {
            return ("Tether USD", "Ethereum");
        }
    }
    

    Returned data:

    # offset pointing to the length of the 1st param
    0x0000000000000000000000000000000000000000000000000000000000000040
    
    # offset pointing to the length of the 2nd param
    0x0000000000000000000000000000000000000000000000000000000000000080
    
    # 1st param length
    0x000000000000000000000000000000000000000000000000000000000000000a
    
    # followed by 1st param value
    0x5465746865722055534400000000000000000000000000000000000000000000
    
    # 2nd param length
    0x0000000000000000000000000000000000000000000000000000000000000008
    
    # followed by 2nd param value
    0x457468657265756d000000000000000000000000000000000000000000000000
    

    If you had a fixed-length param between those two, the returned data structure would look like this:

    1. offset to the length of the 1st param
    2. the (fixed length) 2nd param actual value
    3. offset to the length of the 3rd param
    4. the rest is the same as above

    Docs: https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types