3

I have two addresses, _token1 and _token2. I would like to pack them into bytes data to pass it to a function.

bytes memory params = abi.encode(_token1, _token2);

The obtained params is the concatenation of zero-padded data.

This can be decoded in a different function like this:

(address token1, address token2) = abi.decode(params, (address, address));

Since one address is 20 bytes, which is smaller than 32 bytes, we can get a short bytes by abi.encodePacked(_token1, _token2). My question is how can I decode this encodePacked data?

ywat
  • 217
  • 1
  • 7
  • Have a look at this: https://ethereum.stackexchange.com/questions/119583/when-to-use-abi-encode-abi-encodepacked-or-abi-encodewithsignature-in-solidity – vampireAb Jan 23 '23 at 10:41

2 Answers2

7

You can do it only in assembly.

This is an example using two addresses. packed is formed by 32 bytes for length + 20 bytes for first address + 20 bytes for second address. By doing mload(add(packed, ADDRESS_LENGTH)) we end up reading 12+20 bytes, where the last 20 is the first address; since the variable x is of type address, the first 12 bytes are automatically dropped. Same reasoning for the second address.

    uint256 constant ADDRESS_LENGTH = 0x14;
function testDecodePacked(address _x, address _y) external view returns(address, address) {
    bytes memory packed = abi.encodePacked(_x, _y);

    address x;
    address y;
    assembly {
        x := mload(add(packed, ADDRESS_LENGTH))
        y := mload(add(packed, mul(ADDRESS_LENGTH, 2)))
    }

    require((x == _x) && (y == _y));
    return (x, y);
}

0xSanson
  • 3,114
  • 2
  • 11
  • 23
  • 1
    Thank you! now I understand how it works. packed is a byte sequence which is encodes similar to string. the first 32 bytes are almost zero except for the length information. x is loaded from the position of 20, but the first 12 bytes are discarded, so it is essentially loaded from 32 to 51. Similarly, y is loaded from 40, the first 12 bytes are discarded, so it is loaded from 52 to 71. – ywat Jan 23 '23 at 19:01
  • Perfect!!! :) Thank you! – Mila A Mar 26 '24 at 12:01
1

You can do it without using assembly.

You need to pass the encoded bytes as calldata as function argument. This is a static data type thus it can be sliced as a bytes array.

uint256 constant ADDRESS_LENGTH = 0x14;

function testDecodePacked(address addr1, address addr2) public { bytes memory packed = abi.encodePacked(addr1, addr2); (address addr1_, address addr2_) = this.decodePacked(packed); assert(addr1_ == addr1); assert(addr2_ == addr2); }

function decodePacked(bytes calldata packed) external pure returns (address addr1, address addr2) { addr1 = address(uint160(uint256(bytes32(packed[:ADDRESS_LENGTH]) >> 96))); addr2 = address(uint160(uint256(bytes32(packed[ADDRESS_LENGTH:]) >> 96))); }