Skip to content

Commit

Permalink
♻️ Fix EIP7702Proxy and optimize (#1351)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Feb 11, 2025
1 parent be154cd commit 947389e
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 26 deletions.
35 changes: 17 additions & 18 deletions src/accounts/EIP7702Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ pragma solidity ^0.8.4;
///
/// 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 */
Expand Down Expand Up @@ -54,15 +51,21 @@ contract EIP7702Proxy {
fallback() external payable virtual {
uint256 s = __self;
assembly {
mstore(0x40, returndatasize()) // Optimization trick to change `6040608052` into `3d604052`.
// 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) {
if iszero(xor(address(), s)) {
if iszero(calldatasize()) {
mstore(calldatasize(), sload(_ERC1967_IMPLEMENTATION_SLOT))
return(calldatasize(), 0x20)
}
let fnSel := shr(224, calldataload(0x00))
// `implementation()`.
if eq(0x5c60da1b, fnSel) {
mstore(0x00, sload(_ERC1967_IMPLEMENTATION_SLOT))
return(0x00, 0x20)
if staticcall(gas(), address(), calldatasize(), 0x00, 0x00, 0x20) {
return(0x00, returndatasize())
}
}
let admin := sload(_ERC1967_ADMIN_SLOT)
// `admin()`.
Expand Down Expand Up @@ -92,21 +95,17 @@ contract EIP7702Proxy {
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)) {
if iszero(
and( // The arguments of `and` are evaluated from right to left.
delegatecall(
gas(), mload(calldatasize()), 0x00, calldatasize(), calldatasize(), 0x00
),
staticcall(gas(), s, calldatasize(), 0x00, calldatasize(), 0x20)
)
) {
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
Expand Down
24 changes: 16 additions & 8 deletions test/EIP7702Proxy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ contract EIP7702ProxyTest is SoladyTest {
// EIP7702ProxyTest(instance).revertWithError();
}

function testEIP7702Proxy(bytes32) public {
function testEIP7702Proxy(bytes32, bool f) public {
vm.pauseGasMetering();

address admin = _randomUniqueHashedAddress();
IEIP7702ProxyWithAdminABI eip7702Proxy =
IEIP7702ProxyWithAdminABI(address(new EIP7702Proxy(address(this), admin)));
assertEq(eip7702Proxy.admin(), admin);
assertEq(eip7702Proxy.implementation(), address(this));

if (_randomChance(32)) {
if (!f && _randomChance(16)) {
address newAdmin = _randomUniqueHashedAddress();
vm.startPrank(admin);
eip7702Proxy.changeAdmin(newAdmin);
Expand All @@ -67,7 +69,7 @@ contract EIP7702ProxyTest is SoladyTest {
vm.stopPrank();
}

if (_randomChance(32)) {
if (!f && _randomChance(16)) {
address newImplementation = _randomUniqueHashedAddress();
vm.startPrank(admin);
eip7702Proxy.upgrade(newImplementation);
Expand All @@ -77,7 +79,7 @@ contract EIP7702ProxyTest is SoladyTest {
vm.stopPrank();
}

if (_randomChance(32)) {
if (!f && _randomChance(16)) {
vm.startPrank(admin);
vm.expectRevert();
eip7702Proxy.bad();
Expand All @@ -87,16 +89,22 @@ contract EIP7702ProxyTest is SoladyTest {
address authority = _randomUniqueHashedAddress();
vm.etch(authority, abi.encodePacked(hex"ef0100", address(eip7702Proxy)));

emit LogAddress("authority", authority);
emit LogAddress("proxy", address(eip7702Proxy));
emit LogAddress("address(this)", address(this));

vm.resumeGasMetering();

// 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);
}

function testEIP7702Proxy() public {
this.testEIP7702Proxy(0, true);
}
}

0 comments on commit 947389e

Please sign in to comment.