0

I’m trying to understand ABI decoding of multicall RPC calls so I can create stubbed tests using WireMock for my Rust EVM application.

The multicall function signature is:

function aggregate(Call[] calldata calls) external payable 
    returns (uint256 blockNumber, bytes[] memory returnData);

The two ERC-20 functions I’m calling inside it are:

function symbol() public view returns (string);
function decimals() public view returns (uint8);

What I Tried

I attempted to manually construct the encoded response.

For three tokens I tried this:

0x
1 -  000000000000000000000000000000000000000000000000000000000378cc49 // block number
2 -  0000000000000000000000000000000000000000000000000000000000000040 // Offset to start of array
3 -  0000000000000000000000000000000000000000000000000000000000000006 // Length of array
4 -  0000000000000000000000000000000000000000000000000000000000000080 // Offset to string 1
5 -  00000000000000000000000000000000000000000000000000000000000000e0 // Offset to string 2
6 -  0000000000000000000000000000000000000000000000000000000000000140 // Offset to string 3
7 -  0000000000000000000000000000000000000000000000000000000000000012 // Decimals for coin 1
8 -  0000000000000000000000000000000000000000000000000000000000000004 // Length of string 1
9 -  57424e4200000000000000000000000000000000000000000000000000000000 // String contents
10 - 0000000000000000000000000000000000000000000000000000000000000012 // Decimals for coin 
11 - 0000000000000000000000000000000000000000000000000000000000000004 // Length of string
12 - 43616b6500000000000000000000000000000000000000000000000000000000 // String contents
13 - 0000000000000000000000000000000000000000000000000000000000000012 // Decimals for coin 
14 - 0000000000000000000000000000000000000000000000000000000000000004 // Length of string
15 - 5553444300000000000000000000000000000000000000000000000000000000 // String contents

Then this failed so I tried to do this for a single return:


"0x
000000000000000000000000000000000000000000000000000000000378cc49 // Block number
0000000000000000000000000000000000000000000000000000000000000040 // Offset to array
0000000000000000000000000000000000000000000000000000000000000002 // Length of array
0000000000000000000000000000000000000000000000000000000000000040 // Offset to string
0000000000000000000000000000000000000000000000000000000000000012 // Decimals
0000000000000000000000000000000000000000000000000000000000000004 // String Length
57424e4200000000000000000000000000000000000000000000000000000000" // String contents

But when I run these through WireMock and hit them from Rust, I get:

type check failed for "offset (usize)" with data: 0000000000000000000000000040000000000000000000000000000000000000

What Actually Happens

When I make a real multicall, the response looks like this:

aggregateReturn { blockNumber: 58256789, returnData: [ "0x0000000000000000000000000000000000000000000000000000000000000012", "0x0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000004 57424e4200000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000012", "0x0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000004 43616b6500000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000012", "0x0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000004 5553444300000000000000000000000000000000000000000000000000000000" ] }

My Question

How should I correctly construct a stubbed multicall response for testing (e.g. for 3 tokens with decimals = 18 and symbols "BNB", "Cake", "USDC")?

Thanks in advance

Edit: I tried to follow this as a tutorial so really can't see what I am missing:

ABI examples

1 Answer 1

0

import { encodeAbiParameters, decodeAbiParameters, parseAbiParameters } from 'viem'

const blockNumber = 58256789n
const returnData = [
  encodeAbiParameters(parseAbiParameters('string'), ['BNB']),
  encodeAbiParameters(parseAbiParameters('uint8'), [18]),
  encodeAbiParameters(parseAbiParameters('string'), ['Cake']),
  encodeAbiParameters(parseAbiParameters('uint8'), [18]),
  encodeAbiParameters(parseAbiParameters('string'), ['USDC']),
  encodeAbiParameters(parseAbiParameters('uint8'), [18])
]

const encoded = encodeAbiParameters(
  parseAbiParameters('uint256, bytes[]'), 
  [blockNumber, returnData]
)

console.log('Encoded for WireMock:', encoded)

const [decodedBlock, decodedData] = decodeAbiParameters(
  parseAbiParameters('uint256, bytes[]'),
  encoded
)

console.log('Block:', decodedBlock)

// Decode each return value
decodedData.forEach((data, i) => {
  if (i % 2 === 0) {
    const [symbol] = decodeAbiParameters(parseAbiParameters('string'), data)
    console.log(`Token ${i/2 + 1} symbol:`, symbol)
  } else {
    const [decimals] = decodeAbiParameters(parseAbiParameters('uint8'), data)
    console.log(`Token ${Math.floor(i/2) + 1} decimals:`, decimals)
  }
})

You can try this script with viem library:

https://viem.sh/docs/getting-started

Sign up to request clarification or add additional context in comments.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

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.