I am developing a low-level library that interacts with RPC nodes directly. There is a requirement to call the AaveV2 getAllReserveTokens function, and I want to decode the output data.

The particular function that I am calling is this: https://etherscan.io/address/0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d#readContract#F3

When I do an eth_call, the output is a long output, I have pasted it here: https://paste.ee/p/TJ5cr

As you can see on etherscan, the output type is a tuple[], and the abi specifies that the tuple contains a string and address.

I understood that the first 32 bytes is the offset, from where the dynamic array starts, so 0000000000000000000000000000000000000000000000000000000000000020 = 32.

The second 32 bytes is the length of the array, and hence, the array is of length 0000000000000000000000000000000000000000000000000000000000000025 = 37.

This all tracks with the output of the etherscan function call.

However, I cannot figure out how to decode the rest of the data. Please can someone help me with this?

2 Answers 2


After re-reading https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types with a fresher brain, I understood that the way the bytes are divided is as follows:

first 32 bytes: offset for the list of tuples

next 32 bytes: length of the list (37)

next 37*32 bytes: each defining the start position for the respective tuples

each tuple (starting at the start position from above) has the offset for the start of the string (which always corresponds to the position after the address), and then the address, followed by the length of the string and then the string.

This has really helped me understand how complex data types are encoded in RLP.


If you look at the source code of the contract, the return type of the function getAllReserveTokens is TokenData[] memory which is an array of a custom struct defined in the storage of the contract. The bytes you are receiving as a response from the contract is the RLP encoded bytes of the data. All data is stored in this form on the EVM.

Not sure what framework you are working in, but the same methodology is easily reproducible in most common languages. Simply define a local instance of the struct, and decode the bytes received from the call with the TokenData[] type as the second parameter. Here's a little foundry snippet.

    contract AaveTest is DSTest {
        CheatCodes cheatCodes;
        struct TokenData {
            string symbol;
            address tokenAddress;
        function setUp() public {
            cheatCodes = CheatCodes(HEVM_ADDRESS);
        function testEncodeAaveReturn() public {
            (bool success, bytes memory data) = AAVE_PROTOCOL_DATA_PROVIDER.call(
            TokenData[] memory tokens = abi.decode(data, (TokenData[]));
            for (uint256 i = 0; i < tokens.length; i++) {
            bytes memory encoded = abi.encode(tokens);
            assertEq(string(encoded), string(data));

In Typescript for example you could use ethersjs, ethers-rs (rust) go-ethereum (go) ..etc:

    const iface = new ethers.utils.Interface(AAVE_DATA_PROVIDER_ABI)
    interface TokenData = { symbol:string, tokenAddress:string; }
    //Convert the byte array to a string if its not already
    function decode(data:string) public {
        const tokenData:TokenData = iface.decodeFunctionResult("getAllReservesTokens()", data);
  • Thank you for you answer, I appreciate it. I am trying to develop my own library in java so I'm trying to not use libraries like web3j. Is it possible, knowing that the return type is a list of structs containing a string and an address, to parse the output chunk by chunk?
    – pestopasta
    Commented Dec 7, 2022 at 10:11

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.