6

I created a Uniswap V3 pool using UI. In Transaction multicall(bytes[] data) function was called with this parameter:

[0]:  0000000000000000000000000000000000000000000000000000000000000020
[1]:  0000000000000000000000000000000000000000000000000000000000000002
[2]:  0000000000000000000000000000000000000000000000000000000000000040
[3]:  0000000000000000000000000000000000000000000000000000000000000100
[4]:  0000000000000000000000000000000000000000000000000000000000000084
[5]:  13ead5620000000000000000000000005c8cd1c2f2997f7a041026cc29de8177
[6]:  b4c6d8ec00000000000000000000000089e54f174ca5ff39cf53ab58004158e2
[7]:  ca012eac00000000000000000000000000000000000000000000000000000000
[8]:  00000bb8000000000000000000000000000000000035f2482336c0d4c2ba6e94
[9]:  faa1d66f00000000000000000000000000000000000000000000000000000000
[10]: 0000000000000000000000000000000000000000000000000000000000000164
[11]: 883164560000000000000000000000005c8cd1c2f2997f7a041026cc29de8177
[12]: b4c6d8ec00000000000000000000000089e54f174ca5ff39cf53ab58004158e2
[13]: ca012eac00000000000000000000000000000000000000000000000000000000
[14]: 00000bb8ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
[15]: fff2764c00000000000000000000000000000000000000000000000000000000
[16]: 000a11a8000000000000000000000000000000000000000000000000000000e8
[17]: d4a510000000000000000000000000000000000000000000000a56d35c029fd1
[18]: 6645e079000000000000000000000000000000000000000000000000000000e8
[19]: 40308c030000000000000000000000000000000000000000000a503344abc0fb
[20]: e23670910000000000000000000000005a2b5cb4ce921abd65f0c66c2c839894
[21]: bfc2076c00000000000000000000000000000000000000000000000000000000
[22]: 6244356a00000000000000000000000000000000000000000000000000000000

This is how multicall function looks like:

function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i = 0; i < data.length; i++) {
        (bool success, bytes memory result) = address(this).delegatecall(data[i]);

        if (!success) {
            // Next 5 lines from https://ethereum.stackexchange.com/a/83577
            if (result.length < 68) revert();
            assembly {
                result := add(result, 0x04)
            }
            revert(abi.decode(result, (string)));
        }

        results[i] = result;
    }
}

How to understand what methods (and parameters) were called here:

address(this).delegatecall(data[i])

4 Answers 4

6

The comments for the accepted answer includes a link to an NPM library that makes an http call to get the function name - which is not a good idea. Also, I think it's much more complicated than it needs to be. The Ethers library provides most of the functionality needed.

In the case of Uniswap multicalls, the contract calls the function on the current contract, so there really is no need to try to look it up from any contract - the function will be in the contract. If you know the contract, then you can decode it using the ABI. You must know the ABI because you decoded the multicall.

const decodeMulticall = (abi: ReadonlyArray<any>, calls: string[]) => {
  const abiInterface = new ethers.utils.Interface(abi);
  return calls.map(call => {
    try {
      const func = call.slice(0, 10);
      const decodedArgs = abiInterface.decodeFunctionData(func, call)
      const functionName = abiInterface.getFunction(func).name
      return {name: functionName, args: decodedArgs};
    }
    catch (ex) {
      return; // you could return a type here to indicate it was not parsed
    }
  })
1
  • I would extend this answer with the answer by @hroussille to get the calls array from the calldata you can decode it using the following: let calls = web3.eth.abi.decodeParameter("bytes[]", calldata); Then pass calls to your decodeMulticall function.
    – MShakeG
    Commented Mar 16, 2023 at 9:59
2

I wrote a bit of code to do exactly that using the 4bytes API to find the functions signatures from their function identifier and some Regex to decode the function's specific calldata, it is (very) far from being perfect but you might find it useful, here is a version on your specific calldata :

const Web3 = require("Web3");
const axios = require("axios");
const CALLDATA = "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008413ead5620000000000000000000000005c8cd1c2f2997f7a041026cc29de8177b4c6d8ec00000000000000000000000089e54f174ca5ff39cf53ab58004158e2ca012eac0000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000035f2482336c0d4c2ba6e94faa1d66f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164883164560000000000000000000000005c8cd1c2f2997f7a041026cc29de8177b4c6d8ec00000000000000000000000089e54f174ca5ff39cf53ab58004158e2ca012eac0000000000000000000000000000000000000000000000000000000000000bb8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2764c00000000000000000000000000000000000000000000000000000000000a11a8000000000000000000000000000000000000000000000000000000e8d4a510000000000000000000000000000000000000000000000a56d35c029fd16645e079000000000000000000000000000000000000000000000000000000e840308c030000000000000000000000000000000000000000000a503344abc0fbe23670910000000000000000000000005a2b5cb4ce921abd65f0c66c2c839894bfc2076c000000000000000000000000000000000000000000000000000000006244356a00000000000000000000000000000000000000000000000000000000";

const web3 = new Web3();

function extractParameters(signature) {
  let params = [];

  const allParameters = /\b[^()]+\((.*)\)$/gm;
  const splitParameters = /((\(.+?\))|([^,() ]+)){1}/gm;

  let _allParameters = allParameters.exec(signature)[1];

  while ((match = splitParameters.exec(_allParameters))) {
    params.push(match[0]);
  }

  return params;
}

async function decode(calldata) {
  let calls = web3.eth.abi.decodeParameter("bytes[]", calldata);

  for (let index = 0; index < calls.length; index++) {
    let call = calls[index].replace("0x", "");
    let selector = call.slice(0, 8);
    let data = call.slice(8);
    let request = await axios.get(
      `https://www.4byte.directory/api/v1/signatures/?hex_signature=${selector}`
    );
    let signature = request.data.results[0].text_signature;

    console.log(
      `Call ${index} : Selector : ${selector} Signature : ${signature} Data size : ${data.length}`
    );

    let parameters = extractParameters(signature);
    let decoded = web3.eth.abi.decodeParameters(parameters, data);

    for (let i = 0; i < parameters.length; i++) {
      console.log(
        `Parameter ${i} - Type : ${parameters[i]} - Value : ${decoded[i]}`
      );
    }
  }
}

decode(CALLDATA);

Which indicates that your call data is making 2 different calls :

  • mint((address,address,uint24,int24,int24,uint256,uint256,uint256,uint256,address,uint256))
  • createAndInitializePoolIfNecessary(address,address,uint24,uint160)

I made some assumptions regarding the values returned by 4bytes (only keeping the first match) which may lead to errors... I may improve it later and update the code here.

The parameters types are extracted from the signature and automatically decoded / displayed. Feel free to change the calldata given to decode for any other multicall, if the signature is present on 4bytes it should work too !

I hope it helps !

1
2

Using /abi-decoder

const axios = require("axios");
const abiDecoder = require("abi-decoder");

const INPUT_DATA =
  "0xac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000008413ead5620000000000000000000000005c8cd1c2f2997f7a041026cc29de8177b4c6d8ec00000000000000000000000089e54f174ca5ff39cf53ab58004158e2ca012eac0000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000035f2482336c0d4c2ba6e94faa1d66f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164883164560000000000000000000000005c8cd1c2f2997f7a041026cc29de8177b4c6d8ec00000000000000000000000089e54f174ca5ff39cf53ab58004158e2ca012eac0000000000000000000000000000000000000000000000000000000000000bb8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2764c00000000000000000000000000000000000000000000000000000000000a11a8000000000000000000000000000000000000000000000000000000e8d4a510000000000000000000000000000000000000000000000a56d35c029fd16645e079000000000000000000000000000000000000000000000000000000e840308c030000000000000000000000000000000000000000000a503344abc0fbe23670910000000000000000000000005a2b5cb4ce921abd65f0c66c2c839894bfc2076c000000000000000000000000000000000000000000000000000000006244356a00000000000000000000000000000000000000000000000000000000";
const contractAddressInteractedWith =
  "0xc36442b4a4522e871399cd717abdd847ab11fe88";

const API_KEY = "YOURAOIKEYHERE";

// https://api.etherscan.io/apis
// this will the pull the abi
const URL = `https://api.etherscan.io/api?module=contract&action=getabi&address=${contractAddressInteractedWith}&apikey=${API_KEY}`;

const abiFromUrl = async () => {
  const res = await axios.get(URL);
  return JSON.parse(res.data.result);
};

const decode = async () => {
  const ABI = await abiFromUrl();
  // pass the ABI to the decoder
  abiDecoder.addABI(ABI);
  const decodeMulticall = abiDecoder.decodeMethod(INPUT_DATA);
  // you have to stringify to see the inner data objects
  console.log("decodeMulticall", JSON.stringify(decodeMulticall, null, 1));
  const decodeParamsValue = abiDecoder.decodeMethod(
    decodeMulticall.params[0].value[0]
  );
  console.log("FINAL OUTPUT", decodeParamsValue);
};
decode();

first console.log will give you this:

enter image description here

from here reach to inner value property and then FINAL_OUTPUT

enter image description here

1

I faced the same issue and this is how i solved it

from web3 import Web3
import requests

#Txn Hash 0xb26a0e7bec364162027da0b3fb3977d50371a5ecac5f762db976b1a76eb0a1ef
web3 = Web3(Web3.HTTPProvider("Infura API"))

sample_input= "0x5ae401dc00000000000000000000000000000000000000000000000000000000647aa465000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e4472b43f30000000000000000000000000000000000000000066e61c22f323b208439e6460000000000000000000000000000000000000000000000000e4dfc066ae75a9b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000008888a185d4d83d4249acf0f8d262af2a5658b673000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c0000000000000000000000000000000000000000000000000e4dfc066ae75a9b0000000000000000000000003b9a95822216c1d4951a9775aa4628e2f9edc5f000000000000000000000000000000000000000000000000000000000"

v3UniRouter= web3.toChecksumAddress('0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45')

v3UniAbi='ABI INPUT'

v3UniContract = web3.eth.contract(address=v3UniRouter, abi=v3UniAbi)

decoded = v3UniContract.decode_function_input(sample_input)


print(decoded)

multicall_data = decoded[1]['data'][0]

nested_call_decoded = v3UniContract.decode_function_input(multicall_data)

print(nested_call_decoded)

enter image description here

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.