What are ABI encoding functions in Solidity 0.4.24?

Solidity 0.4.24 added a bunch of ABI encoding functions. Copied from Solidity docs, they are…

  • abi.encode(...) returns (bytes): ABI-encodes the given arguments
  • abi.encodePacked(...) returns (bytes): Performes packed encoding of the given arguments
  • abi.encodeWithSelector(bytes4 selector, ...) returns (bytes): ABI-encodes the given arguments starting from the second and prepends the given four-byte selector
  • abi.encodeWithSignature(string signature, ...) returns (bytes): Equivalent to abi.encodeWithSelector(bytes4(keccak256(signature), ...)

Really the interesting functions here are encode and encodePacked, since the other 2 kind of just calls encode

Those functions are useful if you call functions dynamically.

abi.encode

Starting with a simple example. If you just encode a single string like,

abi.encode("AAAA");

You get the following output (96 bytes, 3 words)

0x0000000000000000000000000000000000000000000000000000000000000020
0x0000000000000000000000000000000000000000000000000000000000000004
0x4141414100000000000000000000000000000000000000000000000000000000
  • The 1st word is 0x20 (32 in decimal) padded to 32 bytes. This indicates the starting offset of the first and only parameter.
  • The 2nd word is 0x04 padded to 32 bytes. This indicates the length of our string.
  • Finally the 3rd word is our data, “AAAA”, UTF-8 encoded, padded to 32 bytes.

Now let’s try encoding two strings,

abi.encode("AAAA", "BBBB");

It produces the following output (192 bytes, 6 words)

0x0000000000000000000000000000000000000000000000000000000000000040
0x0000000000000000000000000000000000000000000000000000000000000080
0x0000000000000000000000000000000000000000000000000000000000000004
0x4141414100000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000004
0x4242424200000000000000000000000000000000000000000000000000000000
  • The 1st word is 0x40 (64 in decimal), indicating the starting offset of the first parameter.
  • The 2nd word is 0x80 (128 in decimal), indicating the starting offset of the second parameter.
  • The 3rd word is 0x04, which is the length of the first string.
  • The 4th word is “AAAA”
  • The 5th word is 0x04, which is the length of the second string.
  • The last word is “BBBB”

You can even go fancier and encode some arrays.

uint8[3] memory arr = [0x1, 0x2, 0x42];
abi.encode(arr, "AAAA", "BBBB");

The result is 9 words.

0x0000000000000000000000000000000000000000000000000000000000000001
0x0000000000000000000000000000000000000000000000000000000000000002
0x0000000000000000000000000000000000000000000000000000000000000042
0x00000000000000000000000000000000000000000000000000000000000000a0
0x00000000000000000000000000000000000000000000000000000000000000e0
0x0000000000000000000000000000000000000000000000000000000000000004
0x4141414100000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000004
0x4242424200000000000000000000000000000000000000000000000000000000
  • Because the array is fixed-length, The 3 elements in the array are simply laid out in the first 3 words.
  • The 4th word is the offset for “AAAA” (160 in decimal, meaning the start of the data is at the 6th word)
  • The 5th word is the offset for “BBBB” (224 in decimal, meaning the start of the data is at the 8th word)
  • The 6th word is the length of “AAAA”
  • The 7th word is “AAAA”
  • The 8th word is the length of “BBBB”
  • The 9th word is “BBBB”

abi.encodePacked

Encode packed is simpler (although non-standard) than encode. Dynamic types are encoded in-place without length. Static types will not be padded if they are shorter than 32 bytes.

Packed encode a single string

abi.encodePacked("AAAA");

Simply produces 0x41414141, which is exactly the same as just simply converting it to bytes.

Encoding some uint with string

abi.encodePacked(uint8(0x42), uint256(0x1337), "AAAA", "BBBB");

The output is

0x42
0x0000000000000000000000000000000000000000000000000000000000001337
0x41414141
0x42424242

Interestingly, arrays don’t necessarily have their elements packed.

uint8[3] memory arr = [0x1, 0x2, 0x42];
abi.encodePacked(arr, "AAAA", "BBBB");

The output is

0x0000000000000000000000000000000000000000000000000000000000000001
0x0000000000000000000000000000000000000000000000000000000000000002
0x0000000000000000000000000000000000000000000000000000000000000042
0x41414141
0x42424242

So the uint8 array elements are still padded to 32-byte words.

keccak256 encoding behavior

Before ABI encoding functions were introduced, keccak function accepts multiple arguments like

keccak256("AAAA", "BBBB", 42);

It has been implicitly doing encodePacked. But now if you try calling keccak with those, you are likely to get a compiler warning.

This function only accepts a single "bytes" argument

So it is no longer recommended to let compiler work its magic and implicitly encode the parameters to bytes. It gives the programmer more control of how the data should be encoded, packed or not.

If you want the same behavior as before, you can try encodePacked, or convert the data to bytes yourself. For string, it can be directly converted to bytes like bytes(myString). For anything else, explicit packing may be needed.