ABI Encoding & Decoding

The Contract ABI defines how function calls, return values, events and errors are encoded as bytes.

23.1 The ABI

The Contract ABI defines how function calls, return values, events and errors are encoded as bytes. Static types are encoded in 32-byte words; dynamic types (bytes, string, T[]) are encoded as an offset in the head pointing to a length + data tail.

transfer(address to, uint256 amount) with to = 0xAb...Cd, amount = 100
0xa9059cbb                                                        ← selector
000000000000000000000000ab...cd                                   ← address, left-padded to 32 bytes
0000000000000000000000000000000000000000000000000000000000000064  ← 100

23.2 Encoding functions

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20Like {
    function transfer(address to, uint256 amount) external returns (bool);
}

contract Encoding {
    function encodings(address to, uint256 amount)
        external pure
        returns (bytes memory a, bytes memory b, bytes memory c, bytes memory d, bytes memory e)
    {
        a = abi.encode(to, amount);                                         // 64 bytes, padded
        b = abi.encodePacked(to, amount);                                   // 20 + 32 = 52 bytes
        c = abi.encodeWithSignature("transfer(address,uint256)", to, amount); // typo-prone
        d = abi.encodeWithSelector(IERC20Like.transfer.selector, to, amount);
        e = abi.encodeCall(IERC20Like.transfer, (to, amount));              // type-checked: preferred
    }

    function decode(bytes calldata data) external pure returns (address to, uint256 amount) {
        (to, amount) = abi.decode(data, (address, uint256));
    }

    function decodeCall(bytes calldata callData) external pure returns (bytes4 sel, address to, uint256 amt) {
        sel = bytes4(callData[:4]);
        (to, amt) = abi.decode(callData[4:], (address, uint256
…

23.3 The encodePacked collision pitfall

Never use abi.encodePacked with two or more dynamic types when hashing for signatures, IDs or commitments — use abi.encode.

// "a" + "bc" and "ab" + "c" produce the same packed bytes → same hash!
keccak256(abi.encodePacked("a", "bc")) == keccak256(abi.encodePacked("ab", "c"));   // true

23.4 Function selectors and signatures

transfer(address,uint256) (no spaces, no names, uint256 not uint)

bytes4(keccak256("transfer(address,uint256)")) = 0xa9059cbb encoded as (type1,type2) in signatures full 32-byte keccak256("Transfer(address,address,uint256)")

Tools: cast sig, cast calldata, cast abi-decode, cast 4byte-decode, and signature databases (4byte.directory, openchain.xyz).