Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP: Minimal Proxy Contract with PUSH0 #7639

Merged
merged 21 commits into from
Sep 8, 2023
Merged
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions EIPS/eip-7511.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
eip: 7511
title: Minimal Proxy Contract with `PUSH0`
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
description: Optimize the previous Minimal Proxy Contract (ERC-1167) with the newly introduced `PUSH0` opcode
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
author: 0xAA (@AmazingAng), vectorized (@Vectorized), 0age (@0age)
discussions-to: https://ethereum-magicians.org/t/erc-7511-minimal-proxy-contract-with-push0/15662
status: Draft
type: Standards Track
category: ERC
created: 2023-09-04
requires: 7, 211, 1167, 3855
---

## Abstract

With the newly introduced `PUSH0` opcode ([EIP-3855](./eip-3855.md)) at 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 same functionalities.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Motivation

This standard tries to optimize the Minimal Proxy Contract with the newly added `PUSH0` opcodes. The main motivations are:
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

1. Reduce the contract bytecode size by `1` byte by removing a redundant `SWAP` opcode.
2. Reduce the runtime gas by replacing two `DUP` (cost `3` gas each) to two `PUSH0` (cost `2` gas each).
3. Increase the readability of the proxy contract by redesigning it from first principles with `PUSH0`.

## Specification

### Standard Proxy Contract

The exact runtime code for the minimal proxy contract with `PUSH0` is:

```
365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3
```

wherein 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.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

The disassembly of the new minimal proxy contract code:
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

```shell
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
| pc | op | opcode | stack |
|------|--------|----------------|--------------------|
| [00] | 36 | CALLDATASIZE | cds |
| [01] | 5f | PUSH0 | 0 cds |
| [02] | 5f | PUSH0 | 0 0 cds |
| [03] | 37 | CALLDATACOPY | |
| [04] | 5f | PUSH0 | 0 |
| [05] | 5f | PUSH0 | 0 0 |
| [06] | 36 | CALLDATASIZE | cds 0 0 |
| [07] | 5f | PUSH0 | 0 cds 0 0 |
| [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 |
| [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0|
| [1e] | f4 | DELEGATECALL | suc |
| [1f] | 3d | RETURNDATASIZE | rds suc |
| [20] | 5f | PUSH0 | 0 rds suc |
| [21] | 5f | PUSH0 | 0 0 rds suc |
| [22] | 3e | RETURNDATACOPY | suc |
| [23] | 5f | PUSH0 | 0 suc |
| [24] | 3d | RETURNDATASIZE | rds 0 suc |
| [25] | 91 | SWAP2 | suc 0 rds |
| [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds |
| [27] | 57 | JUMPI | 0 rds |
| [29] | fd | REVERT | |
| [2a] | 5b | JUMPDEST | 0 rds |
| [2b] | f3 | RETURN | |
```
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

### Minimal Creation Code

The minimal creation code of the minimal proxy contract is:

```
602c8060095f395ff3365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3
```

where the first 9 bytes are the initcode:

```
602c8060095f395ff3
```

And the rest are runtime/contract code of the proxy. The length of the creation code is `53` bytes.

### Deploy with Solidity

The minimal proxy contract can be deployed with Solidity using underlying contract:
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

```solidity
// SPDX-License-Identifier: MIT
AmazingAng marked this conversation as resolved.
Show resolved Hide resolved
pragma solidity ^0.8.20;

// Note: this contract requires `PUSH0`, which is available in solidity > 0.8.20 and EVM version > Shanghai
contract Clone0Factory {
error FailedCreateClone();

receive() external payable {}

/**
* @dev Deploys and returns the address of a clone0 (Minimal Proxy Contract with `PUSH0`) that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone0(address impl) public payable returns (address addr) {
// first 18 bytes of the creation code
bytes memory data1 = hex"602c8060095f395ff3365f5f375f5f365f73";
// last 15 bytes of the creation code
bytes memory data2 = hex"5af43d5f5f3e5f3d91602a57fd5bf3";
// complete the creation code of Clone0
bytes memory _code = abi.encodePacked(data1, impl, data2);

// deploy with create op
assembly {
// create(v, p, n)
addr := create(callvalue(), add(_code, 0x20), mload(_code))
}

if (addr == address(0)) {
revert FailedCreateClone();
}
}
}
```

## Rationale

The contract is built from first principals utilizing the newly introduced `PUSH0` opcode. The essential components of the minimal proxy are:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure what you're trying to say with your first sentence there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the sentence is a bit unclear, I have rephrased it to "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:"


1. Copy the calldata with `CALLDATACOPY`.
2. Forward the calldata to the implementation contract using `DELEGATECALL`.
3. Copy the returned data from the `DELEGATECALL`.
4. Return the results or reverts the transaction based on wether the `DELEGATECALL` is successful.

### Step 1: Copy the Calldata

To copy the calldata, we need to provide the arguments for the `CALLDATACOPY` opcodes, which are `[0, 0, cds]`, where `cds` represents calldata size.

```shell
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
| pc | op | opcode | stack |
|------|--------|----------------|--------------------|
| [00] | 36 | CALLDATASIZE | cds |
| [01] | 5f | PUSH0 | 0 cds |
| [02] | 5f | PUSH0 | 0 0 cds |
| [03] | 37 | CALLDATACOPY | |
```
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

### Step 2: Delegatecall

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.

```shell
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
| pc | op | opcode | stack |
|------|--------|----------------|--------------------|
| [04] | 5f | PUSH0 | 0 |
| [05] | 5f | PUSH0 | 0 0 |
| [06] | 36 | CALLDATASIZE | cds 0 0 |
| [07] | 5f | PUSH0 | 0 cds 0 0 |
| [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 |
| [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0|
| [1e] | f4 | DELEGATECALL | suc |
```
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

### Step 3: Copy the Returned Data from the `DELEGATECALL`

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`.

```shell
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
| pc | op | opcode | stack |
|------|--------|----------------|--------------------|
| [1f] | 3d | RETURNDATASIZE | rds suc |
| [20] | 5f | PUSH0 | 0 rds suc |
| [21] | 5f | PUSH0 | 0 0 rds suc |
| [22] | 3e | RETURNDATACOPY | suc |
```
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

### Step 4: Return or Revert

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 auguments for `JUMPI` is `[0x2a, suc]`, where `0x2a` is the destination of the conditional jump.

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`.

```shell
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved
| pc | op | opcode | stack |
|------|--------|----------------|--------------------|
| [23] | 5f | PUSH0 | 0 suc |
| [24] | 3d | RETURNDATASIZE | rds 0 suc |
| [25] | 91 | SWAP2 | suc 0 rds |
| [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds |
| [27] | 57 | JUMPI | 0 rds |
| [29] | fd | REVERT | |
| [2a] | 5b | JUMPDEST | 0 rds |
| [2b] | f3 | RETURN | |
```
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

In the end, we arrived at the runtime code for Minimal Proxy Contract with `PUSH0`:

```
365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3
```

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 increase the readability of the code. In summary, the new Minimal Proxy Contract reduce `200` gas at deployment and `5` gas at runtime, while remaining same functionalities as the old one.

## Backwards Compatibility

Because the new minimal proxy contract uses `PUSH0` opcode, it can only be deployed after Shanghai Upgrade. It behaves the same as previous Minimal Proxy Contract.
Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Security Considerations

The new proxy contract standard is identical to the previous one (ERC-1167). Here are the security considerations when using minimal proxy contracts:

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.

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.

3. **Safety of Logic Contract**: Vulnerabilities in the logic contract affect all associated proxy contracts.

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.

Pandapip1 marked this conversation as resolved.
Show resolved Hide resolved

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).