ERC-1167 Minimal proxy contract

Final · Other · created 2018-06-22

A standard for cheaply cloning a contract with the same functionality using very small bytecode. A clone forwards every call to a fixed original contract and returns the result unchanged.

Why it is needed

When many contracts with the same functionality are needed, deploying the full code each time raises deployment cost accordingly. If everyone builds their own way of forwarding calls to another address, tools such as wallets and block explorers cannot tell what code actually runs, even when they look at that address's bytecode. This makes it hard to verify externally that a clone really behaves like the original. This standard fixes the bytecode to a single form so that anyone can read a clone's code and immediately find the original address. As a result, the original's verified source or external audit results can serve as evidence for the clone's behavior. The clone forwards all call data and all gas to the original and passes back the return value as well as the failure message on revert. The user receives the same result as calling the original directly.

In depth

Standard bytecode

The deployed bytecode of the standard clone contract is 363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3. Bytes 10 through 29 of it (inclusive, 20 bytes in total) are replaced with the address of the original logic contract. The positions filled with bebe in the example are that location. Because the bytecode is fixed to one form, a tool can compare the code at an address against this pattern and then read the 20 bytes at that location to find the original address. The reference implementation is in the optionality/clone-factory repository.

Execution flow breakdown

The deployed code runs in four steps. First, calldatasize and calldatacopy copy the entire incoming call data into memory starting at offset 0. Next, push20 pushes the original address, gas adds all remaining gas, and delegatecall is executed. When the call ends, returndatasize and returndatacopy copy the return data into memory. Finally, it checks whether the delegatecall succeeded and branches with push1 0x2b and jumpi. On success it jumps to the jumpdest at 0x2b and returns, and on failure it executes the revert that follows immediately. In both cases the return data is passed through unchanged, so if the original reverts with a message, that message reaches the caller.

Reliance on EIP-211 and gas savings

This bytecode relies on the EIP-211 rule that returndatasize returns 0 within a call frame before any call has been made. So wherever a 0 must be pushed onto the stack, it uses returndatasize (0x3d) instead of push or dup. The source explains this as a choice to reduce gas as much as possible, and states that returndatasize uses 1 less gas than the dup family. Because of this dependency, EIP-211 is listed in the requires field of the header.

Design goals

The source states six goals. Clone deployment gas must be cheap. It must support initializing the clone within the creation transaction through the factory contract model. The bytecode must be simple so that tools can read it directly and make judgments (see CloneProbe.sol in clone-factory). The behavior must be fixed and unchangeable. This standard was not made to handle upgrades, and it was left that way deliberately for stronger guarantees. The runtime overhead must be small, adding only the cost of one call per invocation. And revert messages must bubble back up to the caller.

Vanity address optimization

Deploying the original contract at an address whose leading bytes are zero lets the proxy bytecode be shortened further. If the address has Z leading zero bytes, pushN (where N is 20 minus Z) is used instead of push20, and only the N nonzero bytes are included. The jump address for the revert branch is also reduced by Z. The source example is the case Z = 4, where push16 pushes a 16-byte address and the jump address changes from 0x2b to 0x27. This saves 4 bytes of bytecode per clone and has no effect on gas at execution time.

Deployment code, tests, compatibility

The deployment bytecode that creates clones is not included in this specification. One method is defined in the reference implementation. The test cases are five: calls without arguments, calls with arguments, fixed-length return values, variable-length return values, and checking revert propagation, and they are included in the reference implementation project. There are no compatibility issues, but systems that use an earlier version of the proxy bytecode do not conform to this standard. The source status is Final.

Original ERC · ← ERCs