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

✨ Improve EIP7702Proxy and add LibEIP7702 #1355

Merged
merged 15 commits into from
Feb 18, 2025
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ accounts
├─ ERC6551 — "Simple ERC6551 account implementation"
├─ ERC6551Proxy — "Relay proxy for upgradeable ERC6551 accounts"
├─ ERC7821 — "Minimal batch executor mixin"
├─ LibEIP7702 — "Library for EIP7702 operations"
├─ LibERC6551 — "Library for interacting with ERC6551 accounts"
├─ LibERC7579 — "Library for handling ERC7579 mode and execution data"
├─ Receiver — "Receiver mixin for ETH and safe-transferred ERC721 and ERC1155 tokens"
Expand Down
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ auto_detect_solc = false
optimizer = true
optimizer_runs = 1_000
gas_limit = 100_000_000 # ETH is 30M, but we use a higher value.
skip = ["*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
skip = ["*/*7702*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
fs_permissions = [{ access = "read", path = "./test/data"}]
remappings = [
"forge-std=test/utils/forge-std/"
]

[profile.pre_global_structs]
skip = ["*/g/*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]
skip = ["*/g/*", "*/*7702*", "*/*Transient*", "*/ext/ithaca/*", "*/ext/zksync/*"]

[profile.post_cancun]
evm_version = "cancun"
Expand Down
1 change: 0 additions & 1 deletion src/Milady.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./accounts/EIP7702Proxy.sol";
import "./accounts/ERC1271.sol";
import "./accounts/ERC4337.sol";
import "./accounts/ERC4337Factory.sol";
Expand Down
57 changes: 42 additions & 15 deletions src/accounts/EIP7702Proxy.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma solidity ^0.8.24;

/// @notice Relay proxy for EIP7702 delegations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/EIP7702Proxy.sol)
Expand Down Expand Up @@ -33,6 +33,11 @@ contract EIP7702Proxy {
bytes32 internal constant _ERC1967_ADMIN_SLOT =
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

/// @dev The transient storage slot for requesting the proxy to initialize the implementation.
/// `uint256(keccak256("eip7702.proxy.delegation.initialization.request")) - 1`.
bytes32 internal constant _EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT =
0x94e11c6e41e7fb92cb8bb65e13fdfbd4eba8b831292a1a220f7915c78c7c078f;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down Expand Up @@ -63,8 +68,8 @@ contract EIP7702Proxy {
return(calldatasize(), 0x20)
}
let fnSel := shr(224, calldataload(0x00))
// `implementation()`.
if eq(0x5c60da1b, fnSel) {
// `implementation()` or `eip7702ProxyImplementation()`.
if or(eq(0x5c60da1b, fnSel), eq(0x7dae87cb, fnSel)) {
if staticcall(gas(), address(), calldatasize(), 0x00, 0x00, 0x20) {
return(0x00, returndatasize())
}
Expand All @@ -77,7 +82,7 @@ contract EIP7702Proxy {
}
// Admin workflow.
if eq(caller(), admin) {
let addr := shr(96, shl(96, calldataload(0x04)))
let addr := shr(96, calldataload(0x10))
// `changeAdmin(address)`.
if eq(0x8f283970, fnSel) {
sstore(_ERC1967_ADMIN_SLOT, addr)
Expand All @@ -97,17 +102,39 @@ contract EIP7702Proxy {
revert(returndatasize(), 0x00)
}
// Workflow for the EIP7702 authority (i.e. the EOA).
// As the authority's storage may be polluted by previous delegations,
// we should always fetch the latest implementation from the proxy.
calldatacopy(0x00, 0x00, calldatasize()) // Forward calldata into the delegatecall.
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)
)
) {
let impl := sload(_ERC1967_IMPLEMENTATION_SLOT) // The preferred implementation on the EOA.
calldatacopy(0x00, 0x00, calldatasize()) // Copy the calldata for the delegatecall.
// If the EOA's implementation, perform the initialization workflow.
if iszero(shl(96, impl)) {
if iszero(
and( // The arguments of `and` are evaluated from right to left.
delegatecall(
gas(), mload(calldatasize()), 0x00, calldatasize(), calldatasize(), 0x00
),
// Fetch the implementation from the proxy.
staticcall(gas(), s, calldatasize(), 0x00, calldatasize(), 0x20)
)
) {
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Because we cannot reliably and efficiently tell if the call is made
// via staticcall or call, we shall ask the delegation to make a proxy delegation
// initialization request to signal that we should initialize the storage slot with
// the actual implementation. This also gives flexibility on whether to let the
// proxy auto-upgrade, or let the authority manually upgrade (via 7702 or passkey).
// A non-zero value in the transient storage denotes a initialization request.
if tload(_EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT) {
let implSlot := _ERC1967_IMPLEMENTATION_SLOT
// The `implementation` is still at `calldatasize()` in memory.
sstore(implSlot, or(shl(160, shr(160, sload(implSlot))), mload(calldatasize())))
tstore(_EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT, 0) // Clear.
}
returndatacopy(0x00, 0x00, returndatasize())
return(0x00, returndatasize())
}
// Otherwise, just delegatecall and bubble up the results without initialization.
if iszero(delegatecall(gas(), impl, 0x00, calldatasize(), calldatasize(), 0x00)) {
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
Expand Down
147 changes: 147 additions & 0 deletions src/accounts/LibEIP7702.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @notice Library for EIP7702 operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/LibEIP7702.sol)
library LibEIP7702 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev The proxy query has failed.
error ProxyQueryFailed();

/// @dev Failed to change the proxy admin.
error ChangeProxyAdminFailed();

/// @dev Failed to upgrade the proxy.
error UpgradeProxyFailed();

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @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 transient storage slot for requesting the proxy to initialize the implementation.
/// `uint256(keccak256("eip7702.proxy.delegation.initialization.request")) - 1`.
bytes32 internal constant _EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT =
0x94e11c6e41e7fb92cb8bb65e13fdfbd4eba8b831292a1a220f7915c78c7c078f;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* AUTHORITY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Returns the delegation of the account.
/// If the account is not an EIP7702 authority, the `delegation` will be `address(0)`.
function delegation(address account) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
extcodecopy(account, 0x00, 0x00, 0x20)
// Note: Checking that it starts with hex"ef01" is the most general and futureproof.
// 7702 bytecode is `abi.encodePacked(hex"ef01", uint8(version), address(delegation))`.
result := mul(shr(96, mload(0x03)), eq(0xef01, shr(240, mload(0x00))))
}
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PROXY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Returns the implementation of the proxy.
/// Assumes that the proxy is a proper EIP7702Proxy, if it exists.
function proxyImplementation(address proxy) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
// Although `implementation()` is supported, we'll use a less common
// function selector to avoid accidental collision with other delegations.
mstore(0x00, 0x7dae87cb) // `eip7702ProxyImplementation()`.
let t := staticcall(gas(), proxy, 0x1c, 0x04, 0x00, 0x20)
if iszero(and(gt(returndatasize(), 0x1f), t)) {
mstore(0x00, 0x26ec9b6a) // `ProxyQueryFailed()`.
revert(0x1c, 0x04)
}
result := mload(0x00)
}
}

/// @dev Returns the admin of the proxy.
/// Assumes that the proxy is a proper EIP7702Proxy, if it exists.
function proxyAdmin(address proxy) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0xf851a440) // `admin()`.
let t := staticcall(gas(), proxy, 0x1c, 0x04, 0x00, 0x20)
if iszero(and(gt(returndatasize(), 0x1f), t)) {
mstore(0x00, 0x26ec9b6a) // `ProxyQueryFailed()`.
revert(0x1c, 0x04)
}
result := mload(0x00)
}
}

/// @dev Changes the admin on the proxy. The caller must be the admin.
/// Assumes that the proxy is a proper EIP7702Proxy, if it exists.
function changeProxyAdmin(address proxy, address newAdmin) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x8f283970) // `changeAdmin(address)`.
mstore(0x20, shr(96, shl(96, newAdmin)))
if iszero(and(eq(mload(0x00), 1), call(gas(), proxy, 0, 0x1c, 0x24, 0x00, 0x20))) {
mstore(0x00, 0xc502e37e) // `ChangeProxyAdminFailed()`.
revert(0x1c, 0x04)
}
}
}

/// @dev Changes the implementation on the proxy. The caller must be the admin.
/// Assumes that the proxy is a proper EIP7702Proxy, if it exists.
function upgradeProxy(address proxy, address newImplementation) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x0900f010) // `upgrade(address)`.
mstore(0x20, shr(96, shl(96, newImplementation)))
if iszero(and(eq(mload(0x00), 1), call(gas(), proxy, 0, 0x1c, 0x24, 0x00, 0x20))) {
mstore(0x00, 0xc6edd882) // `UpgradeProxyFailed()`.
revert(0x1c, 0x04)
}
}
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PROXY DELEGATION OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Upgrades the implementation.
/// The new implementation will NOT be active until the next UserOp or transaction.
/// To "auto-upgrade" to the latest implementation on the proxy, pass in `address(0)` to reset
/// the implementation slot. This causes the proxy to use the latest default implementation,
/// which may be optionally reinitialized via `requestProxyDelegationInitialization()`.
/// This function is intended to be used on the authority of an EIP7702Proxy delegation.
/// The most intended usage pattern is to wrap this in an access-gated admin function.
function upgradeProxyDelegation(address newImplementation) internal {
/// @solidity memory-safe-assembly
assembly {
let s := ERC1967_IMPLEMENTATION_SLOT
mstore(0x00, sload(s))
mstore(0x0c, shl(96, newImplementation))
sstore(s, mload(0x00))
}
}

/// @dev Requests the implementation to be initialized to the latest implementation on the proxy.
/// This function is intended to be used on the authority of an EIP7702Proxy delegation.
/// The most intended usage pattern is to place it at the end of an `execute` function.
function requestProxyDelegationInitialization() internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, sload(ERC1967_IMPLEMENTATION_SLOT))) {
// Use a dedicated transient storage slot for better Swiss-cheese-model safety.
tstore(_EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT, address())
}
}
}
}
Loading
Loading