209 lines
8.1 KiB
Markdown
209 lines
8.1 KiB
Markdown
|
---
|
||
|
eip: 3448
|
||
|
title: MetaProxy Standard
|
||
|
description: A minimal bytecode implementation for creating proxy contracts with immutable metadata attached to the bytecode
|
||
|
author: pinkiebell (@pinkiebell)
|
||
|
discussions-to: https://ethereum-magicians.org/t/erc-3448-metaproxy-factory/5834
|
||
|
status: Final
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2021-03-29
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
By standardizing on a known minimal bytecode proxy implementation with support for immutable metadata, this standard allows users and third party tools (e.g. Etherscan) to:
|
||
|
(a) simply discover that a contract will always redirect in a known manner and
|
||
|
(b) depend on the behavior of the code at the destination contract as the behavior of the redirecting contract and
|
||
|
(c) verify/view the attached metadata.
|
||
|
|
||
|
Tooling can interrogate the bytecode at a redirecting address to determine the location of the code that will run along with the associated metadata - and can depend on representations about that code (verified source, third-party audits, etc).
|
||
|
This implementation forwards all calls via `DELEGATECALL` and any (calldata) input plus the metadata at the end of the bytecode to the implementation contract and then relays the return value back to the caller.
|
||
|
In the case where the implementation reverts, the revert is passed back along with the payload data.
|
||
|
|
||
|
## Motivation
|
||
|
This standard supports use-cases wherein it is desirable to clone exact contract functionality with different parameters at another address.
|
||
|
|
||
|
## Specification
|
||
|
The exact bytecode of the MetaProxy contract is:
|
||
|
```
|
||
|
20 bytes target contract address
|
||
|
----------------------------------------
|
||
|
363d3d373d3d3d3d60368038038091363936013d7300000000000000000000000000000000000000005af43d3d93803e603457fd5bf3
|
||
|
```
|
||
|
wherein the bytes at indices 21 - 41 (inclusive) are replaced with the 20 byte address of the master functionality contract.
|
||
|
Additionally, everything after the MetaProxy bytecode can be arbitrary metadata and the last 32 bytes (one word) of the bytecode must indicate the length of the metadata in bytes.
|
||
|
|
||
|
```
|
||
|
<54 bytes metaproxy> <arbitrary data> <length in bytes of arbitrary data (uint256)>
|
||
|
```
|
||
|
|
||
|
## Rationale
|
||
|
The goals of this effort have been the following:
|
||
|
- a cheap way of storing immutable metadata for each child instead of using storage slots
|
||
|
- inexpensive deployment of clones
|
||
|
- handles error return bubbling for revert messages
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
There are no backwards compatibility issues.
|
||
|
|
||
|
## Test Cases
|
||
|
Tested with:
|
||
|
- invocation with no arguments
|
||
|
- invocation with arguments
|
||
|
- invocation with return values
|
||
|
- invocation with revert (confirming reverted payload is transferred)
|
||
|
|
||
|
A solidity contract with the above test cases can be found [in the EIP asset directory](../assets/eip-3448/MetaProxyTest.sol).
|
||
|
|
||
|
## Reference Implementation
|
||
|
A reference implementation can be found [in the EIP asset directory](../assets/eip-3448/MetaProxyFactory.sol).
|
||
|
|
||
|
### Deployment bytecode
|
||
|
A annotated version of the deploy bytecode:
|
||
|
```
|
||
|
// PUSH1 11;
|
||
|
// CODESIZE;
|
||
|
// SUB;
|
||
|
// DUP1;
|
||
|
// PUSH1 11;
|
||
|
// RETURNDATASIZE;
|
||
|
// CODECOPY;
|
||
|
// RETURNDATASIZE;
|
||
|
// RETURN;
|
||
|
```
|
||
|
|
||
|
### MetaProxy
|
||
|
A annotated version of the MetaProxy bytecode:
|
||
|
```
|
||
|
// copy args
|
||
|
// CALLDATASIZE; calldatasize
|
||
|
// RETURNDATASIZE; 0, calldatasize
|
||
|
// RETURNDATASIZE; 0, 0, calldatasize
|
||
|
// CALLDATACOPY;
|
||
|
|
||
|
// RETURNDATASIZE; 0
|
||
|
// RETURNDATASIZE; 0, 0
|
||
|
// RETURNDATASIZE; 0, 0, 0
|
||
|
// RETURNDATASIZE; 0, 0, 0, 0
|
||
|
|
||
|
// PUSH1 54; 54, 0, 0, 0, 0
|
||
|
// DUP1; 54, 54, 0, 0, 0, 0
|
||
|
// CODESIZE; codesize, 54, 54, 0, 0, 0, 0
|
||
|
// SUB; codesize-54, 54, 0, 0, 0, 0
|
||
|
// DUP1; codesize-54, codesize-54, 54, 0, 0, 0, 0
|
||
|
// SWAP2; 54, codesize-54, codesize-54, 0, 0, 0, 0
|
||
|
// CALLDATASIZE; calldatasize, 54, codesize-54, codesize-54, 0, 0, 0, 0
|
||
|
// CODECOPY; codesize-54, 0, 0, 0, 0
|
||
|
|
||
|
// CALLDATASIZE; calldatasize, codesize-54, 0, 0, 0, 0
|
||
|
// ADD; calldatasize+codesize-54, 0, 0, 0, 0
|
||
|
// RETURNDATASIZE; 0, calldatasize+codesize-54, 0, 0, 0, 0
|
||
|
// PUSH20 0; addr, 0, calldatasize+codesize-54, 0, 0, 0, 0 - zero is replaced with shl(96, address())
|
||
|
// GAS; gas, addr, 0, calldatasize+codesize-54, 0, 0, 0, 0
|
||
|
// DELEGATECALL; (gas, addr, 0, calldatasize() + metadata, 0, 0) delegatecall to the target contract;
|
||
|
//
|
||
|
// RETURNDATASIZE; returndatasize, retcode, 0, 0
|
||
|
// RETURNDATASIZE; returndatasize, returndatasize, retcode, 0, 0
|
||
|
// SWAP4; 0, returndatasize, retcode, 0, returndatasize
|
||
|
// DUP1; 0, 0, returndatasize, retcode, 0, returndatasize
|
||
|
// RETURNDATACOPY; (0, 0, returndatasize) - Copy everything into memory that the call returned
|
||
|
|
||
|
// stack = retcode, 0, returndatasize # this is for either revert(0, returndatasize()) or return (0, returndatasize())
|
||
|
|
||
|
// PUSH1 _SUCCESS_; push jumpdest of _SUCCESS_
|
||
|
// JUMPI; jump if delegatecall returned `1`
|
||
|
// REVERT; (0, returndatasize()) if delegatecall returned `0`
|
||
|
// JUMPDEST _SUCCESS_;
|
||
|
// RETURN; (0, returndatasize()) if delegatecall returned non-zero (1)
|
||
|
```
|
||
|
|
||
|
### Examples
|
||
|
The following code snippets serve only as suggestions and are not a discrete part of this standard.
|
||
|
|
||
|
#### Proxy construction with bytes from abi.encode
|
||
|
```solidity
|
||
|
/// @notice MetaProxy construction via abi encoded bytes.
|
||
|
function createFromBytes (
|
||
|
address a,
|
||
|
uint256 b,
|
||
|
uint256[] calldata c
|
||
|
) external payable returns (address proxy) {
|
||
|
// creates a new proxy where the metadata is the result of abi.encode()
|
||
|
proxy = MetaProxyFactory._metaProxyFromBytes(address(this), abi.encode(a, b, c));
|
||
|
require(proxy != address(0));
|
||
|
// optional one-time setup, a constructor() substitute
|
||
|
MyContract(proxy).init{ value: msg.value }();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Proxy construction with bytes from calldata
|
||
|
```solidity
|
||
|
/// @notice MetaProxy construction via calldata.
|
||
|
function createFromCalldata (
|
||
|
address a,
|
||
|
uint256 b,
|
||
|
uint256[] calldata c
|
||
|
) external payable returns (address proxy) {
|
||
|
// creates a new proxy where the metadata is everything after the 4th byte from calldata.
|
||
|
proxy = MetaProxyFactory._metaProxyFromCalldata(address(this));
|
||
|
require(proxy != address(0));
|
||
|
// optional one-time setup, a constructor() substitute
|
||
|
MyContract(proxy).init{ value: msg.value }();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Retrieving the metadata from calldata and abi.decode
|
||
|
```solidity
|
||
|
/// @notice Returns the metadata of this (MetaProxy) contract.
|
||
|
/// Only relevant with contracts created via the MetaProxy standard.
|
||
|
/// @dev This function is aimed to be invoked with- & without a call.
|
||
|
function getMetadataWithoutCall () public pure returns (
|
||
|
address a,
|
||
|
uint256 b,
|
||
|
uint256[] memory c
|
||
|
) {
|
||
|
bytes memory data;
|
||
|
assembly {
|
||
|
let posOfMetadataSize := sub(calldatasize(), 32)
|
||
|
let size := calldataload(posOfMetadataSize)
|
||
|
let dataPtr := sub(posOfMetadataSize, size)
|
||
|
data := mload(64)
|
||
|
// increment free memory pointer by metadata size + 32 bytes (length)
|
||
|
mstore(64, add(data, add(size, 32)))
|
||
|
mstore(data, size)
|
||
|
let memPtr := add(data, 32)
|
||
|
calldatacopy(memPtr, dataPtr, size)
|
||
|
}
|
||
|
return abi.decode(data, (address, uint256, uint256[]));
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Retrieving the metadata via a call to self
|
||
|
```solidity
|
||
|
/// @notice Returns the metadata of this (MetaProxy) contract.
|
||
|
/// Only relevant with contracts created via the MetaProxy standard.
|
||
|
/// @dev This function is aimed to to be invoked via a call.
|
||
|
function getMetadataViaCall () public pure returns (
|
||
|
address a,
|
||
|
uint256 b,
|
||
|
uint256[] memory c
|
||
|
) {
|
||
|
assembly {
|
||
|
let posOfMetadataSize := sub(calldatasize(), 32)
|
||
|
let size := calldataload(posOfMetadataSize)
|
||
|
let dataPtr := sub(posOfMetadataSize, size)
|
||
|
calldatacopy(0, dataPtr, size)
|
||
|
return(0, size)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Apart from the examples above, it is also possible to use Solidity Structures or any custom data encoding.
|
||
|
|
||
|
## Security Considerations
|
||
|
This standard only covers the bytecode implementation and does not include any serious side effects of itself.
|
||
|
The reference implementation only serves as a example. It is highly recommended to research side effects depending on how the functionality is used and implemented in any project.
|
||
|
|
||
|
## Copyright
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|