Skip to content

Commit eb654d8

Browse files
authored
Add EIP: Minimal Proxy Contract with PUSH0
Merged by EIP-Bot.
1 parent a81438b commit eb654d8

1 file changed

Lines changed: 209 additions & 0 deletions

File tree

EIPS/eip-7511.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
---
2+
eip: 7511
3+
title: Minimal Proxy Contract with PUSH0
4+
description: Optimizes the previous Minimal Proxy Contract with the PUSH0 opcode
5+
author: 0xAA (@AmazingAng), vectorized (@Vectorized), 0age (@0age)
6+
discussions-to: https://ethereum-magicians.org/t/erc-7511-minimal-proxy-contract-with-push0/15662
7+
status: Draft
8+
type: Standards Track
9+
category: ERC
10+
created: 2023-09-04
11+
requires: 7, 211, 1167, 3855
12+
---
13+
14+
## Abstract
15+
16+
With the `PUSH0` opcode ([EIP-3855](./eip-3855.md)), introduced with the Shanghai upgrade, we optimized the previous Minimal Proxy Contract ([ERC-1167](./eip-1167.md)) by 200 gas at deployment and 5 gas at runtime, while retaining the same functionality.
17+
18+
## Motivation
19+
20+
21+
1. Reduce the contract bytecode size by `1` byte by removing a redundant `SWAP` opcode.
22+
2. Reduce the runtime gas by replacing two `DUP` (cost `3` gas each) with two `PUSH0` (cost `2` gas each).
23+
3. Increase the readability of the proxy contract by redesigning it from first principles with `PUSH0`.
24+
25+
## Specification
26+
27+
### Standard Proxy Contract
28+
29+
The exact runtime code for the minimal proxy contract with `PUSH0` is:
30+
31+
```
32+
365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3
33+
```
34+
35+
where the bytes at indices 9 - 28 (inclusive) are replaced with the 20-byte address of the master implementation contract. The length of the runtime code is `44` bytes.
36+
37+
The disassembly of the new minimal proxy contract code is:
38+
39+
| pc | op | opcode | stack |
40+
|------|--------|----------------|--------------------|
41+
| [00] | 36 | CALLDATASIZE | cds |
42+
| [01] | 5f | PUSH0 | 0 cds |
43+
| [02] | 5f | PUSH0 | 0 0 cds |
44+
| [03] | 37 | CALLDATACOPY | |
45+
| [04] | 5f | PUSH0 | 0 |
46+
| [05] | 5f | PUSH0 | 0 0 |
47+
| [06] | 36 | CALLDATASIZE | cds 0 0 |
48+
| [07] | 5f | PUSH0 | 0 cds 0 0 |
49+
| [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 |
50+
| [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0|
51+
| [1e] | f4 | DELEGATECALL | suc |
52+
| [1f] | 3d | RETURNDATASIZE | rds suc |
53+
| [20] | 5f | PUSH0 | 0 rds suc |
54+
| [21] | 5f | PUSH0 | 0 0 rds suc |
55+
| [22] | 3e | RETURNDATACOPY | suc |
56+
| [23] | 5f | PUSH0 | 0 suc |
57+
| [24] | 3d | RETURNDATASIZE | rds 0 suc |
58+
| [25] | 91 | SWAP2 | suc 0 rds |
59+
| [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds |
60+
| [27] | 57 | JUMPI | 0 rds |
61+
| [29] | fd | REVERT | |
62+
| [2a] | 5b | JUMPDEST | 0 rds |
63+
| [2b] | f3 | RETURN | |
64+
65+
### Minimal Creation Code
66+
67+
The minimal creation code of the minimal proxy contract is:
68+
69+
```
70+
602c8060095f395ff3365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3
71+
```
72+
73+
where the first 9 bytes are the initcode:
74+
75+
```
76+
602c8060095f395ff3
77+
```
78+
79+
And the rest are runtime/contract code of the proxy. The length of the creation code is `53` bytes.
80+
81+
### Deploy with Solidity
82+
83+
The minimal proxy contract can be deployed with Solidity using the following contract:
84+
85+
```solidity
86+
// SPDX-License-Identifier: CC0-1.0
87+
pragma solidity ^0.8.20;
88+
89+
// Note: this contract requires `PUSH0`, which is available in solidity > 0.8.20 and EVM version > Shanghai
90+
contract Clone0Factory {
91+
error FailedCreateClone();
92+
93+
receive() external payable {}
94+
95+
/**
96+
* @dev Deploys and returns the address of a clone0 (Minimal Proxy Contract with `PUSH0`) that mimics the behaviour of `implementation`.
97+
*
98+
* This function uses the create opcode, which should never revert.
99+
*/
100+
function clone0(address impl) public payable returns (address addr) {
101+
// first 18 bytes of the creation code
102+
bytes memory data1 = hex"602c8060095f395ff3365f5f375f5f365f73";
103+
// last 15 bytes of the creation code
104+
bytes memory data2 = hex"5af43d5f5f3e5f3d91602a57fd5bf3";
105+
// complete the creation code of Clone0
106+
bytes memory _code = abi.encodePacked(data1, impl, data2);
107+
108+
// deploy with create op
109+
assembly {
110+
// create(v, p, n)
111+
addr := create(callvalue(), add(_code, 0x20), mload(_code))
112+
}
113+
114+
if (addr == address(0)) {
115+
revert FailedCreateClone();
116+
}
117+
}
118+
}
119+
```
120+
121+
## Rationale
122+
123+
The optimized contract is constructed with essential components of the proxy contract and incorporates the recently added `PUSH0` opcode. The core elements of the minimal proxy include:
124+
125+
1. Copy the calldata with `CALLDATACOPY`.
126+
2. Forward the calldata to the implementation contract using `DELEGATECALL`.
127+
3. Copy the returned data from the `DELEGATECALL`.
128+
4. Return the results or revert the transaction based on whether the `DELEGATECALL` is successful.
129+
130+
### Step 1: Copy the Calldata
131+
132+
To copy the calldata, we need to provide the arguments for the `CALLDATACOPY` opcodes, which are `[0, 0, cds]`, where `cds` represents calldata size.
133+
134+
| pc | op | opcode | stack |
135+
|------|--------|----------------|--------------------|
136+
| [00] | 36 | CALLDATASIZE | cds |
137+
| [01] | 5f | PUSH0 | 0 cds |
138+
| [02] | 5f | PUSH0 | 0 0 cds |
139+
| [03] | 37 | CALLDATACOPY | |
140+
141+
### Step 2: Delegatecall
142+
143+
To forward the calldata to the delegate call, we need to prepare arguments for the `DELEGATECALL` opcodes, which are `[gas 0xbebe. 0 cds 0 0]`, where `gas` represents the remaining gas, `0xbebe.` represents the address of the implementation contract, and `suc` represents whether the delegatecall is successful.
144+
145+
| pc | op | opcode | stack |
146+
|------|--------|----------------|--------------------|
147+
| [04] | 5f | PUSH0 | 0 |
148+
| [05] | 5f | PUSH0 | 0 0 |
149+
| [06] | 36 | CALLDATASIZE | cds 0 0 |
150+
| [07] | 5f | PUSH0 | 0 cds 0 0 |
151+
| [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 |
152+
| [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0|
153+
| [1e] | f4 | DELEGATECALL | suc |
154+
155+
### Step 3: Copy the Returned Data from the `DELEGATECALL`
156+
157+
To copy the returndata, we need to provide the arguments for the `RETURNDATACOPY` opcodes, which are `[0, 0, red]`, where `rds` represents size of returndata from the `DELEGATECALL`.
158+
159+
| pc | op | opcode | stack |
160+
|------|--------|----------------|--------------------|
161+
| [1f] | 3d | RETURNDATASIZE | rds suc |
162+
| [20] | 5f | PUSH0 | 0 rds suc |
163+
| [21] | 5f | PUSH0 | 0 0 rds suc |
164+
| [22] | 3e | RETURNDATACOPY | suc |
165+
166+
### Step 4: Return or Revert
167+
168+
Lastly, we need to return the data or revert the transaction based on whether the `DELEGATECALL` is successful. There is no `if/else` in opcodes, so we need to use `JUMPI` and `JUMPDEST` instead. The arguments for `JUMPI` is `[0x2a, suc]`, where `0x2a` is the destination of the conditional jump.
169+
170+
We also need to prepare the argument `[0, rds]` for `REVERT` and `RETURN` opcodes before the `JUMPI`, otherwise we have to prepare them twice. We cannot avoid the `SWAP` operation, because we can only get `rds` after the `DELEGATECALL`.
171+
172+
| pc | op | opcode | stack |
173+
|------|--------|----------------|--------------------|
174+
| [23] | 5f | PUSH0 | 0 suc |
175+
| [24] | 3d | RETURNDATASIZE | rds 0 suc |
176+
| [25] | 91 | SWAP2 | suc 0 rds |
177+
| [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds |
178+
| [27] | 57 | JUMPI | 0 rds |
179+
| [29] | fd | REVERT | |
180+
| [2a] | 5b | JUMPDEST | 0 rds |
181+
| [2b] | f3 | RETURN | |
182+
183+
In the end, we arrived at the runtime code for Minimal Proxy Contract with `PUSH0`:
184+
185+
```
186+
365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3
187+
```
188+
189+
The length of the runtime code is `44` bytes, which reduced `1` byte from the previous Minimal Proxy Contract. Moreover, it replaced the `RETURNDATASIZE` and `DUP` operations with `PUSH0`, which saves gas and increases the readability of the code. In summary, the new Minimal Proxy Contract reduces `200` gas at deployment and `5` gas at runtime, while remaining the same functionalities as the old one.
190+
191+
## Backwards Compatibility
192+
193+
Because the new minimal proxy contract uses `PUSH0` opcode, it can only be deployed after the Shanghai Upgrade. It behaves the same as the previous Minimal Proxy Contract.
194+
195+
## Security Considerations
196+
197+
The new proxy contract standard is identical to the previous one (ERC-1167). Here are the security considerations when using minimal proxy contracts:
198+
199+
1. **Non-Upgradability**: Minimal Proxy Contracts delegate their logic to another contract (often termed the "implementation" or "logic" contract). This delegation is fixed upon deployment, meaning you can't change which implementation contract the proxy delegates to after its creation.
200+
201+
2. **Initialization Concerns**: Proxy contracts lack constructors, so you need to use an initialization function after deployment. Skipping this step could leave the contract unsafe.
202+
203+
3. **Safety of Logic Contract**: Vulnerabilities in the logic contract affect all associated proxy contracts.
204+
205+
4. **Transparency Issues**: Because of its complexity, users might see the proxy as an empty contract, making it challenging to trace back to the actual logic contract.
206+
207+
## Copyright
208+
209+
Copyright and related rights waived via [CC0](../LICENSE.md).

0 commit comments

Comments
 (0)