-
Notifications
You must be signed in to change notification settings - Fork 376
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
827d103
commit b904efc
Showing
5 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# EIP7702Proxy | ||
|
||
Relay proxy for EIP7702 delegations. | ||
|
||
|
||
<b>Note:</b> | ||
|
||
This relay proxy is useful for upgradeable EIP7702 accounts | ||
without the need for redelegation. | ||
|
||
EOA -> EIP7702Proxy (relay) -> EIP7702 account implementation. | ||
|
||
This relay proxy also allows for correctly revealing the | ||
"Read as Proxy" and "Write as Proxy" tabs on Etherscan. | ||
|
||
This proxy can only be used by a EIP7702 authority. | ||
If any regular contract uses this proxy, it will not work. | ||
|
||
|
||
|
||
<!-- customintro:start --><!-- customintro:end --> | ||
|
||
## Immutables | ||
|
||
### __self | ||
|
||
```solidity | ||
uint256 internal immutable __self = uint256(uint160(address(this))) | ||
``` | ||
|
||
For allowing the differentiation of the EOA and the proxy itself. | ||
|
||
## Storage | ||
|
||
### _ERC1967_IMPLEMENTATION_SLOT | ||
|
||
```solidity | ||
bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = | ||
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc | ||
``` | ||
|
||
The ERC-1967 storage slot for the implementation in the proxy. | ||
`uint256(keccak256("eip1967.proxy.implementation")) - 1`. | ||
|
||
### _ERC1967_ADMIN_SLOT | ||
|
||
```solidity | ||
bytes32 internal constant _ERC1967_ADMIN_SLOT = | ||
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 | ||
``` | ||
|
||
The ERC-1967 storage slot for the admin in the proxy. | ||
`uint256(keccak256("eip1967.proxy.admin")) - 1`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
/// @notice Relay proxy for EIP7702 delegations. | ||
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/EIP7702Proxy.sol) | ||
/// | ||
/// @dev Note: This relay proxy is useful for upgradeable EIP7702 accounts | ||
/// without the need for redelegation. | ||
/// | ||
/// EOA -> EIP7702Proxy (relay) -> EIP7702 account implementation. | ||
/// | ||
/// This relay proxy also allows for correctly revealing the | ||
/// "Read as Proxy" and "Write as Proxy" tabs on Etherscan. | ||
/// | ||
/// This proxy can only be used by a EIP7702 authority. | ||
/// If any regular contract uses this proxy, it will not work. | ||
contract EIP7702Proxy { | ||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* IMMUTABLES */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @dev For allowing the differentiation of the EOA and the proxy itself. | ||
uint256 internal immutable __self = uint256(uint160(address(this))); | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* STORAGE */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
/// @dev The ERC-1967 storage slot for the implementation in the proxy. | ||
/// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. | ||
bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = | ||
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | ||
|
||
/// @dev The ERC-1967 storage slot for the admin in the proxy. | ||
/// `uint256(keccak256("eip1967.proxy.admin")) - 1`. | ||
bytes32 internal constant _ERC1967_ADMIN_SLOT = | ||
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* CONSTRUCTOR */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
constructor(address initialImplementation, address initialAdmin) payable { | ||
assembly { | ||
sstore(_ERC1967_IMPLEMENTATION_SLOT, shr(96, shl(96, initialImplementation))) | ||
sstore(_ERC1967_ADMIN_SLOT, shr(96, shl(96, initialAdmin))) | ||
} | ||
} | ||
|
||
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ | ||
/* FALLBACK */ | ||
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ | ||
|
||
fallback() external payable virtual { | ||
uint256 s = __self; | ||
assembly { | ||
// Workflow for calling on the proxy itself. | ||
// We cannot put these functions in the public ABI as this proxy must | ||
// fully forward all the calldata from EOAs pointing to this proxy. | ||
if eq(address(), s) { | ||
let fnSel := shr(224, calldataload(0x00)) | ||
// `implementation()`. | ||
if eq(0x5c60da1b, fnSel) { | ||
mstore(0x00, sload(_ERC1967_IMPLEMENTATION_SLOT)) | ||
return(0x00, 0x20) | ||
} | ||
let admin := sload(_ERC1967_ADMIN_SLOT) | ||
// `admin()`. | ||
if eq(0xf851a440, fnSel) { | ||
mstore(0x00, admin) | ||
return(0x00, 0x20) | ||
} | ||
// Admin workflow. | ||
if eq(caller(), admin) { | ||
let addr := shr(96, shl(96, calldataload(0x04))) | ||
// `changeAdmin(address)`. | ||
if eq(0x8f283970, fnSel) { | ||
sstore(_ERC1967_ADMIN_SLOT, addr) | ||
mstore(0x00, 1) | ||
return(0x00, 0x20) // Store and return `true`. | ||
} | ||
// `upgrade(address)`. | ||
if eq(0x0900f010, fnSel) { | ||
sstore(_ERC1967_IMPLEMENTATION_SLOT, addr) | ||
mstore(0x00, 1) | ||
return(0x00, 0x20) // Store and return `true`. | ||
} | ||
// For minimalism, we shall skip events and calldata bounds checks. | ||
// We don't need to forward any data to the new implementation. | ||
// This "proxy" is actually close to an upgradeable beacon. | ||
} | ||
revert(returndatasize(), 0x00) | ||
} | ||
// Workflow for the EIP7702 authority (i.e. the EOA). | ||
// Copy the delegation from the EIP7702 bytecode. | ||
extcodecopy(address(), 0x20, 0x00, 0x20) // Out-of-bounds bytes copied are zero. | ||
mstore(0x00, 0x5c60da1b) // `implementation()`. | ||
// Require that the bytecode is less than 24 bytes and begins with the expected prefix. | ||
if iszero( | ||
and( // Any dirty upper 96 bits of the target address is ignored in `staticcall`. | ||
staticcall(gas(), mload(0x17), 0x1c, 0x04, 0x00, 0x20), | ||
and(eq(0xef0100, shr(232, mload(0x20))), lt(extcodesize(address()), 24)) | ||
) | ||
) { revert(returndatasize(), 0x00) } | ||
// As the authority's storage may be polluted by previous delegations, | ||
// we should always fetch the latest implementation from the proxy. | ||
let implementation := mload(0x00) | ||
calldatacopy(0x00, 0x00, calldatasize()) // Forward calldata into the delegatecall. | ||
if iszero(delegatecall(gas(), implementation, 0x00, calldatasize(), 0x00, 0x00)) { | ||
returndatacopy(0x00, 0x00, returndatasize()) | ||
revert(0x00, returndatasize()) | ||
} | ||
returndatacopy(0x00, 0x00, returndatasize()) | ||
return(0x00, returndatasize()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import "./utils/SoladyTest.sol"; | ||
import {EIP7702Proxy} from "../src/accounts/EIP7702Proxy.sol"; | ||
|
||
interface IEIP7702ProxyWithAdminABI { | ||
function implementation() external view returns (address); | ||
function admin() external view returns (address); | ||
function changeAdmin(address) external returns (bool); | ||
function upgrade(address) external returns (bool); | ||
function bad() external; | ||
} | ||
|
||
contract EIP7702ProxyTest is SoladyTest { | ||
error CustomError(uint256 currentValue); | ||
|
||
uint256 public value; | ||
|
||
bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = | ||
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | ||
|
||
bytes32 internal constant _ERC1967_ADMIN_SLOT = | ||
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; | ||
|
||
function setValue(uint256 value_) public { | ||
value = value_; | ||
} | ||
|
||
function revertWithError() public view { | ||
revert CustomError(value); | ||
} | ||
|
||
function _checkBehavesLikeProxy(address instance) internal { | ||
assertTrue(instance != address(0)); | ||
|
||
uint256 v = _random(); | ||
uint256 thisValue = this.value(); | ||
if (thisValue == v) { | ||
v ^= 1; | ||
} | ||
EIP7702ProxyTest(instance).setValue(v); | ||
assertEq(v, EIP7702ProxyTest(instance).value()); | ||
// assertEq(thisValue, this.value()); | ||
// vm.expectRevert(abi.encodeWithSelector(CustomError.selector, v)); | ||
// EIP7702ProxyTest(instance).revertWithError(); | ||
} | ||
|
||
function testEIP7702Proxy(bytes32) public { | ||
address admin = _randomUniqueHashedAddress(); | ||
IEIP7702ProxyWithAdminABI eip7702Proxy = | ||
IEIP7702ProxyWithAdminABI(address(new EIP7702Proxy(address(this), admin))); | ||
assertEq(eip7702Proxy.admin(), admin); | ||
assertEq(eip7702Proxy.implementation(), address(this)); | ||
|
||
if (_randomChance(32)) { | ||
address newAdmin = _randomUniqueHashedAddress(); | ||
vm.startPrank(admin); | ||
eip7702Proxy.changeAdmin(newAdmin); | ||
assertEq(eip7702Proxy.admin(), newAdmin); | ||
vm.stopPrank(); | ||
admin = newAdmin; | ||
|
||
vm.startPrank(_randomUniqueHashedAddress()); | ||
vm.expectRevert(); | ||
eip7702Proxy.changeAdmin(newAdmin); | ||
vm.stopPrank(); | ||
} | ||
|
||
if (_randomChance(32)) { | ||
address newImplementation = _randomUniqueHashedAddress(); | ||
vm.startPrank(admin); | ||
eip7702Proxy.upgrade(newImplementation); | ||
assertEq(eip7702Proxy.implementation(), newImplementation); | ||
eip7702Proxy.upgrade(address(this)); | ||
assertEq(eip7702Proxy.implementation(), address(this)); | ||
vm.stopPrank(); | ||
} | ||
|
||
if (_randomChance(32)) { | ||
vm.startPrank(admin); | ||
vm.expectRevert(); | ||
eip7702Proxy.bad(); | ||
vm.stopPrank(); | ||
} | ||
|
||
address authority = _randomUniqueHashedAddress(); | ||
vm.etch(authority, abi.encodePacked(hex"ef0100", address(eip7702Proxy))); | ||
|
||
// Runtime REVM detection. | ||
// If this check fails, then we are not ready to test it in CI. | ||
// The exact length is 23 at the time of writing as of the EIP7702 spec, | ||
// but we give our heuristic some leeway. | ||
if (authority.code.length > 0x20) return; | ||
|
||
emit LogAddress("authority", authority); | ||
emit LogAddress("proxy", address(eip7702Proxy)); | ||
emit LogAddress("address(this)", address(this)); | ||
|
||
_checkBehavesLikeProxy(authority); | ||
} | ||
} |