0

When I encode my abi data in web3js or web3.py for a uni v2 trade I get the following:

0x
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000064c809a5
00000000000000000000000000000000000000000000000ad78ebc5ac6200000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000010693794192ce3e67394d6aa
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d
000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c

But when I use remix to encode/decode I get an extra 32 byte field added. Solidity will not decode on chain without this field.

0x
0000000000000000000000000000000000000000000000000000000000000020 // this guy
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000064c809a5
00000000000000000000000000000000000000000000000ad78ebc5ac6200000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000010693794192ce3e67394d6aa
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d
000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c

What is this extra 32 byte field 0000000000000000000000000000000000000000000000000000000000000020 and why is it only added with an encode through solidity? I'm assuming it has something to do with the array of addresses but I'm not sure what.

Thanks!


Code to reproduce ethers/web3js

const ethers = require('ethers'); // "ethers": "^5.7.2",
const Web3 = require("web3"); // "web3": "^1.8.1"
const web3 = new Web3();
let types = [ 'address[]', 'uint256', 'uint256', 'uint256', 'uint160' ]
let values = [
  [
    "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
    "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
  ],
  1690831269,
  ethers.BigNumber.from("200000000000000000000"),
  0,
  ethers.BigNumber.from("5078959830289813231285622442"),
];
web3.eth.abi.encodeParameters(types, values)

Code to reproduce solidity

pragma solidity 0.8.18;

contract Test {
    struct ExactInputSingleParams {
        address[] path;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    function encode() external pure returns (bytes memory) {
        address[] memory addresses = new address[](2);
        addresses[0] = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d;
        addresses[1] = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
        ExactInputSingleParams memory _params = ExactInputSingleParams({
            path: addresses,
            deadline: 1690831269,
            amountIn: 200000000000000000000,
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 5078959830289813231285622442
        });
        return abi.encode(_params);
    }

    function decode(bytes memory message) external pure returns(uint256) {
        ExactInputSingleParams memory _params = abi.decode(
            message,
            (ExactInputSingleParams)
        );
        return _params.deadline;
    }
}

1 Answer 1

1

"that guy" indicates where the data start, and it is hex 0x20 so data starts at byte 32. (it seems this appears only whenever there is any dynamic value)

word 1 is 0xa0, which is the offset where the array data starts, at byte 160, after the static values.
word 2 is 0x64c809a5, which is 1690831269
word 3 is 0xad78ebc5ac6200000, which is 200000000000000000000
word 4 is 0x00
word 5 is 0x10693794192ce3e67394d6aa, which is 5078959830289813231285622442
word 6 is 0x02, which is the length of the dynamic array
And the last 2 words are the values of the array.

ethers/web3js do not implement the first pointer because its not really neccesary. I don´t know why Solidity implemented it, but its not a problem as long as you use the native functions to encode and decode.

5
  • Do you have any documentation you can include to support this theory? I don't doubt that it is correct, but I could not find any information about it.
    – Nat
    Aug 1 at 12:25
  • Also can you comment why this only happens in structs that have arrays in them. I have another struct I have used that doesn't have arrays and doesn't include this type.
    – Nat
    Aug 1 at 12:26
  • I would like to say the ABI specification, but unfortunately it is not stated in which cases that first word is added: docs.soliditylang.org/en/v0.8.21/abi-spec.html In other articles it is indicated: coinsbench.com/solidity-tutorial-all-about-abi-46da8b517e7 In my opinion, that word is added whenever there is any dynamic value amongst the encoded data. If the dynamic array is removed, the word is not generated. As said, I don't think it's a problem as long as you use the native functions for encoding and decoding. It would only be a problem if you try to do it manually.
    – ceseshi
    Aug 1 at 16:55
  • Edited the answer to indicate that the initial word 0xa0 is the offset to the array data, and not the size of the static data, although it matches, but it is not the same
    – ceseshi
    Aug 1 at 17:02
  • I have the same question and don't think it points to anything. In the post, the 1st field happens to be an array, in my experiments, this 0x00...20 line appears as long as there is dynamic type in the array, regardless of at what byte location the dynamic type is. For example, if you shift the array to 4th field, the result of 1st line is still 0x00..20. Sep 6 at 20:31

Your Answer

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

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