I am new to solidity. I have a doubt. How and when to use these different abi encoding techniques that are there in solidity and what's the difference between them. For example, should we abi.encode
or abi.encodePacked
the input for keccak256 hashing function.
2 Answers
This depends what you require and what you want to ensure.
abi.encode
will apply ABI encoding rules. Therefore all elementary types are padded to 32 bytes and dynamic arrays include their length. Therefore it is possible to also decode this data again (with abi.decode
) when the type are known.
abi.encodePacked
will only use the minimal required memory to encode the data. E.g. an address will only use 20 bytes and for dynamic arrays only the elements will be stored without length. For more info see the Solidity docs for packed mode.
For the input of the keccak
method it is important that you can ensure that the resulting bytes of the encoding are unique. So if you always encode the same types and arrays always have the same length then there is no problem. But if you switch the parameters that you encode or encode multiple dynamic arrays you might have conflicts.
For example:
abi.encodePacked(address(0x0000000000000000000000000000000000000001), uint(0))
encodes to the same as abi.encodePacked(uint(0x0000000000000000000000000000000000000001000000000000000000000000), address(0))
and
abi.encodePacked(uint[](1,2), uint[](3))
encodes to the same as abi.encodePacked(uint[](1), uint[](2,3))
Therefore these examples will also generate the same hashes even so they are different inputs.
On the other hand you require less memory and therefore in most cases abi.encodePacked
uses less gas than abi.encode
.
EDIT: abi.encodePacked
should stop to be used since there are conversions around to deprecate it in future versions of Solidity, see: https://github.com/ethereum/solidity/issues/11593
Let's declare a few variables:
➜ uint16 a = 2344
➜ uint16 b = 359
➜ address _addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
2344 = 0x928 and 359 = 0x167
When using abi.encode:
➜ abi.encode(a, b, _addr)
Type: dynamic bytes
├ Hex (Memory):
├─ Length ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000060
├─ Contents ([0x20:..]): 0x000000000000000000000000000000000000000000000000000000000000092800000000000000000000000000000000000000000000000000000000000001670000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
├ Hex (Tuple Encoded):
├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
├─ Length ([0x20:0x40]): 0x0000000000000000000000000000000000000000000000000000000000000060
└─ Contents ([0x40:..]): 0x000000000000000000000000000000000000000000000000000000000000092800000000000000000000000000000000000000000000000000000000000001670000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
- Splitting the single line output:
0x0000000000000000000000000000000000000000000000000000000000000928 0x0000000000000000000000000000000000000000000000000000000000000167 0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
Here we can see that it follows the abi encoding specifications.
Now what if I have a scenario where I want to concatenate them or in the sense just pack all of them into one, why waste the extra space ?
When using abi.encodepacked()
➜ abi.encodePacked(a, b, _addr)
Type: dynamic bytes
├ Hex (Memory):
├─ Length ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000018
├─ Contents ([0x20:..]): 0x092801675b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000
├ Hex (Tuple Encoded):
├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
├─ Length ([0x20:0x40]): 0x0000000000000000000000000000000000000000000000000000000000000018
└─ Contents ([0x40:..]): 0x092801675b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000
Output: 0x092801675b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000
Here we can see that all the variables has been packed together, both the uints and the address.
- Hashing using abi.encodePacked() is not recommended because hash collisions can occur since the values are concatenated, Here's an example.
Using "AAA","BBB"
➜ abi.encodePacked("AAA","BBB")
Type: dynamic bytes
├ Hex (Memory):
├─ Length ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000006
├─ Contents ([0x20:..]): 0x4141414242420000000000000000000000000000000000000000000000000000
├ Hex (Tuple Encoded):
├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
├─ Length ([0x20:0x40]): 0x0000000000000000000000000000000000000000000000000000000000000006
└─ Contents ([0x40:..]): 0x4141414242420000000000000000000000000000000000000000000000000000
Output: 0x4141414242420000000000000000000000000000000000000000000000000000
Now let's encode it with "AA", "ABBB"
➜ abi.encodePacked("AA","ABBB")
Type: dynamic bytes
├ Hex (Memory):
├─ Length ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000006
├─ Contents ([0x20:..]): 0x4141414242420000000000000000000000000000000000000000000000000000
├ Hex (Tuple Encoded):
├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
├─ Length ([0x20:0x40]): 0x0000000000000000000000000000000000000000000000000000000000000006
└─ Contents ([0x40:..]): 0x4141414242420000000000000000000000000000000000000000000000000000
Output: 0x4141414242420000000000000000000000000000000000000000000000000000
It also produces the same output because it's being concatenated and packed.