Latest 8 from a total of 8 transactions
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Transfer | 23923671 | 8 days ago | IN | 0.00002776 ETH | 0.00000084 | ||||
| Transfer | 23850734 | 18 days ago | IN | 0.00002945 ETH | 0.00000534 | ||||
| Transfer | 23691713 | 41 days ago | IN | 0.00002293 ETH | 0.00003619 | ||||
| Transfer | 23648905 | 47 days ago | IN | 0.00002125 ETH | 0.00000369 | ||||
| Transfer | 23588110 | 55 days ago | IN | 0.00002044 ETH | 0.00000394 | ||||
| Transfer | 23578693 | 56 days ago | IN | 0.00002044 ETH | 0.00000443 | ||||
| Transfer | 23441542 | 76 days ago | IN | 0.0000165 ETH | 0.0001484 | ||||
| Transfer | 23434380 | 77 days ago | IN | 0.0000165 ETH | 0.00001114 |
Latest 9 internal transactions
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| Send | 23924324 | 8 days ago | 0.00001851 ETH | ||||
| Send | 23851028 | 18 days ago | 0.00001963 ETH | ||||
| Send | 23694416 | 40 days ago | 0.00001362 ETH | ||||
| Send | 23650233 | 46 days ago | 0.00001416 ETH | ||||
| Send | 23588275 | 55 days ago | 0.00001363 ETH | ||||
| Send | 23584635 | 56 days ago | 0.00001363 ETH | ||||
| Send | 23450622 | 74 days ago | 0.00001405 ETH | ||||
| Send | 23436372 | 76 days ago | 0.0000132 ETH | ||||
| 0x6100513d | 23436369 | 76 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Minimal Proxy Contract for 0x000000000000c5a9089039570dd36455b5c07383
Contract Name:
SemiModularAccountBytecode
Compiler Version
v0.8.26+commit.8a97fa7a
Optimization Enabled:
Yes with 50000 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {LibClone} from "solady/utils/LibClone.sol";
import {ExecutionInstallDelegate} from "../helpers/ExecutionInstallDelegate.sol";
import {SemiModularAccountBase} from "./SemiModularAccountBase.sol";
/// @title Semi-Modular Account Bytecode
/// @author Alchemy
/// @notice An implementation of a semi-modular account which reads the signer from proxy bytecode if it is not
/// disabled and zero in storage.
/// @dev Inherits SemiModularAccountBase. This account requires that its proxy is compliant with Solady's LibClone
/// ERC1967WithImmutableArgs bytecode with a bytecode-appended address (should be encodePacked) to be used as the
/// fallback signer.
contract SemiModularAccountBytecode is SemiModularAccountBase {
constructor(IEntryPoint entryPoint, ExecutionInstallDelegate executionInstallDelegate)
SemiModularAccountBase(entryPoint, executionInstallDelegate)
{}
/// @inheritdoc IModularAccount
function accountId() external pure override returns (string memory) {
return "alchemy.sma-bytecode.1.0.0";
}
/// @dev If the fallback signer is set in storage, we ignore the bytecode signer.
function _retrieveFallbackSignerUnchecked(SemiModularAccountStorage storage _storage)
internal
view
override
returns (address)
{
address storageFallbackSigner = _storage.fallbackSigner;
if (storageFallbackSigner != address(0)) {
return storageFallbackSigner;
}
// If the signer in storage is zero, default to
bytes memory appendedData = LibClone.argsOnERC1967(address(this), 0, 20);
return address(uint160(bytes20(appendedData)));
}
}// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {ExecutionManifest} from "./IExecutionModule.sol";
type ModuleEntity is bytes24;
// ModuleEntity is a packed representation of a module function
// Layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address
// 0x________________________________________BBBBBBBB________________ // Entity ID
// 0x________________________________________________0000000000000000 // unused
type ValidationConfig is bytes25;
// ValidationConfig is a packed representation of a validation function and flags for its configuration.
// Layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address
// 0x________________________________________BBBBBBBB________________ // Entity ID
// 0x________________________________________________CC______________ // ValidationFlags
// 0x__________________________________________________00000000000000 // unused
type ValidationFlags is uint8;
// ValidationFlags layout:
// 0b00000___ // unused
// 0b_____A__ // isGlobal
// 0b______B_ // isSignatureValidation
// 0b_______C // isUserOpValidation
type HookConfig is bytes25;
// HookConfig is a packed representation of a hook function and flags for its configuration.
// Layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address
// 0x________________________________________BBBBBBBB________________ // Entity ID
// 0x________________________________________________CC______________ // Hook Flags
//
// Hook flags layout:
// 0b00000___ // unused
// 0b_____A__ // hasPre (exec only)
// 0b______B_ // hasPost (exec only)
// 0b_______C // hook type (0 for exec, 1 for validation)
struct Call {
// The target address for the account to call.
address target;
// The value to send with the call.
uint256 value;
// The calldata for the call.
bytes data;
}
interface IModularAccount {
event ExecutionInstalled(address indexed module, ExecutionManifest manifest);
event ExecutionUninstalled(address indexed module, bool onUninstallSucceeded, ExecutionManifest manifest);
event ValidationInstalled(address indexed module, uint32 indexed entityId);
event ValidationUninstalled(address indexed module, uint32 indexed entityId, bool onUninstallSucceeded);
/// @notice Standard execute method.
/// @param target The target address for the account to call.
/// @param value The value to send with the call.
/// @param data The calldata for the call.
/// @return The return data from the call.
function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory);
/// @notice Standard executeBatch method.
/// @dev If the target is a module, the call SHOULD revert. If any of the calls revert, the entire batch MUST
/// revert.
/// @param calls The array of calls.
/// @return An array containing the return data from the calls.
function executeBatch(Call[] calldata calls) external payable returns (bytes[] memory);
/// @notice Execute a call using the specified runtime validation.
/// @param data The calldata to send to the account.
/// @param authorization The authorization data to use for the call. The first 24 bytes is a ModuleEntity which
/// specifies which runtime validation to use, and the rest is sent as a parameter to runtime validation.
function executeWithRuntimeValidation(bytes calldata data, bytes calldata authorization)
external
payable
returns (bytes memory);
/// @notice Install a module to the modular account.
/// @param module The module to install.
/// @param manifest the manifest describing functions to install.
/// @param installData Optional data to be used by the account to handle the initial execution setup. Data
/// encoding
/// is implementation-specific.
function installExecution(address module, ExecutionManifest calldata manifest, bytes calldata installData)
external;
/// @notice Uninstall a module from the modular account.
/// @param module The module to uninstall.
/// @param manifest the manifest describing functions to uninstall.
/// @param uninstallData Optional data to be used by the account to handle the execution uninstallation. Data
/// encoding is implementation-specific.
function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata uninstallData)
external;
/// @notice Installs a validation function across a set of execution selectors, and optionally mark it as a
/// global validation function.
/// @dev This does not validate anything against the manifest - the caller must ensure validity.
/// @param validationConfig The validation function to install, along with configuration flags.
/// @param selectors The selectors to install the validation function for.
/// @param installData Optional data to be used by the account to handle the initial validation setup. Data
/// encoding is implementation-specific.
/// @param hooks Optional hooks to install and associate with the validation function. Data encoding is
/// implementation-specific.
function installValidation(
ValidationConfig validationConfig,
bytes4[] calldata selectors,
bytes calldata installData,
bytes[] calldata hooks
) external;
/// @notice Uninstall a validation function from a set of execution selectors.
/// @param validationFunction The validation function to uninstall.
/// @param uninstallData Optional data to be used by the account to handle the validation uninstallation. Data
/// encoding is implementation-specific.
/// @param hookUninstallData Optional data to be used by the account to handle hook uninstallation. Data
/// encoding
/// is implementation-specific.
function uninstallValidation(
ModuleEntity validationFunction,
bytes calldata uninstallData,
bytes[] calldata hookUninstallData
) external;
/// @notice Return a unique identifier for the account implementation.
/// @dev This function MUST return a string in the format "vendor.account.semver". The vendor and account
/// names MUST NOT contain a period character.
/// @return The account ID.
function accountId() external view returns (string memory);
}/**
** Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
** Only one instance required on each chain.
**/
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */
import "./PackedUserOperation.sol";
import "./IStakeManager.sol";
import "./IAggregator.sol";
import "./INonceManager.sol";
interface IEntryPoint is IStakeManager, INonceManager {
/***
* An event emitted after each successful request.
* @param userOpHash - Unique identifier for the request (hash its entire content, except signature).
* @param sender - The account that generates this request.
* @param paymaster - If non-null, the paymaster that pays for this request.
* @param nonce - The nonce value from the request.
* @param success - True if the sender transaction succeeded, false if reverted.
* @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation.
* @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation,
* validation and execution).
*/
event UserOperationEvent(
bytes32 indexed userOpHash,
address indexed sender,
address indexed paymaster,
uint256 nonce,
bool success,
uint256 actualGasCost,
uint256 actualGasUsed
);
/**
* Account "sender" was deployed.
* @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow.
* @param sender - The account that is deployed
* @param factory - The factory used to deploy this account (in the initCode)
* @param paymaster - The paymaster used by this UserOp
*/
event AccountDeployed(
bytes32 indexed userOpHash,
address indexed sender,
address factory,
address paymaster
);
/**
* An event emitted if the UserOperation "callData" reverted with non-zero length.
* @param userOpHash - The request unique identifier.
* @param sender - The sender of this request.
* @param nonce - The nonce used in the request.
* @param revertReason - The return bytes from the (reverted) call to "callData".
*/
event UserOperationRevertReason(
bytes32 indexed userOpHash,
address indexed sender,
uint256 nonce,
bytes revertReason
);
/**
* An event emitted if the UserOperation Paymaster's "postOp" call reverted with non-zero length.
* @param userOpHash - The request unique identifier.
* @param sender - The sender of this request.
* @param nonce - The nonce used in the request.
* @param revertReason - The return bytes from the (reverted) call to "callData".
*/
event PostOpRevertReason(
bytes32 indexed userOpHash,
address indexed sender,
uint256 nonce,
bytes revertReason
);
/**
* UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made.
* @param userOpHash - The request unique identifier.
* @param sender - The sender of this request.
* @param nonce - The nonce used in the request.
*/
event UserOperationPrefundTooLow(
bytes32 indexed userOpHash,
address indexed sender,
uint256 nonce
);
/**
* An event emitted by handleOps(), before starting the execution loop.
* Any event emitted before this event, is part of the validation.
*/
event BeforeExecution();
/**
* Signature aggregator used by the following UserOperationEvents within this bundle.
* @param aggregator - The aggregator used for the following UserOperationEvents.
*/
event SignatureAggregatorChanged(address indexed aggregator);
/**
* A custom revert error of handleOps, to identify the offending op.
* Should be caught in off-chain handleOps simulation and not happen on-chain.
* Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts.
* NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it.
* @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero).
* @param reason - Revert reason. The string starts with a unique code "AAmn",
* where "m" is "1" for factory, "2" for account and "3" for paymaster issues,
* so a failure can be attributed to the correct entity.
*/
error FailedOp(uint256 opIndex, string reason);
/**
* A custom revert error of handleOps, to report a revert by account or paymaster.
* @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero).
* @param reason - Revert reason. see FailedOp(uint256,string), above
* @param inner - data from inner cought revert reason
* @dev note that inner is truncated to 2048 bytes
*/
error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner);
error PostOpReverted(bytes returnData);
/**
* Error case when a signature aggregator fails to verify the aggregated signature it had created.
* @param aggregator The aggregator that failed to verify the signature
*/
error SignatureValidationFailed(address aggregator);
// Return value of getSenderAddress.
error SenderAddressResult(address sender);
// UserOps handled, per aggregator.
struct UserOpsPerAggregator {
PackedUserOperation[] userOps;
// Aggregator address
IAggregator aggregator;
// Aggregated signature
bytes signature;
}
/**
* Execute a batch of UserOperations.
* No signature aggregator is used.
* If any account requires an aggregator (that is, it returned an aggregator when
* performing simulateValidation), then handleAggregatedOps() must be used instead.
* @param ops - The operations to execute.
* @param beneficiary - The address to receive the fees.
*/
function handleOps(
PackedUserOperation[] calldata ops,
address payable beneficiary
) external;
/**
* Execute a batch of UserOperation with Aggregators
* @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts).
* @param beneficiary - The address to receive the fees.
*/
function handleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
address payable beneficiary
) external;
/**
* Generate a request Id - unique identifier for this request.
* The request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid.
* @param userOp - The user operation to generate the request ID for.
* @return hash the hash of this UserOperation
*/
function getUserOpHash(
PackedUserOperation calldata userOp
) external view returns (bytes32);
/**
* Gas and return values during simulation.
* @param preOpGas - The gas used for validation (including preValidationGas)
* @param prefund - The required prefund for this operation
* @param accountValidationData - returned validationData from account.
* @param paymasterValidationData - return validationData from paymaster.
* @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp)
*/
struct ReturnInfo {
uint256 preOpGas;
uint256 prefund;
uint256 accountValidationData;
uint256 paymasterValidationData;
bytes paymasterContext;
}
/**
* Returned aggregated signature info:
* The aggregator returned by the account, and its current stake.
*/
struct AggregatorStakeInfo {
address aggregator;
StakeInfo stakeInfo;
}
/**
* Get counterfactual sender address.
* Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation.
* This method always revert, and returns the address in SenderAddressResult error
* @param initCode - The constructor code to be passed into the UserOperation.
*/
function getSenderAddress(bytes memory initCode) external;
error DelegateAndRevert(bool success, bytes ret);
/**
* Helper method for dry-run testing.
* @dev calling this method, the EntryPoint will make a delegatecall to the given data, and report (via revert) the result.
* The method always revert, so is only useful off-chain for dry run calls, in cases where state-override to replace
* actual EntryPoint code is less convenient.
* @param target a target contract to make a delegatecall from entrypoint
* @param data data to pass to target in a delegatecall
*/
function delegateAndRevert(address target, bytes calldata data) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Minimal proxy library.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol)
/// @author Minimal proxy by 0age (https://github.com/0age)
/// @author Clones with immutable args by wighawag, zefram.eth, Saw-mon & Natalie
/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args)
/// @author Minimal ERC1967 proxy by jtriley-eth (https://github.com/jtriley-eth/minimum-viable-proxy)
///
/// @dev Minimal proxy:
/// Although the sw0nt pattern saves 5 gas over the ERC1167 pattern during runtime,
/// it is not supported out-of-the-box on Etherscan. Hence, we choose to use the 0age pattern,
/// which saves 4 gas over the ERC1167 pattern during runtime, and has the smallest bytecode.
/// - Automatically verified on Etherscan.
///
/// @dev Minimal proxy (PUSH0 variant):
/// This is a new minimal proxy that uses the PUSH0 opcode introduced during Shanghai.
/// It is optimized first for minimal runtime gas, then for minimal bytecode.
/// The PUSH0 clone functions are intentionally postfixed with a jarring "_PUSH0" as
/// many EVM chains may not support the PUSH0 opcode in the early months after Shanghai.
/// Please use with caution.
/// - Automatically verified on Etherscan.
///
/// @dev Clones with immutable args (CWIA):
/// The implementation of CWIA here is does NOT append the immutable args into the calldata
/// passed into delegatecall. It is simply an ERC1167 minimal proxy with the immutable arguments
/// appended to the back of the runtime bytecode.
/// - Uses the identity precompile (0x4) to copy args during deployment.
///
/// @dev Minimal ERC1967 proxy:
/// An minimal ERC1967 proxy, intended to be upgraded with UUPS.
/// This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic.
/// - Automatically verified on Etherscan.
///
/// @dev Minimal ERC1967 proxy with immutable args:
/// - Uses the identity precompile (0x4) to copy args during deployment.
/// - Automatically verified on Etherscan.
///
/// @dev ERC1967I proxy:
/// An variant of the minimal ERC1967 proxy, with a special code path that activates
/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the
/// `implementation` address. The returned implementation is guaranteed to be valid if the
/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`.
///
/// @dev ERC1967I proxy with immutable args:
/// An variant of the minimal ERC1967 proxy, with a special code path that activates
/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the
/// - Uses the identity precompile (0x4) to copy args during deployment.
///
/// @dev Minimal ERC1967 beacon proxy:
/// A minimal beacon proxy, intended to be upgraded with an upgradable beacon.
/// - Automatically verified on Etherscan.
///
/// @dev Minimal ERC1967 beacon proxy with immutable args:
/// - Uses the identity precompile (0x4) to copy args during deployment.
/// - Automatically verified on Etherscan.
///
/// @dev ERC1967I beacon proxy:
/// An variant of the minimal ERC1967 beacon proxy, with a special code path that activates
/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the
/// `implementation` address. The returned implementation is guaranteed to be valid if the
/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`.
///
/// @dev ERC1967I proxy with immutable args:
/// An variant of the minimal ERC1967 beacon proxy, with a special code path that activates
/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the
/// - Uses the identity precompile (0x4) to copy args during deployment.
library LibClone {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The keccak256 of deployed code for the clone proxy,
/// with the implementation set to `address(0)`.
bytes32 internal constant CLONE_CODE_HASH =
0x48db2cfdb2853fce0b464f1f93a1996469459df3ab6c812106074c4106a1eb1f;
/// @dev The keccak256 of deployed code for the PUSH0 proxy,
/// with the implementation set to `address(0)`.
bytes32 internal constant PUSH0_CLONE_CODE_HASH =
0x67bc6bde1b84d66e267c718ba44cf3928a615d29885537955cb43d44b3e789dc;
/// @dev The keccak256 of deployed code for the ERC-1167 CWIA proxy,
/// with the implementation set to `address(0)`.
bytes32 internal constant CWIA_CODE_HASH =
0x3cf92464268225a4513da40a34d967354684c32cd0edd67b5f668dfe3550e940;
/// @dev The keccak256 of the deployed code for the ERC1967 proxy.
bytes32 internal constant ERC1967_CODE_HASH =
0xaaa52c8cc8a0e3fd27ce756cc6b4e70c51423e9b597b11f32d3e49f8b1fc890d;
/// @dev The keccak256 of the deployed code for the ERC1967I proxy.
bytes32 internal constant ERC1967I_CODE_HASH =
0xce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f7;
/// @dev The keccak256 of the deployed code for the ERC1967 beacon proxy.
bytes32 internal constant ERC1967_BEACON_PROXY_CODE_HASH =
0x14044459af17bc4f0f5aa2f658cb692add77d1302c29fe2aebab005eea9d1162;
/// @dev The keccak256 of the deployed code for the ERC1967 beacon proxy.
bytes32 internal constant ERC1967I_BEACON_PROXY_CODE_HASH =
0xf8c46d2793d5aa984eb827aeaba4b63aedcab80119212fce827309788735519a;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Unable to deploy the clone.
error DeploymentFailed();
/// @dev The salt must start with either the zero address or `by`.
error SaltDoesNotStartWith();
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MINIMAL PROXY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a clone of `implementation`.
function clone(address implementation) internal returns (address instance) {
instance = clone(0, implementation);
}
/// @dev Deploys a clone of `implementation`.
/// Deposits `value` ETH during deployment.
function clone(uint256 value, address implementation) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
/**
* --------------------------------------------------------------------------+
* CREATION (9 bytes) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* --------------------------------------------------------------------------|
* RUNTIME (44 bytes) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* |
* ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 3d | RETURNDATASIZE | 0 0 0 | |
* 3d | RETURNDATASIZE | 0 0 0 0 | |
* |
* ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 0 0 | |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | |
* 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | |
* 37 | CALLDATACOPY | 0 0 0 0 | [0..cds): calldata |
* |
* ::: delegate call to the implementation contract :::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata |
* 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata |
* 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata |
* f4 | DELEGATECALL | success 0 0 | [0..cds): calldata |
* |
* ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata |
* 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata |
* 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata |
* 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata |
* |
* 60 0x2a | PUSH1 0x2a | 0x2a success 0 rds | [0..rds): returndata |
* 57 | JUMPI | 0 rds | [0..rds): returndata |
* |
* ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* fd | REVERT | | [0..rds): returndata |
* |
* ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | 0 rds | [0..rds): returndata |
* f3 | RETURN | | [0..rds): returndata |
* --------------------------------------------------------------------------+
*/
mstore(0x21, 0x5af43d3d93803e602a57fd5bf3)
mstore(0x14, implementation)
mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73)
instance := create(value, 0x0c, 0x35)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Deploys a deterministic clone of `implementation` with `salt`.
function cloneDeterministic(address implementation, bytes32 salt)
internal
returns (address instance)
{
instance = cloneDeterministic(0, implementation, salt);
}
/// @dev Deploys a deterministic clone of `implementation` with `salt`.
/// Deposits `value` ETH during deployment.
function cloneDeterministic(uint256 value, address implementation, bytes32 salt)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x21, 0x5af43d3d93803e602a57fd5bf3)
mstore(0x14, implementation)
mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73)
instance := create2(value, 0x0c, 0x35, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the initialization code of the clone of `implementation`.
function initCode(address implementation) internal pure returns (bytes memory c) {
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
mstore(add(c, 0x40), 0x5af43d3d93803e602a57fd5bf30000000000000000000000)
mstore(add(c, 0x28), implementation)
mstore(add(c, 0x14), 0x602c3d8160093d39f33d3d3d3d363d3d37363d73)
mstore(c, 0x35) // Store the length.
mstore(0x40, add(c, 0x60)) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the clone of `implementation`.
function initCodeHash(address implementation) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x21, 0x5af43d3d93803e602a57fd5bf3)
mstore(0x14, implementation)
mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73)
hash := keccak256(0x0c, 0x35)
mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the address of the clone of `implementation`, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddress(address implementation, bytes32 salt, address deployer)
internal
pure
returns (address predicted)
{
bytes32 hash = initCodeHash(implementation);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MINIMAL PROXY OPERATIONS (PUSH0 VARIANT) */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a PUSH0 clone of `implementation`.
function clone_PUSH0(address implementation) internal returns (address instance) {
instance = clone_PUSH0(0, implementation);
}
/// @dev Deploys a PUSH0 clone of `implementation`.
/// Deposits `value` ETH during deployment.
function clone_PUSH0(uint256 value, address implementation)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
/**
* --------------------------------------------------------------------------+
* CREATION (9 bytes) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 5f | PUSH0 | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 5f | PUSH0 | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* --------------------------------------------------------------------------|
* RUNTIME (45 bytes) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* |
* ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: |
* 5f | PUSH0 | 0 | |
* 5f | PUSH0 | 0 0 | |
* |
* ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 | |
* 5f | PUSH0 | 0 cds 0 0 | |
* 5f | PUSH0 | 0 0 cds 0 0 | |
* 37 | CALLDATACOPY | 0 0 | [0..cds): calldata |
* |
* ::: delegate call to the implementation contract :::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 | [0..cds): calldata |
* 5f | PUSH0 | 0 cds 0 0 | [0..cds): calldata |
* 73 addr | PUSH20 addr | addr 0 cds 0 0 | [0..cds): calldata |
* 5a | GAS | gas addr 0 cds 0 0 | [0..cds): calldata |
* f4 | DELEGATECALL | success | [0..cds): calldata |
* |
* ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds success | [0..cds): calldata |
* 5f | PUSH0 | 0 rds success | [0..cds): calldata |
* 5f | PUSH0 | 0 0 rds success | [0..cds): calldata |
* 3e | RETURNDATACOPY | success | [0..rds): returndata |
* |
* 60 0x29 | PUSH1 0x29 | 0x29 success | [0..rds): returndata |
* 57 | JUMPI | | [0..rds): returndata |
* |
* ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds | [0..rds): returndata |
* 5f | PUSH0 | 0 rds | [0..rds): returndata |
* fd | REVERT | | [0..rds): returndata |
* |
* ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | | [0..rds): returndata |
* 3d | RETURNDATASIZE | rds | [0..rds): returndata |
* 5f | PUSH0 | 0 rds | [0..rds): returndata |
* f3 | RETURN | | [0..rds): returndata |
* --------------------------------------------------------------------------+
*/
mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16
mstore(0x14, implementation) // 20
mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9
instance := create(value, 0x0e, 0x36)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x24, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`.
function cloneDeterministic_PUSH0(address implementation, bytes32 salt)
internal
returns (address instance)
{
instance = cloneDeterministic_PUSH0(0, implementation, salt);
}
/// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`.
/// Deposits `value` ETH during deployment.
function cloneDeterministic_PUSH0(uint256 value, address implementation, bytes32 salt)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16
mstore(0x14, implementation) // 20
mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9
instance := create2(value, 0x0e, 0x36, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x24, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the initialization code of the PUSH0 clone of `implementation`.
function initCode_PUSH0(address implementation) internal pure returns (bytes memory c) {
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
mstore(add(c, 0x40), 0x5af43d5f5f3e6029573d5ffd5b3d5ff300000000000000000000) // 16
mstore(add(c, 0x26), implementation) // 20
mstore(add(c, 0x12), 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9
mstore(c, 0x36) // Store the length.
mstore(0x40, add(c, 0x60)) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the PUSH0 clone of `implementation`.
function initCodeHash_PUSH0(address implementation) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16
mstore(0x14, implementation) // 20
mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9
hash := keccak256(0x0e, 0x36)
mstore(0x24, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the address of the PUSH0 clone of `implementation`, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddress_PUSH0(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHash_PUSH0(implementation);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CLONES WITH IMMUTABLE ARGS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a clone of `implementation` with immutable arguments encoded in `args`.
function clone(address implementation, bytes memory args) internal returns (address instance) {
instance = clone(0, implementation, args);
}
/// @dev Deploys a clone of `implementation` with immutable arguments encoded in `args`.
/// Deposits `value` ETH during deployment.
function clone(uint256 value, address implementation, bytes memory args)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
/**
* ---------------------------------------------------------------------------+
* CREATION (10 bytes) |
* ---------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------|
* 61 runSize | PUSH2 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* ---------------------------------------------------------------------------|
* RUNTIME (45 bytes + extraLength) |
* ---------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------|
* |
* ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds | |
* 3d | RETURNDATASIZE | 0 cds | |
* 3d | RETURNDATASIZE | 0 0 cds | |
* 37 | CALLDATACOPY | | [0..cds): calldata |
* |
* ::: delegate call to the implementation contract ::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | 0 0 0 | [0..cds): calldata |
* 36 | CALLDATASIZE | cds 0 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata |
* 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata |
* 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata |
* f4 | DELEGATECALL | success 0 0 | [0..cds): calldata |
* |
* ::: copy return data to memory ::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds success 0 | [0..cds): calldata |
* 82 | DUP3 | 0 rds success 0 | [0..cds): calldata |
* 80 | DUP1 | 0 0 rds success 0 | [0..cds): calldata |
* 3e | RETURNDATACOPY | success 0 | [0..rds): returndata |
* 90 | SWAP1 | 0 success | [0..rds): returndata |
* 3d | RETURNDATASIZE | rds 0 success | [0..rds): returndata |
* 91 | SWAP2 | success 0 rds | [0..rds): returndata |
* |
* 60 0x2b | PUSH1 0x2b | 0x2b success 0 rds | [0..rds): returndata |
* 57 | JUMPI | 0 rds | [0..rds): returndata |
* |
* ::: revert ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* fd | REVERT | | [0..rds): returndata |
* |
* ::: return ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | 0 rds | [0..rds): returndata |
* f3 | RETURN | | [0..rds): returndata |
* ---------------------------------------------------------------------------+
*/
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n))
mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3)
mstore(add(m, 0x14), implementation)
mstore(m, add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(0x88, n)))
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`.
instance := create(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37))
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
// 0,0x5e17b14ADd6c386305A32928F985b29bbA34Eff5, hex"01020304"
/// @dev Deploys a deterministic clone of `implementation`
/// with immutable arguments encoded in `args` and `salt`.
function cloneDeterministic(address implementation, bytes memory args, bytes32 salt)
internal
returns (address instance)
{
instance = cloneDeterministic(0, implementation, args, salt);
}
/// @dev Deploys a deterministic clone of `implementation`
/// with immutable arguments encoded in `args` and `salt`.
function cloneDeterministic(
uint256 value,
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n))
mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3)
mstore(add(m, 0x14), implementation)
mstore(m, add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(0x88, n)))
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`.
instance := create2(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns the initialization code hash of the clone of `implementation`
/// using immutable arguments encoded in `args`.
function initCode(address implementation, bytes memory args)
internal
pure
returns (bytes memory c)
{
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffd2))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(c, 0x57), i), mload(add(add(args, 0x20), i)))
}
mstore(add(c, 0x37), 0x5af43d82803e903d91602b57fd5bf3)
mstore(add(c, 0x28), implementation)
mstore(add(c, 0x14), add(0x61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(0x88, n)))
mstore(c, add(0x37, n)) // Store the length.
mstore(add(c, add(n, 0x57)), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(c, add(n, 0x77))) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the clone of `implementation`
/// using immutable arguments encoded in `args`.
function initCodeHash(address implementation, bytes memory args)
internal
pure
returns (bytes32 hash)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffd2))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(m, 0x43), i), mload(add(add(args, 0x20), i)))
}
mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3)
mstore(add(m, 0x14), implementation)
mstore(m, add(0x61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(0x88, n)))
hash := keccak256(add(m, 0x0c), add(n, 0x37))
}
}
/// @dev Returns the address of the clone of
/// `implementation` using immutable arguments encoded in `args`, with `salt`, by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddress(
address implementation,
bytes memory data,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHash(implementation, data);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/// @dev Equivalent to `argsOnClone(instance, 0, 2 ** 256 - 1)`.
function argsOnClone(address instance) internal view returns (bytes memory args) {
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
mstore(args, sub(extcodesize(instance), 0x2d)) // Store the length.
extcodecopy(instance, add(args, 0x20), 0x2d, add(mload(args), 0x20))
mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `argsOnClone(instance, start, 2 ** 256 - 1)`.
function argsOnClone(address instance, uint256 start)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
let n := sub(extcodesize(instance), 0x2d)
extcodecopy(instance, add(args, 0x20), add(start, 0x2d), add(n, 0x20))
mstore(args, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory.
}
}
/// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `instance` MUST be deployed via the clone with immutable args functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `instance` does not have any code.
function argsOnClone(address instance, uint256 start, uint256 end)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(instance, args, add(start, 0x0d), add(d, 0x20))
if iszero(and(0xff, mload(add(args, d)))) {
let n := sub(extcodesize(instance), 0x2d)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(args, d) // Store the length.
mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(args, 0x40), d)) // Allocate memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MINIMAL ERC1967 PROXY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: The ERC1967 proxy here is intended to be upgraded with UUPS.
// This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic.
/// @dev Deploys a minimal ERC1967 proxy with `implementation`.
function deployERC1967(address implementation) internal returns (address instance) {
instance = deployERC1967(0, implementation);
}
/// @dev Deploys a minimal ERC1967 proxy with `implementation`.
/// Deposits `value` ETH during deployment.
function deployERC1967(uint256 value, address implementation)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
/**
* ---------------------------------------------------------------------------------+
* CREATION (34 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code |
* 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code |
* 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code |
* 55 | SSTORE | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* ---------------------------------------------------------------------------------|
* RUNTIME (61 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* |
* ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds | |
* 3d | RETURNDATASIZE | 0 cds | |
* 3d | RETURNDATASIZE | 0 0 cds | |
* 37 | CALLDATACOPY | | [0..calldatasize): calldata |
* |
* ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata |
* 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata |
* 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata |
* 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata |
* f4 | DELEGATECALL | succ | [0..calldatasize): calldata |
* |
* ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata |
* 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata |
* 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata |
* 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata |
* |
* ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: |
* 60 0x38 | PUSH1 0x38 | dest succ | [0..returndatasize): returndata |
* 57 | JUMPI | | [0..returndatasize): returndata |
* |
* ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata |
* 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata |
* fd | REVERT | | [0..returndatasize): returndata |
* |
* ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | | [0..returndatasize): returndata |
* 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata |
* 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata |
* f3 | RETURN | | [0..returndatasize): returndata |
* ---------------------------------------------------------------------------------+
*/
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x20, 0x6009)
mstore(0x1e, implementation)
mstore(0x0a, 0x603d3d8160223d3973)
instance := create(value, 0x21, 0x5f)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`.
function deployDeterministicERC1967(address implementation, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967(0, implementation, salt);
}
/// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967(uint256 value, address implementation, bytes32 salt)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x20, 0x6009)
mstore(0x1e, implementation)
mstore(0x0a, 0x603d3d8160223d3973)
instance := create2(value, 0x21, 0x5f, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967(address implementation, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967(0, implementation, salt);
}
/// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967(uint256 value, address implementation, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x20, 0x6009)
mstore(0x1e, implementation)
mstore(0x0a, 0x603d3d8160223d3973)
// Compute and store the bytecode hash.
mstore(add(m, 0x35), keccak256(0x21, 0x5f))
mstore(m, shl(88, address()))
mstore8(m, 0xff) // Write the prefix.
mstore(add(m, 0x15), salt)
instance := keccak256(m, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, 0x21, 0x5f, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation`.
function initCodeERC1967(address implementation) internal pure returns (bytes memory c) {
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
mstore(add(c, 0x60), 0x3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f300)
mstore(add(c, 0x40), 0x55f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc)
mstore(add(c, 0x20), or(shl(24, implementation), 0x600951))
mstore(add(c, 0x09), 0x603d3d8160223d3973)
mstore(c, 0x5f) // Store the length.
mstore(0x40, add(c, 0x80)) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation`.
function initCodeHashERC1967(address implementation) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x20, 0x6009)
mstore(0x1e, implementation)
mstore(0x0a, 0x603d3d8160223d3973)
hash := keccak256(0x21, 0x5f)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the address of the ERC1967 proxy of `implementation`, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967(implementation);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MINIMAL ERC1967 PROXY WITH IMMUTABLE ARGS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a minimal ERC1967 proxy with `implementation` and `args`.
function deployERC1967(address implementation, bytes memory args)
internal
returns (address instance)
{
instance = deployERC1967(0, implementation, args);
}
/// @dev Deploys a minimal ERC1967 proxy with `implementation` and `args`.
/// Deposits `value` ETH during deployment.
function deployERC1967(uint256 value, address implementation, bytes memory args)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n))
mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x16, 0x6009)
mstore(0x14, implementation)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`.
mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n)))
mstore(m, mload(0x16))
instance := create(value, m, add(n, 0x60))
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`.
function deployDeterministicERC1967(address implementation, bytes memory args, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967(0, implementation, args, salt);
}
/// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967(
uint256 value,
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n))
mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x16, 0x6009)
mstore(0x14, implementation)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`.
mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n)))
mstore(m, mload(0x16))
instance := create2(value, m, add(n, 0x60), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Creates a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967(address implementation, bytes memory args, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967(0, implementation, args, salt);
}
/// @dev Creates a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967(
uint256 value,
address implementation,
bytes memory args,
bytes32 salt
) internal returns (bool alreadyDeployed, address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n))
mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x16, 0x6009)
mstore(0x14, implementation)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`.
mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n)))
mstore(m, mload(0x16))
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, keccak256(m, add(n, 0x60)))
mstore(0x01, shl(96, address()))
mstore(0x15, salt)
instance := keccak256(0x00, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, m, add(n, 0x60), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x35, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation` and `args`.
function initCodeERC1967(address implementation, bytes memory args)
internal
pure
returns (bytes memory c)
{
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffc2))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(c, 0x80), i), mload(add(add(args, 0x20), i)))
}
mstore(add(c, 0x60), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(add(c, 0x40), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(add(c, 0x20), 0x6009)
mstore(add(c, 0x1e), implementation)
mstore(add(c, 0x0a), add(0x61003d3d8160233d3973, shl(56, n)))
mstore(c, add(n, 0x60)) // Store the length.
mstore(add(c, add(n, 0x80)), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(c, add(n, 0xa0))) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation` and `args`.
function initCodeHashERC1967(address implementation, bytes memory args)
internal
pure
returns (bytes32 hash)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffc2))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(m, 0x60), i), mload(add(add(args, 0x20), i)))
}
mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076)
mstore(0x16, 0x6009)
mstore(0x14, implementation)
mstore(0x00, add(0x61003d3d8160233d3973, shl(56, n)))
mstore(m, mload(0x16))
hash := keccak256(m, add(n, 0x60))
}
}
/// @dev Returns the address of the ERC1967 proxy of `implementation`, `args`, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967(implementation, args);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/// @dev Equivalent to `argsOnERC1967(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967(address instance) internal view returns (bytes memory args) {
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
mstore(args, sub(extcodesize(instance), 0x3d)) // Store the length.
extcodecopy(instance, add(args, 0x20), 0x3d, add(mload(args), 0x20))
mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `argsOnERC1967(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967(address instance, uint256 start)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
let n := sub(extcodesize(instance), 0x3d)
extcodecopy(instance, add(args, 0x20), add(start, 0x3d), add(n, 0x20))
mstore(args, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory.
}
}
/// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `instance` MUST be deployed via the ERC1967 with immutable args functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `instance` does not have any code.
function argsOnERC1967(address instance, uint256 start, uint256 end)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(instance, args, add(start, 0x1d), add(d, 0x20))
if iszero(and(0xff, mload(add(args, d)))) {
let n := sub(extcodesize(instance), 0x3d)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(args, d) // Store the length.
mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(args, 0x40), d)) // Allocate memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1967I PROXY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: This proxy has a special code path that activates if `calldatasize() == 1`.
// This code path skips the delegatecall and directly returns the `implementation` address.
// The returned implementation is guaranteed to be valid if the keccak256 of the
// proxy's code is equal to `ERC1967I_CODE_HASH`.
/// @dev Deploys a ERC1967I proxy with `implementation`.
function deployERC1967I(address implementation) internal returns (address instance) {
instance = deployERC1967I(0, implementation);
}
/// @dev Deploys a ERC1967I proxy with `implementation`.
/// Deposits `value` ETH during deployment.
function deployERC1967I(uint256 value, address implementation)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
/**
* ---------------------------------------------------------------------------------+
* CREATION (34 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code |
* 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code |
* 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code |
* 55 | SSTORE | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* ---------------------------------------------------------------------------------|
* RUNTIME (82 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* |
* ::: check calldatasize ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds | |
* 58 | PC | 1 cds | |
* 14 | EQ | eqs | |
* 60 0x43 | PUSH1 0x43 | dest eqs | |
* 57 | JUMPI | | |
* |
* ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds | |
* 3d | RETURNDATASIZE | 0 cds | |
* 3d | RETURNDATASIZE | 0 0 cds | |
* 37 | CALLDATACOPY | | [0..calldatasize): calldata |
* |
* ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata |
* 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata |
* 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata |
* 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata |
* f4 | DELEGATECALL | succ | [0..calldatasize): calldata |
* |
* ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata |
* 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata |
* 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata |
* 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata |
* |
* ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: |
* 60 0x3E | PUSH1 0x3E | dest succ | [0..returndatasize): returndata |
* 57 | JUMPI | | [0..returndatasize): returndata |
* |
* ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata |
* 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata |
* fd | REVERT | | [0..returndatasize): returndata |
* |
* ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | | [0..returndatasize): returndata |
* 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata |
* 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata |
* f3 | RETURN | | [0..returndatasize): returndata |
* |
* ::: implementation , return :::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | | |
* 60 0x20 | PUSH1 0x20 | 32 | |
* 60 0x0F | PUSH1 0x0F | o 32 | |
* 3d | RETURNDATASIZE | 0 o 32 | |
* 39 | CODECOPY | | [0..32): implementation slot |
* 3d | RETURNDATASIZE | 0 | [0..32): implementation slot |
* 51 | MLOAD | slot | [0..32): implementation slot |
* 54 | SLOAD | impl | [0..32): implementation slot |
* 3d | RETURNDATASIZE | 0 impl | [0..32): implementation slot |
* 52 | MSTORE | | [0..32): implementation address |
* 59 | MSIZE | 32 | [0..32): implementation address |
* 3d | RETURNDATASIZE | 0 32 | [0..32): implementation address |
* f3 | RETURN | | [0..32): implementation address |
* ---------------------------------------------------------------------------------+
*/
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation))))
instance := create(value, 0x0c, 0x74)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`.
function deployDeterministicERC1967I(address implementation, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967I(0, implementation, salt);
}
/// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967I(uint256 value, address implementation, bytes32 salt)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation))))
instance := create2(value, 0x0c, 0x74, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967I(address implementation, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967I(0, implementation, salt);
}
/// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967I(uint256 value, address implementation, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation))))
// Compute and store the bytecode hash.
mstore(add(m, 0x35), keccak256(0x0c, 0x74))
mstore(m, shl(88, address()))
mstore8(m, 0xff) // Write the prefix.
mstore(add(m, 0x15), salt)
instance := keccak256(m, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, 0x0c, 0x74, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the initialization code of the ERC1967I proxy of `implementation`.
function initCodeERC1967I(address implementation) internal pure returns (bytes memory c) {
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
mstore(add(c, 0x74), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(add(c, 0x54), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(add(c, 0x34), 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(add(c, 0x1d), implementation)
mstore(add(c, 0x09), 0x60523d8160223d3973)
mstore(add(c, 0x94), 0)
mstore(c, 0x74) // Store the length.
mstore(0x40, add(c, 0xa0)) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the ERC1967I proxy of `implementation`.
function initCodeHashERC1967I(address implementation) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation))))
hash := keccak256(0x0c, 0x74)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the address of the ERC1967I proxy of `implementation`, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967I(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967I(implementation);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1967I PROXY WITH IMMUTABLE ARGS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a minimal ERC1967I proxy with `implementation` and `args`.
function deployERC1967I(address implementation, bytes memory args) internal returns (address) {
return deployERC1967I(0, implementation, args);
}
/// @dev Deploys a minimal ERC1967I proxy with `implementation` and `args`.
/// Deposits `value` ETH during deployment.
function deployERC1967I(uint256 value, address implementation, bytes memory args)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n))
mstore(add(m, 0x6b), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(add(m, 0x4b), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(add(m, 0x2b), 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(add(m, 0x14), implementation)
mstore(m, add(0xfe6100523d8160233d3973, shl(56, n)))
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
instance := create(value, add(m, add(0x15, lt(n, 0xffae))), add(0x75, n))
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Deploys a deterministic ERC1967I proxy with `implementation`, `args`, and `salt`.
function deployDeterministicERC1967I(address implementation, bytes memory args, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967I(0, implementation, args, salt);
}
/// @dev Deploys a deterministic ERC1967I proxy with `implementation`,`args`, and `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967I(
uint256 value,
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n))
mstore(add(m, 0x6b), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(add(m, 0x4b), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(add(m, 0x2b), 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(add(m, 0x14), implementation)
mstore(m, add(0xfe6100523d8160233d3973, shl(56, n)))
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
instance := create2(value, add(m, add(0x15, lt(n, 0xffae))), add(0x75, n), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Creates a deterministic ERC1967I proxy with `implementation`, `args` and `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967I(address implementation, bytes memory args, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967I(0, implementation, args, salt);
}
/// @dev Creates a deterministic ERC1967I proxy with `implementation`,`args` and `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967I(
uint256 value,
address implementation,
bytes memory args,
bytes32 salt
) internal returns (bool alreadyDeployed, address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x75), n))
mstore(add(m, 0x55), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(add(m, 0x35), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(add(m, 0x15), 0x5155f3365814604357363d3d373d3d363d7f360894)
mstore(0x16, 0x600f)
mstore(0x14, implementation)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
mstore(gt(n, 0xffad), add(0xfe6100523d8160233d3973, shl(56, n)))
mstore(m, mload(0x16))
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, keccak256(m, add(n, 0x75)))
mstore(0x01, shl(96, address()))
mstore(0x15, salt)
instance := keccak256(0x00, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, m, add(0x75, n), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x35, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the initialization code of the ERC1967I proxy of `implementation`and `args`.
function initCodeERC1967I(address implementation, bytes memory args)
internal
pure
returns (bytes memory c)
{
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(c, 0x95), i), mload(add(add(args, 0x20), i)))
}
mstore(add(c, 0x75), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(add(c, 0x55), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(add(c, 0x35), 0x600f5155f3365814604357363d3d373d3d363d7f360894)
mstore(add(c, 0x1e), implementation)
mstore(add(c, 0x0a), add(0x6100523d8160233d3973, shl(56, n)))
mstore(add(c, add(n, 0x95)), 0)
mstore(c, add(0x75, n)) // Store the length.
mstore(0x40, add(c, add(n, 0xb5))) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the ERC1967I proxy of `implementation` and `args.
function initCodeHashERC1967I(address implementation, bytes memory args)
internal
pure
returns (bytes32 hash)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(m, 0x75), i), mload(add(add(args, 0x20), i)))
}
mstore(add(m, 0x55), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3)
mstore(add(m, 0x35), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4)
mstore(add(m, 0x15), 0x5155f3365814604357363d3d373d3d363d7f360894)
mstore(0x16, 0x600f)
mstore(0x14, implementation)
mstore(0x00, add(0x6100523d8160233d3973, shl(56, n)))
mstore(m, mload(0x16))
hash := keccak256(m, add(0x75, n))
}
}
/// @dev Returns the address of the ERC1967I proxy of `implementation`, 'args` with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967I(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967I(implementation, args);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/// @dev Equivalent to `argsOnERC1967I(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967I(address instance) internal view returns (bytes memory args) {
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
mstore(args, sub(extcodesize(instance), 0x52)) // Store the length.
extcodecopy(instance, add(args, 0x20), 0x52, add(mload(args), 0x20))
mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `argsOnERC1967I(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967I(address instance, uint256 start)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
let n := sub(extcodesize(instance), 0x52)
extcodecopy(instance, add(args, 0x20), add(start, 0x52), add(n, 0x20))
mstore(args, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory.
}
}
/// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `instance` MUST be deployed via the ERC1967 with immutable args functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `instance` does not have any code.
function argsOnERC1967I(address instance, uint256 start, uint256 end)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(instance, args, add(start, 0x32), add(d, 0x20))
if iszero(and(0xff, mload(add(args, d)))) {
let n := sub(extcodesize(instance), 0x52)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(args, d) // Store the length.
mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(args, 0x40), d)) // Allocate memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANT ERC1967 BOOTSTRAP OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: This enables an ERC1967 proxy to be deployed at a deterministic address
// independent of the implementation:
// ```
// address bootstrap = LibClone.constantERC1967Bootstrap();
// address instance = LibClone.deployDeterministicERC1967(0, bootstrap, salt);
// LibClone.bootstrapConstantERC1967(bootstrap, implementation);
// ```
/// @dev Deploys the constant ERC1967 bootstrap if it has not been deployed.
function constantERC1967Bootstrap() internal returns (address bootstrap) {
bootstrap = constantERC1967BootstrapAddress();
/// @solidity memory-safe-assembly
assembly {
if iszero(extcodesize(bootstrap)) {
mstore(0x20, 0x0894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55)
mstore(0x00, 0x60258060093d393df358357f36)
if iszero(create2(0, 0x13, 0x2e, 0)) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Returns the implementation address of the ERC1967 bootstrap for this contract.
function constantERC1967BootstrapAddress() internal view returns (address bootstrap) {
bytes32 hash = 0xfe1a42b9c571a6a8c083c94ac67b9cfd74e2582923426aa3b762e3431d717cd1;
bootstrap = predictDeterministicAddress(hash, bytes32(0), address(this));
}
/// @dev Replaces the implementation at `instance`.
function bootstrapERC1967(address instance, address implementation) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, shr(96, shl(96, implementation)))
if iszero(call(gas(), instance, 0, 0x00, 0x20, codesize(), 0x00)) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MINIMAL ERC1967 BEACON PROXY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: If you use this proxy, you MUST make sure that the beacon is a
// valid ERC1967 beacon. This means that the beacon must always return a valid
// address upon a staticcall to `implementation()`, given sufficient gas.
// For performance, the deployment operations and the proxy assumes that the
// beacon is always valid and will NOT validate it.
/// @dev Deploys a minimal ERC1967 beacon proxy.
function deployERC1967BeaconProxy(address beacon) internal returns (address instance) {
instance = deployERC1967BeaconProxy(0, beacon);
}
/// @dev Deploys a minimal ERC1967 beacon proxy.
/// Deposits `value` ETH during deployment.
function deployERC1967BeaconProxy(uint256 value, address beacon)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
/**
* ---------------------------------------------------------------------------------+
* CREATION (34 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* 73 beac | PUSH20 beac | beac 0 r | [0..runSize): runtime code |
* 60 slotPos | PUSH1 slotPos | slotPos beac 0 r | [0..runSize): runtime code |
* 51 | MLOAD | slot beac 0 r | [0..runSize): runtime code |
* 55 | SSTORE | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* ---------------------------------------------------------------------------------|
* RUNTIME (82 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* |
* ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds | |
* 3d | RETURNDATASIZE | 0 cds | |
* 3d | RETURNDATASIZE | 0 0 cds | |
* 37 | CALLDATACOPY | | [0..calldatasize): calldata |
* |
* ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata |
* |
* ~~~~~~~ beacon staticcall sub procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
* 60 0x20 | PUSH1 0x20 | 32 | |
* 36 | CALLDATASIZE | cds 32 | |
* 60 0x04 | PUSH1 0x04 | 4 cds 32 | |
* 36 | CALLDATASIZE | cds 4 cds 32 | |
* 63 0x5c60da1b | PUSH4 0x5c60da1b | 0x5c60da1b cds 4 cds 32 | |
* 60 0xe0 | PUSH1 0xe0 | 224 0x5c60da1b cds 4 cds 32 | |
* 1b | SHL | sel cds 4 cds 32 | |
* 36 | CALLDATASIZE | cds sel cds 4 cds 32 | |
* 52 | MSTORE | cds 4 cds 32 | sel |
* 7f slot | PUSH32 slot | s cds 4 cds 32 | sel |
* 54 | SLOAD | beac cds 4 cds 32 | sel |
* 5a | GAS | g beac cds 4 cds 32 | sel |
* fa | STATICCALL | succ | impl |
* 50 | POP | | impl |
* 36 | CALLDATASIZE | cds | impl |
* 51 | MLOAD | impl | impl |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
* 5a | GAS | g impl 0 cds 0 0 | [0..calldatasize): calldata |
* f4 | DELEGATECALL | succ | [0..calldatasize): calldata |
* |
* ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata |
* 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata |
* 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata |
* 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata |
* |
* ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: |
* 60 0x4d | PUSH1 0x4d | dest succ | [0..returndatasize): returndata |
* 57 | JUMPI | | [0..returndatasize): returndata |
* |
* ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata |
* 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata |
* fd | REVERT | | [0..returndatasize): returndata |
* |
* ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | | [0..returndatasize): returndata |
* 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata |
* 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata |
* f3 | RETURN | | [0..returndatasize): returndata |
* ---------------------------------------------------------------------------------+
*/
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon))))
instance := create(value, 0x0c, 0x74)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `salt`.
function deployDeterministicERC1967BeaconProxy(address beacon, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967BeaconProxy(0, beacon, salt);
}
/// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967BeaconProxy(uint256 value, address beacon, bytes32 salt)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon))))
instance := create2(value, 0x0c, 0x74, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Creates a deterministic minimal ERC1967 beacon proxy with `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967BeaconProxy(address beacon, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967BeaconProxy(0, beacon, salt);
}
/// @dev Creates a deterministic minimal ERC1967 beacon proxy with `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967BeaconProxy(uint256 value, address beacon, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon))))
// Compute and store the bytecode hash.
mstore(add(m, 0x35), keccak256(0x0c, 0x74))
mstore(m, shl(88, address()))
mstore8(m, 0xff) // Write the prefix.
mstore(add(m, 0x15), salt)
instance := keccak256(m, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, 0x0c, 0x74, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the initialization code of the minimal ERC1967 beacon proxy.
function initCodeERC1967BeaconProxy(address beacon) internal pure returns (bytes memory c) {
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
mstore(add(c, 0x74), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(add(c, 0x54), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(add(c, 0x34), 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(add(c, 0x1d), beacon)
mstore(add(c, 0x09), 0x60523d8160223d3973)
mstore(add(c, 0x94), 0)
mstore(c, 0x74) // Store the length.
mstore(0x40, add(c, 0xa0)) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the minimal ERC1967 beacon proxy.
function initCodeHashERC1967BeaconProxy(address beacon) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon))))
hash := keccak256(0x0c, 0x74)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the address of the ERC1967 beacon proxy, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967BeaconProxy(
address beacon,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967BeaconProxy(beacon);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1967 BEACON PROXY WITH IMMUTABLE ARGS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a minimal ERC1967 beacon proxy with `args`.
function deployERC1967BeaconProxy(address beacon, bytes memory args)
internal
returns (address instance)
{
instance = deployERC1967BeaconProxy(0, beacon, args);
}
/// @dev Deploys a minimal ERC1967 beacon proxy with `args`.
/// Deposits `value` ETH during deployment.
function deployERC1967BeaconProxy(uint256 value, address beacon, bytes memory args)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n))
mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(add(m, 0x14), beacon)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n)))
instance := create(value, add(m, 0x16), add(n, 0x75))
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `args` and `salt`.
function deployDeterministicERC1967BeaconProxy(address beacon, bytes memory args, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967BeaconProxy(0, beacon, args, salt);
}
/// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `args` and `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967BeaconProxy(
uint256 value,
address beacon,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n))
mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(add(m, 0x14), beacon)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n)))
instance := create2(value, add(m, 0x16), add(n, 0x75), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Creates a deterministic minimal ERC1967 beacon proxy with `args` and `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967BeaconProxy(address beacon, bytes memory args, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967BeaconProxy(0, beacon, args, salt);
}
/// @dev Creates a deterministic minimal ERC1967 beacon proxy with `args` and `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967BeaconProxy(
uint256 value,
address beacon,
bytes memory args,
bytes32 salt
) internal returns (bool alreadyDeployed, address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n))
mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(add(m, 0x14), beacon)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n)))
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, keccak256(add(m, 0x16), add(n, 0x75)))
mstore(0x01, shl(96, address()))
mstore(0x15, salt)
instance := keccak256(0x00, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, add(m, 0x16), add(n, 0x75), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x35, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the initialization code of the minimal ERC1967 beacon proxy.
function initCodeERC1967BeaconProxy(address beacon, bytes memory args)
internal
pure
returns (bytes memory c)
{
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(c, 0x95), i), mload(add(add(args, 0x20), i)))
}
mstore(add(c, 0x75), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(add(c, 0x55), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(add(c, 0x35), 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(add(c, 0x1e), beacon)
mstore(add(c, 0x0a), add(0x6100523d8160233d3973, shl(56, n)))
mstore(c, add(n, 0x75)) // Store the length.
mstore(add(c, add(n, 0x95)), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(c, add(n, 0xb5))) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the minimal ERC1967 beacon proxy with `args`.
function initCodeHashERC1967BeaconProxy(address beacon, bytes memory args)
internal
pure
returns (bytes32 hash)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(m, 0x8b), i), mload(add(add(args, 0x20), i)))
}
mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3)
mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c)
mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da)
mstore(add(m, 0x14), beacon)
mstore(m, add(0x6100523d8160233d3973, shl(56, n)))
hash := keccak256(add(m, 0x16), add(n, 0x75))
}
}
/// @dev Returns the address of the ERC1967 beacon proxy with `args`, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967BeaconProxy(
address beacon,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967BeaconProxy(beacon, args);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/// @dev Equivalent to `argsOnERC1967BeaconProxy(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967BeaconProxy(address instance) internal view returns (bytes memory args) {
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
mstore(args, sub(extcodesize(instance), 0x52)) // Store the length.
extcodecopy(instance, add(args, 0x20), 0x52, add(mload(args), 0x20))
mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `argsOnERC1967BeaconProxy(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967BeaconProxy(address instance, uint256 start)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
let n := sub(extcodesize(instance), 0x52)
extcodecopy(instance, add(args, 0x20), add(start, 0x52), add(n, 0x20))
mstore(args, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory.
}
}
/// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `instance` MUST be deployed via the ERC1967 beacon proxy with immutable args functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `instance` does not have any code.
function argsOnERC1967BeaconProxy(address instance, uint256 start, uint256 end)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(instance, args, add(start, 0x32), add(d, 0x20))
if iszero(and(0xff, mload(add(args, d)))) {
let n := sub(extcodesize(instance), 0x52)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(args, d) // Store the length.
mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(args, 0x40), d)) // Allocate memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1967I BEACON PROXY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: This proxy has a special code path that activates if `calldatasize() == 1`.
// This code path skips the delegatecall and directly returns the `implementation` address.
// The returned implementation is guaranteed to be valid if the keccak256 of the
// proxy's code is equal to `ERC1967_BEACON_PROXY_CODE_HASH`.
//
// If you use this proxy, you MUST make sure that the beacon is a
// valid ERC1967 beacon. This means that the beacon must always return a valid
// address upon a staticcall to `implementation()`, given sufficient gas.
// For performance, the deployment operations and the proxy assumes that the
// beacon is always valid and will NOT validate it.
/// @dev Deploys a ERC1967I beacon proxy.
function deployERC1967IBeaconProxy(address beacon) internal returns (address instance) {
instance = deployERC1967IBeaconProxy(0, beacon);
}
/// @dev Deploys a ERC1967I beacon proxy.
/// Deposits `value` ETH during deployment.
function deployERC1967IBeaconProxy(uint256 value, address beacon)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
/**
* ---------------------------------------------------------------------------------+
* CREATION (34 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* 73 beac | PUSH20 beac | beac 0 r | [0..runSize): runtime code |
* 60 slotPos | PUSH1 slotPos | slotPos beac 0 r | [0..runSize): runtime code |
* 51 | MLOAD | slot beac 0 r | [0..runSize): runtime code |
* 55 | SSTORE | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* ---------------------------------------------------------------------------------|
* RUNTIME (87 bytes) |
* ---------------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------------------------------------|
* |
* ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds | |
* 3d | RETURNDATASIZE | 0 cds | |
* 3d | RETURNDATASIZE | 0 0 cds | |
* 37 | CALLDATACOPY | | [0..calldatasize): calldata |
* |
* ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata |
* |
* ~~~~~~~ beacon staticcall sub procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
* 60 0x20 | PUSH1 0x20 | 32 | |
* 36 | CALLDATASIZE | cds 32 | |
* 60 0x04 | PUSH1 0x04 | 4 cds 32 | |
* 36 | CALLDATASIZE | cds 4 cds 32 | |
* 63 0x5c60da1b | PUSH4 0x5c60da1b | 0x5c60da1b cds 4 cds 32 | |
* 60 0xe0 | PUSH1 0xe0 | 224 0x5c60da1b cds 4 cds 32 | |
* 1b | SHL | sel cds 4 cds 32 | |
* 36 | CALLDATASIZE | cds sel cds 4 cds 32 | |
* 52 | MSTORE | cds 4 cds 32 | sel |
* 7f slot | PUSH32 slot | s cds 4 cds 32 | sel |
* 54 | SLOAD | beac cds 4 cds 32 | sel |
* 5a | GAS | g beac cds 4 cds 32 | sel |
* fa | STATICCALL | succ | impl |
* ~~~~~~ check calldatasize ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
* 36 | CALLDATASIZE | cds succ | |
* 14 | EQ | | impl |
* 60 0x52 | PUSH1 0x52 | | impl |
* 57 | JUMPI | | impl |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
* 36 | CALLDATASIZE | cds | impl |
* 51 | MLOAD | impl | impl |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
* 5a | GAS | g impl 0 cds 0 0 | [0..calldatasize): calldata |
* f4 | DELEGATECALL | succ | [0..calldatasize): calldata |
* |
* ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata |
* 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata |
* 60 0x01 | PUSH1 0x01 | 1 0 rds succ | [0..calldatasize): calldata |
* 3e | RETURNDATACOPY | succ | [1..returndatasize): returndata |
* |
* ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: |
* 60 0x52 | PUSH1 0x52 | dest succ | [1..returndatasize): returndata |
* 57 | JUMPI | | [1..returndatasize): returndata |
* |
* ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds | [1..returndatasize): returndata |
* 60 0x01 | PUSH1 0x01 | 1 rds | [1..returndatasize): returndata |
* fd | REVERT | | [1..returndatasize): returndata |
* |
* ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | | [1..returndatasize): returndata |
* 3d | RETURNDATASIZE | rds | [1..returndatasize): returndata |
* 60 0x01 | PUSH1 0x01 | 1 rds | [1..returndatasize): returndata |
* f3 | RETURN | | [1..returndatasize): returndata |
* ---------------------------------------------------------------------------------+
*/
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon))))
instance := create(value, 0x07, 0x79)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Deploys a deterministic ERC1967I beacon proxy with `salt`.
function deployDeterministicERC1967IBeaconProxy(address beacon, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967IBeaconProxy(0, beacon, salt);
}
/// @dev Deploys a deterministic ERC1967I beacon proxy with `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967IBeaconProxy(uint256 value, address beacon, bytes32 salt)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon))))
instance := create2(value, 0x07, 0x79, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Creates a deterministic ERC1967I beacon proxy with `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967IBeaconProxy(address beacon, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967IBeaconProxy(0, beacon, salt);
}
/// @dev Creates a deterministic ERC1967I beacon proxy with `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967IBeaconProxy(uint256 value, address beacon, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon))))
// Compute and store the bytecode hash.
mstore(add(m, 0x35), keccak256(0x07, 0x79))
mstore(m, shl(88, address()))
mstore8(m, 0xff) // Write the prefix.
mstore(add(m, 0x15), salt)
instance := keccak256(m, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, 0x07, 0x79, salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the initialization code of the ERC1967I beacon proxy.
function initCodeERC1967IBeaconProxy(address beacon) internal pure returns (bytes memory c) {
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
mstore(add(c, 0x79), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(add(c, 0x59), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(add(c, 0x39), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(add(c, 0x1d), beacon)
mstore(add(c, 0x09), 0x60573d8160223d3973)
mstore(add(c, 0x99), 0)
mstore(c, 0x79) // Store the length.
mstore(0x40, add(c, 0xa0)) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the ERC1967I beacon proxy.
function initCodeHashERC1967IBeaconProxy(address beacon) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon))))
hash := keccak256(0x07, 0x79)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero slot.
}
}
/// @dev Returns the address of the ERC1967I beacon proxy, with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967IBeaconProxy(
address beacon,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967IBeaconProxy(beacon);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1967I BEACON PROXY WITH IMMUTABLE ARGS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Deploys a ERC1967I beacon proxy with `args.
function deployERC1967IBeaconProxy(address beacon, bytes memory args)
internal
returns (address instance)
{
instance = deployERC1967IBeaconProxy(0, beacon, args);
}
/// @dev Deploys a ERC1967I beacon proxy with `args.
/// Deposits `value` ETH during deployment.
function deployERC1967IBeaconProxy(uint256 value, address beacon, bytes memory args)
internal
returns (address instance)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n))
mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(add(m, 0x14), beacon)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`.
mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n)))
instance := create(value, add(m, 0x16), add(n, 0x7a))
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Deploys a deterministic ERC1967I beacon proxy with `args` and `salt`.
function deployDeterministicERC1967IBeaconProxy(address beacon, bytes memory args, bytes32 salt)
internal
returns (address instance)
{
instance = deployDeterministicERC1967IBeaconProxy(0, beacon, args, salt);
}
/// @dev Deploys a deterministic ERC1967I beacon proxy with `args` and `salt`.
/// Deposits `value` ETH during deployment.
function deployDeterministicERC1967IBeaconProxy(
uint256 value,
address beacon,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n))
mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(add(m, 0x14), beacon)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`.
mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n)))
instance := create2(value, add(m, 0x16), add(n, 0x7a), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Creates a deterministic ERC1967I beacon proxy with `args` and `salt`.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967IBeaconProxy(address beacon, bytes memory args, bytes32 salt)
internal
returns (bool alreadyDeployed, address instance)
{
return createDeterministicERC1967IBeaconProxy(0, beacon, args, salt);
}
/// @dev Creates a deterministic ERC1967I beacon proxy with `args` and `salt`.
/// Deposits `value` ETH during deployment.
/// Note: This method is intended for use in ERC4337 factories,
/// which are expected to NOT revert if the proxy is already deployed.
function createDeterministicERC1967IBeaconProxy(
uint256 value,
address beacon,
bytes memory args,
bytes32 salt
) internal returns (bool alreadyDeployed, address instance) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let n := mload(args)
pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n))
mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(add(m, 0x14), beacon)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`.
mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n)))
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, keccak256(add(m, 0x16), add(n, 0x7a)))
mstore(0x01, shl(96, address()))
mstore(0x15, salt)
instance := keccak256(0x00, 0x55)
for {} 1 {} {
if iszero(extcodesize(instance)) {
instance := create2(value, add(m, 0x16), add(n, 0x7a), salt)
if iszero(instance) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
break
}
alreadyDeployed := 1
if iszero(value) { break }
if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
break
}
mstore(0x35, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the initialization code of the ERC1967I beacon proxy with `args`.
function initCodeERC1967IBeaconProxy(address beacon, bytes memory args)
internal
pure
returns (bytes memory c)
{
/// @solidity memory-safe-assembly
assembly {
c := mload(0x40)
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffa8))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(c, 0x9a), i), mload(add(add(args, 0x20), i)))
}
mstore(add(c, 0x7a), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(add(c, 0x5a), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(add(c, 0x3a), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(add(c, 0x1e), beacon)
mstore(add(c, 0x0a), add(0x6100573d8160233d3973, shl(56, n)))
mstore(add(c, add(n, 0x9a)), 0)
mstore(c, add(n, 0x7a)) // Store the length.
mstore(0x40, add(c, add(n, 0xba))) // Allocate memory.
}
}
/// @dev Returns the initialization code hash of the ERC1967I beacon proxy with `args`.
function initCodeHashERC1967IBeaconProxy(address beacon, bytes memory args)
internal
pure
returns (bytes32 hash)
{
/// @solidity memory-safe-assembly
assembly {
let c := mload(0x40) // Cache the free memory pointer.
let n := mload(args)
// Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffa8))
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(add(add(c, 0x90), i), mload(add(add(args, 0x20), i)))
}
mstore(add(c, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3)
mstore(add(c, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513)
mstore(add(c, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36)
mstore(add(c, 0x14), beacon)
mstore(c, add(0x6100573d8160233d3973, shl(56, n)))
hash := keccak256(add(c, 0x16), add(n, 0x7a))
}
}
/// @dev Returns the address of the ERC1967I beacon proxy, with `args` and salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddressERC1967IBeaconProxy(
address beacon,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes32 hash = initCodeHashERC1967IBeaconProxy(beacon, args);
predicted = predictDeterministicAddress(hash, salt, deployer);
}
/// @dev Equivalent to `argsOnERC1967IBeaconProxy(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967IBeaconProxy(address instance)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
mstore(args, sub(extcodesize(instance), 0x57)) // Store the length.
extcodecopy(instance, add(args, 0x20), 0x57, add(mload(args), 0x20))
mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `argsOnERC1967IBeaconProxy(instance, start, 2 ** 256 - 1)`.
function argsOnERC1967IBeaconProxy(address instance, uint256 start)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
let n := sub(extcodesize(instance), 0x57)
extcodecopy(instance, add(args, 0x20), add(start, 0x57), add(n, 0x20))
mstore(args, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory.
}
}
/// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `instance` MUST be deployed via the ERC1967I beacon proxy with immutable args functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `instance` does not have any code.
function argsOnERC1967IBeaconProxy(address instance, uint256 start, uint256 end)
internal
view
returns (bytes memory args)
{
/// @solidity memory-safe-assembly
assembly {
args := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(instance, args, add(start, 0x37), add(d, 0x20))
if iszero(and(0xff, mload(add(args, d)))) {
let n := sub(extcodesize(instance), 0x57)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(args, d) // Store the length.
mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(args, 0x40), d)) // Allocate memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OTHER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `address(0)` if the implementation address cannot be determined.
function implementationOf(address instance) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
for { extcodecopy(instance, 0x00, 0x00, 0x57) } 1 {} {
if mload(0x2d) {
// ERC1967I and ERC1967IBeaconProxy detection.
if or(
eq(keccak256(0x00, 0x52), ERC1967I_CODE_HASH),
eq(keccak256(0x00, 0x57), ERC1967I_BEACON_PROXY_CODE_HASH)
) {
pop(staticcall(gas(), instance, 0x00, 0x01, 0x00, 0x20))
result := mload(0x0c)
break
}
}
// 0age clone detection.
result := mload(0x0b)
codecopy(0x0b, codesize(), 0x14) // Zeroize the 20 bytes for the address.
if iszero(xor(keccak256(0x00, 0x2c), CLONE_CODE_HASH)) { break }
mstore(0x0b, result) // Restore the zeroized memory.
// CWIA detection.
result := mload(0x0a)
codecopy(0x0a, codesize(), 0x14) // Zeroize the 20 bytes for the address.
if iszero(xor(keccak256(0x00, 0x2d), CWIA_CODE_HASH)) { break }
mstore(0x0a, result) // Restore the zeroized memory.
// PUSH0 clone detection.
result := mload(0x09)
codecopy(0x09, codesize(), 0x14) // Zeroize the 20 bytes for the address.
result := shr(xor(keccak256(0x00, 0x2d), PUSH0_CLONE_CODE_HASH), result)
break
}
result := shr(96, result)
mstore(0x37, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Returns the address when a contract with initialization code hash,
/// `hash`, is deployed with `salt`, by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictDeterministicAddress(bytes32 hash, bytes32 salt, address deployer)
internal
pure
returns (address predicted)
{
/// @solidity memory-safe-assembly
assembly {
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, hash)
mstore(0x01, shl(96, deployer))
mstore(0x15, salt)
predicted := keccak256(0x00, 0x55)
mstore(0x35, 0) // Restore the overwritten part of the free memory pointer.
}
}
/// @dev Requires that `salt` starts with either the zero address or `by`.
function checkStartsWith(bytes32 salt, address by) internal pure {
/// @solidity memory-safe-assembly
assembly {
// If the salt does not start with the zero address or `by`.
if iszero(or(iszero(shr(96, salt)), eq(shr(96, shl(96, by)), shr(96, salt)))) {
mstore(0x00, 0x0c4549ef) // `SaltDoesNotStartWith()`.
revert(0x1c, 0x04)
}
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {
ExecutionManifest,
ManifestExecutionHook
} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol";
import {HookConfig, IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol";
import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol";
import {AccountStorage, ExecutionStorage, getAccountStorage, toSetValue} from "../account/AccountStorage.sol";
import {KnownSelectorsLib} from "../libraries/KnownSelectorsLib.sol";
import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol";
import {ModuleInstallCommonsLib} from "../libraries/ModuleInstallCommonsLib.sol";
/// @title Execution Install Delegate
/// @author Alchemy
/// @notice This contract acts as an external library which is meant to handle execution function installations and
/// uninstallations via delegatecall.
contract ExecutionInstallDelegate {
using LinkedListSetLib for LinkedListSet;
address internal immutable _THIS_ADDRESS;
error ERC4337FunctionNotAllowed(bytes4 selector);
error ExecutionFunctionAlreadySet(bytes4 selector);
error ExecutionFunctionNotSet(bytes4 selector);
error ExecutionHookNotSet(HookConfig hookConfig);
error IModuleFunctionNotAllowed(bytes4 selector);
error NullModule();
error OnlyDelegateCall();
modifier onlyDelegateCall() {
if (address(this) == _THIS_ADDRESS) {
revert OnlyDelegateCall();
}
_;
}
constructor() {
_THIS_ADDRESS = address(this);
}
// External Functions
/// @notice Update components according to the manifest.
function installExecution(
address module,
ExecutionManifest calldata manifest,
bytes calldata moduleInstallData
) external onlyDelegateCall {
AccountStorage storage _storage = getAccountStorage();
if (module == address(0)) {
revert NullModule();
}
// Update components according to the manifest.
uint256 length = manifest.executionFunctions.length;
for (uint256 i = 0; i < length; ++i) {
bytes4 selector = manifest.executionFunctions[i].executionSelector;
bool skipRuntimeValidation = manifest.executionFunctions[i].skipRuntimeValidation;
bool allowGlobalValidation = manifest.executionFunctions[i].allowGlobalValidation;
_setExecutionFunction(selector, skipRuntimeValidation, allowGlobalValidation, module);
}
length = manifest.executionHooks.length;
for (uint256 i = 0; i < length; ++i) {
ManifestExecutionHook memory mh = manifest.executionHooks[i];
LinkedListSet storage executionHooks = _storage.executionStorage[mh.executionSelector].executionHooks;
HookConfig hookConfig = HookConfigLib.packExecHook({
_module: module,
_entityId: mh.entityId,
_hasPre: mh.isPreHook,
_hasPost: mh.isPostHook
});
ModuleInstallCommonsLib.addExecHooks(executionHooks, hookConfig);
}
length = manifest.interfaceIds.length;
for (uint256 i = 0; i < length; ++i) {
_storage.supportedIfaces[manifest.interfaceIds[i]] += 1;
}
ModuleInstallCommonsLib.onInstall(module, moduleInstallData, type(IModule).interfaceId);
emit IModularAccount.ExecutionInstalled(module, manifest);
}
/// @notice Remove components according to the manifest, in reverse order (by component type) of their
/// installation.
function uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata uninstallData)
external
onlyDelegateCall
{
AccountStorage storage _storage = getAccountStorage();
if (module == address(0)) {
revert NullModule();
}
uint256 length = manifest.executionHooks.length;
for (uint256 i = 0; i < length; ++i) {
ManifestExecutionHook memory mh = manifest.executionHooks[i];
LinkedListSet storage executionHooks = _storage.executionStorage[mh.executionSelector].executionHooks;
HookConfig hookConfig = HookConfigLib.packExecHook({
_module: module,
_entityId: mh.entityId,
_hasPre: mh.isPreHook,
_hasPost: mh.isPostHook
});
_removeExecHooks(executionHooks, hookConfig);
}
length = manifest.executionFunctions.length;
for (uint256 i = 0; i < length; ++i) {
bytes4 selector = manifest.executionFunctions[i].executionSelector;
_removeExecutionFunction(selector);
}
length = manifest.interfaceIds.length;
for (uint256 i = 0; i < length; ++i) {
_storage.supportedIfaces[manifest.interfaceIds[i]] -= 1;
}
// Clear the module storage for the account.
bool onUninstallSuccess = ModuleInstallCommonsLib.onUninstall(module, uninstallData);
emit IModularAccount.ExecutionUninstalled(module, onUninstallSuccess, manifest);
}
// Private Functions
function _setExecutionFunction(
bytes4 selector,
bool skipRuntimeValidation,
bool allowGlobalValidation,
address module
) internal {
ExecutionStorage storage _executionStorage = getAccountStorage().executionStorage[selector];
if (_executionStorage.module != address(0)) {
revert ExecutionFunctionAlreadySet(selector);
}
// Note that there is no check for native function selectors. Installing a function with a colliding
// selector will lead to the installed function being unreachable.
// Make sure incoming execution function is not a function in IModule
if (KnownSelectorsLib.isIModuleFunction(uint32(selector))) {
revert IModuleFunctionNotAllowed(selector);
}
// Also make sure it doesn't collide with functions defined by ERC-4337 and called by the entry point. This
// prevents a malicious module from sneaking in a function with the same selector as e.g.
// `validatePaymasterUserOp` and turning the account into their own personal paymaster.
if (KnownSelectorsLib.isERC4337Function(uint32(selector))) {
revert ERC4337FunctionNotAllowed(selector);
}
_executionStorage.module = module;
_executionStorage.skipRuntimeValidation = skipRuntimeValidation;
_executionStorage.allowGlobalValidation = allowGlobalValidation;
}
function _removeExecutionFunction(bytes4 selector) internal {
ExecutionStorage storage _executionStorage = getAccountStorage().executionStorage[selector];
if (_executionStorage.module == address(0)) {
revert ExecutionFunctionNotSet(selector);
}
_executionStorage.module = address(0);
_executionStorage.skipRuntimeValidation = false;
_executionStorage.allowGlobalValidation = false;
}
function _removeExecHooks(LinkedListSet storage hooks, HookConfig hookConfig) internal {
if (!hooks.tryRemove(toSetValue(hookConfig))) {
revert ExecutionHookNotSet(hookConfig);
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {ModuleEntity, ValidationConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol";
import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol";
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {FALLBACK_VALIDATION_ID, FALLBACK_VALIDATION_LOOKUP_KEY} from "../helpers/Constants.sol";
import {ExecutionInstallDelegate} from "../helpers/ExecutionInstallDelegate.sol";
import {SignatureType} from "../helpers/SignatureType.sol";
import {RTCallBuffer, SigCallBuffer, UOCallBuffer} from "../libraries/ExecutionLib.sol";
import {ValidationLocatorLib, ValidationLookupKey} from "../libraries/ValidationLocatorLib.sol";
import {ModularAccountBase} from "./ModularAccountBase.sol";
/// @title Semi-Modular Account Base
/// @author Alchemy
/// @notice Abstract base contract for the Alchemy Semi-Modular Account variants. Includes fallback signer
/// functionality.
/// @dev Inherits ModularAccountBase. Overrides certain functionality from ModularAccountBase, and exposes an
/// internal virtual getter for the fallback signer.
abstract contract SemiModularAccountBase is ModularAccountBase {
using MessageHashUtils for bytes32;
using ModuleEntityLib for ModuleEntity;
using ValidationConfigLib for ValidationConfig;
struct SemiModularAccountStorage {
address fallbackSigner;
bool fallbackSignerDisabled;
}
// keccak256("ReplaySafeHash(bytes32 hash)")
bytes32 private constant _REPLAY_SAFE_HASH_TYPEHASH =
0x294a8735843d4afb4f017c76faf3b7731def145ed0025fc9b1d5ce30adf113ff;
// keccak256("ERC6900.SemiModularAccount.Storage")
uint256 internal constant _SEMI_MODULAR_ACCOUNT_STORAGE_SLOT =
0x5b9dc9aa943f8fa2653ceceda5e3798f0686455280432166ba472eca0bc17a32;
uint256 internal constant _SIG_VALIDATION_PASSED = 0;
uint256 internal constant _SIG_VALIDATION_FAILED = 1;
event FallbackSignerUpdated(address indexed newFallbackSigner, bool isDisabled);
error FallbackSignerMismatch();
error FallbackValidationInstallationNotAllowed();
error FallbackSignerDisabled();
error InvalidSignatureType();
constructor(IEntryPoint entryPoint, ExecutionInstallDelegate executionInstallDelegate)
ModularAccountBase(entryPoint, executionInstallDelegate)
{}
/// @notice Updates the fallback signer data in storage.
/// @param fallbackSigner The new signer to set.
/// @param isDisabled Whether to disable fallback signing entirely.
function updateFallbackSignerData(address fallbackSigner, bool isDisabled) external wrapNativeFunction {
SemiModularAccountStorage storage _storage = _getSemiModularAccountStorage();
_storage.fallbackSigner = fallbackSigner;
_storage.fallbackSignerDisabled = isDisabled;
emit FallbackSignerUpdated(fallbackSigner, isDisabled);
}
function installValidation(
ValidationConfig validationConfig,
bytes4[] calldata selectors,
bytes calldata installData,
bytes[] calldata hooks
) external override wrapNativeFunction {
// Previously, it was possible to "alias" the fallback validation by installing a module at the reserved
// validation entity id 0. Not failing here could cause unexpected behavior, so this is checked to
// explicitly revert and warn the caller that this operation would not do what is requested.
//
// Note that this state can still be reached by upgrading from MA to SMA, but should be handled with
// initialization and de-init steps.
if (validationConfig.entityId() == FALLBACK_VALIDATION_ID && validationConfig.module() != address(0)) {
revert FallbackValidationInstallationNotAllowed();
}
_installValidation(validationConfig, selectors, installData, hooks);
}
/// @notice Returns the fallback signer data in storage.
/// @return The fallback signer and a boolean, true if the fallback signer validation is disabled, false if it
/// is enabled.
function getFallbackSignerData() external view returns (address, bool) {
SemiModularAccountStorage storage _storage = _getSemiModularAccountStorage();
return (_retrieveFallbackSignerUnchecked(_storage), _storage.fallbackSignerDisabled);
}
function _execUserOpValidation(
ValidationLookupKey validationLookupKey,
bytes32 userOpHash,
bytes calldata signatureSegment,
UOCallBuffer callBuffer
) internal override returns (uint256) {
if (validationLookupKey.eq(FALLBACK_VALIDATION_LOOKUP_KEY)) {
address fallbackSigner = _getFallbackSigner();
if (_checkSignature(fallbackSigner, userOpHash.toEthSignedMessageHash(), signatureSegment)) {
return _SIG_VALIDATION_PASSED;
}
return _SIG_VALIDATION_FAILED;
}
return super._execUserOpValidation(validationLookupKey, userOpHash, signatureSegment, callBuffer);
}
function _execRuntimeValidation(
ValidationLookupKey validationLookupKey,
RTCallBuffer callBuffer,
bytes calldata authorization
) internal override {
if (validationLookupKey.eq(FALLBACK_VALIDATION_LOOKUP_KEY)) {
address fallbackSigner = _getFallbackSigner();
if (msg.sender != fallbackSigner) {
revert FallbackSignerMismatch();
}
} else {
super._execRuntimeValidation(validationLookupKey, callBuffer, authorization);
}
}
function _exec1271Validation(
SigCallBuffer buffer,
bytes32 hash,
ValidationLookupKey validationLookupKey,
bytes calldata signature
) internal view override returns (bytes4) {
if (validationLookupKey.eq(FALLBACK_VALIDATION_LOOKUP_KEY)) {
address fallbackSigner = _getFallbackSigner();
// If called during validateUserOp, this implies that we're doing a deferred validation installation.
// In this case, as the hash is already replay-safe, we don't need to wrap it.
if (msg.sig != this.validateUserOp.selector) {
hash = _replaySafeHash(hash);
}
if (_checkSignature(fallbackSigner, hash, signature)) {
return _1271_MAGIC_VALUE;
}
return _1271_INVALID;
}
return super._exec1271Validation(buffer, hash, validationLookupKey, signature);
}
function _checkSignature(address owner, bytes32 digest, bytes calldata sig) internal view returns (bool) {
if (sig.length < 1) {
revert InvalidSignatureType();
}
SignatureType sigType = SignatureType(uint8(bytes1(sig)));
sig = sig[1:];
if (sigType == SignatureType.EOA) {
(address recovered, ECDSA.RecoverError err,) = ECDSA.tryRecover(digest, sig);
if (err == ECDSA.RecoverError.NoError && recovered == owner) {
return true;
}
return false;
} else if (sigType == SignatureType.CONTRACT_OWNER) {
return SignatureChecker.isValidERC1271SignatureNow(owner, digest, sig);
}
revert InvalidSignatureType();
}
function _isValidationGlobal(ValidationLookupKey validationFunction) internal view override returns (bool) {
if (validationFunction.eq(FALLBACK_VALIDATION_LOOKUP_KEY) || super._isValidationGlobal(validationFunction))
{
return true;
}
// At this point, the validation is not the fallback, and it's not an installed global validation.
SemiModularAccountStorage storage smaStorage = _getSemiModularAccountStorage();
// Before checking direct-call validation, we return false if fallback validation is disabled.
if (smaStorage.fallbackSignerDisabled) {
return false;
}
// Retrieve the fallback signer.
address fallbackSigner = _retrieveFallbackSignerUnchecked(smaStorage);
// Compute the direct call validation key.
ValidationLookupKey fallbackDirectCallValidation = ValidationLocatorLib.directCallLookupKey(fallbackSigner);
// Return true if the validation function passed is the fallback direct call validation key, and the sender
// is the fallback signer. This enforces that context is a
return validationFunction.eq(fallbackDirectCallValidation) && msg.sender == fallbackSigner;
}
function _getFallbackSigner() internal view returns (address) {
SemiModularAccountStorage storage _storage = _getSemiModularAccountStorage();
if (_storage.fallbackSignerDisabled) {
revert FallbackSignerDisabled();
}
// This can return zero.
return _retrieveFallbackSignerUnchecked(_storage);
}
/// @dev SMA implementations must implement their own fallback signer getter.
///
/// NOTE: The passed storage pointer may point to a struct with a zero address signer. It's up
/// to inheritors to determine what to do with that information. No assumptions about storage
/// state are safe to make besides layout.
function _retrieveFallbackSignerUnchecked(SemiModularAccountStorage storage _storage)
internal
view
virtual
returns (address)
{
return _storage.fallbackSigner;
}
/// @notice Returns the replay-safe hash generated from the passed typed data hash for 1271 validation.
/// @param hash The typed data hash to wrap in a replay-safe hash.
/// @return The replay-safe hash, to be used for 1271 signature generation.
///
/// @dev Generates a replay-safe hash to wrap a standard typed data hash. This prevents replay attacks by
/// enforcing the domain separator, which includes this contract's address and the chainId. This is only
/// relevant for 1271 validation because UserOp validation relies on the UO hash and the Entrypoint has
/// safeguards.
///
/// NOTE: Like in signature-based validation modules, the returned hash should be used to generate signatures,
/// but the original hash should be passed to the external-facing function for 1271 validation.
function _replaySafeHash(bytes32 hash) internal view returns (bytes32) {
return MessageHashUtils.toTypedDataHash({
domainSeparator: _domainSeparator(),
structHash: _hashStructReplaySafeHash(hash)
});
}
function _getSemiModularAccountStorage() internal pure returns (SemiModularAccountStorage storage) {
SemiModularAccountStorage storage _storage;
assembly ("memory-safe") {
_storage.slot := _SEMI_MODULAR_ACCOUNT_STORAGE_SLOT
}
return _storage;
}
// Conditionally skip allocation of call buffers.
function _validationIsNative(ValidationLookupKey validationLookupKey)
internal
pure
virtual
override
returns (bool)
{
return validationLookupKey.eq(FALLBACK_VALIDATION_LOOKUP_KEY);
}
/// @notice Adds a EIP-712 replay safe hash wrapper to the digest
/// @param hash The hash to wrap in a replay-safe hash
/// @return The replay-safe hash
function _hashStructReplaySafeHash(bytes32 hash) internal pure virtual returns (bytes32) {
bytes32 res;
assembly ("memory-safe") {
mstore(0x00, _REPLAY_SAFE_HASH_TYPEHASH)
mstore(0x20, hash)
res := keccak256(0, 0x40)
}
return res;
}
/// @dev Overrides ModularAccountView.
function _isNativeFunction(uint32 selector) internal pure virtual override returns (bool) {
return super._isNativeFunction(selector) || selector == uint32(this.updateFallbackSignerData.selector)
|| selector == uint32(this.getFallbackSignerData.selector);
}
/// @dev Overrides ModularAccountView.
function _isWrappedNativeFunction(uint32 selector) internal pure virtual override returns (bool) {
return
super._isWrappedNativeFunction(selector) || selector == uint32(this.updateFallbackSignerData.selector);
}
}// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {IModule} from "./IModule.sol";
struct ManifestExecutionFunction {
// The selector to install
bytes4 executionSelector;
// If true, the function won't need runtime validation, and can be called by anyone.
bool skipRuntimeValidation;
// If true, the function can be validated by a global validation function.
bool allowGlobalValidation;
}
struct ManifestExecutionHook {
bytes4 executionSelector;
uint32 entityId;
bool isPreHook;
bool isPostHook;
}
/// @dev A struct describing how the module should be installed on a modular account.
struct ExecutionManifest {
// Execution functions defined in this module to be installed on the MSCA.
ManifestExecutionFunction[] executionFunctions;
ManifestExecutionHook[] executionHooks;
// List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include
// IModule's interface ID.
bytes4[] interfaceIds;
}
interface IExecutionModule is IModule {
/// @notice Describe the contents and intended configuration of the module.
/// @dev This manifest MUST stay constant over time.
/// @return A manifest describing the contents and intended configuration of the module.
function executionManifest() external pure returns (ExecutionManifest memory);
}// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
/**
* User Operation struct
* @param sender - The sender account of this request.
* @param nonce - Unique value the sender uses to verify it is not a replay.
* @param initCode - If set, the account contract will be created by this constructor/
* @param callData - The method call to execute on this account.
* @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call.
* @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid.
* Covers batch overhead.
* @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters.
* @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data
* The paymaster will pay for the transaction instead of the sender.
* @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID.
*/
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.7.5;
/**
* Manage deposits and stakes.
* Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account).
* Stake is value locked for at least "unstakeDelay" by the staked entity.
*/
interface IStakeManager {
event Deposited(address indexed account, uint256 totalDeposit);
event Withdrawn(
address indexed account,
address withdrawAddress,
uint256 amount
);
// Emitted when stake or unstake delay are modified.
event StakeLocked(
address indexed account,
uint256 totalStaked,
uint256 unstakeDelaySec
);
// Emitted once a stake is scheduled for withdrawal.
event StakeUnlocked(address indexed account, uint256 withdrawTime);
event StakeWithdrawn(
address indexed account,
address withdrawAddress,
uint256 amount
);
/**
* @param deposit - The entity's deposit.
* @param staked - True if this entity is staked.
* @param stake - Actual amount of ether staked for this entity.
* @param unstakeDelaySec - Minimum delay to withdraw the stake.
* @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked.
* @dev Sizes were chosen so that deposit fits into one cell (used during handleOp)
* and the rest fit into a 2nd cell (used during stake/unstake)
* - 112 bit allows for 10^15 eth
* - 48 bit for full timestamp
* - 32 bit allows 150 years for unstake delay
*/
struct DepositInfo {
uint256 deposit;
bool staked;
uint112 stake;
uint32 unstakeDelaySec;
uint48 withdrawTime;
}
// API struct used by getStakeInfo and simulateValidation.
struct StakeInfo {
uint256 stake;
uint256 unstakeDelaySec;
}
/**
* Get deposit info.
* @param account - The account to query.
* @return info - Full deposit information of given account.
*/
function getDepositInfo(
address account
) external view returns (DepositInfo memory info);
/**
* Get account balance.
* @param account - The account to query.
* @return - The deposit (for gas payment) of the account.
*/
function balanceOf(address account) external view returns (uint256);
/**
* Add to the deposit of the given account.
* @param account - The account to add to.
*/
function depositTo(address account) external payable;
/**
* Add to the account's stake - amount and delay
* any pending unstake is first cancelled.
* @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn.
*/
function addStake(uint32 _unstakeDelaySec) external payable;
/**
* Attempt to unlock the stake.
* The value can be withdrawn (using withdrawStake) after the unstake delay.
*/
function unlockStake() external;
/**
* Withdraw from the (unlocked) stake.
* Must first call unlockStake and wait for the unstakeDelay to pass.
* @param withdrawAddress - The address to send withdrawn value.
*/
function withdrawStake(address payable withdrawAddress) external;
/**
* Withdraw from the deposit.
* @param withdrawAddress - The address to send withdrawn value.
* @param withdrawAmount - The amount to withdraw.
*/
function withdrawTo(
address payable withdrawAddress,
uint256 withdrawAmount
) external;
}// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
/**
* Aggregated Signatures validator.
*/
interface IAggregator {
/**
* Validate aggregated signature.
* Revert if the aggregated signature does not match the given list of operations.
* @param userOps - Array of UserOperations to validate the signature for.
* @param signature - The aggregated signature.
*/
function validateSignatures(
PackedUserOperation[] calldata userOps,
bytes calldata signature
) external view;
/**
* Validate signature of a single userOp.
* This method should be called by bundler after EntryPointSimulation.simulateValidation() returns
* the aggregator this account uses.
* First it validates the signature over the userOp. Then it returns data to be used when creating the handleOps.
* @param userOp - The userOperation received from the user.
* @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps.
* (usually empty, unless account and aggregator support some kind of "multisig".
*/
function validateUserOpSignature(
PackedUserOperation calldata userOp
) external view returns (bytes memory sigForUserOp);
/**
* Aggregate multiple signatures into a single value.
* This method is called off-chain to calculate the signature to pass with handleOps()
* bundler MAY use optimized custom code perform this aggregation.
* @param userOps - Array of UserOperations to collect the signatures from.
* @return aggregatedSignature - The aggregated signature.
*/
function aggregateSignatures(
PackedUserOperation[] calldata userOps
) external view returns (bytes memory aggregatedSignature);
}// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
interface INonceManager {
/**
* Return the next nonce for this sender.
* Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop)
* But UserOp with different keys can come with arbitrary order.
*
* @param sender the account address
* @param key the high 192 bit of the nonce
* @return nonce a full nonce to pass for next UserOp with this sender.
*/
function getNonce(address sender, uint192 key)
external view returns (uint256 nonce);
/**
* Manually increment the nonce of the sender.
* This method is exposed just for completeness..
* Account does NOT need to call it, neither during validation, nor elsewhere,
* as the EntryPoint will update the nonce regardless.
* Possible use-case is call it with various keys to "initialize" their nonces to one, so that future
* UserOperations will not pay extra for the first transaction with a given key.
*/
function incrementNonce(uint192 key) external;
}// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
interface IModule is IERC165 {
/// @notice Initialize module data for the modular account.
/// @dev Called by the modular account during `installExecution`.
/// @param data Optional bytes array to be decoded and used by the module to setup initial module data for the
/// modular account.
function onInstall(bytes calldata data) external;
/// @notice Clear module data for the modular account.
/// @dev Called by the modular account during `uninstallExecution`.
/// @param data Optional bytes array to be decoded and used by the module to clear module data for the modular
/// account.
function onUninstall(bytes calldata data) external;
/// @notice Return a unique identifier for the module.
/// @dev This function MUST return a string in the format "vendor.module.semver". The vendor and module
/// names MUST NOT contain a period character.
/// @return The module ID.
function moduleId() external view returns (string memory);
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {HookConfig, ModuleEntity} from "../interfaces/IModularAccount.sol";
// Hook types:
// Exec hook: bools for hasPre, hasPost
// Validation hook: no bools
// Hook fields:
// module address
// entity ID
// hook type
// if exec hook: hasPre, hasPost
// Hook config is a packed representation of a hook function and flags for its configuration.
// Layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address
// 0x________________________________________BBBBBBBB________________ // Entity ID
// 0x________________________________________________CC______________ // Hook Flags
// Hook flags layout:
// 0b00000___ // unused
// 0b_____A__ // hasPre (exec only)
// 0b______B_ // hasPost (exec only)
// 0b_______C // hook type (0 for exec, 1 for validation)
library HookConfigLib {
// Hook type constants
// Exec has no bits set
bytes32 internal constant _HOOK_TYPE_EXEC = bytes32(uint256(0));
// Validation has 1 in the 25th byte
bytes32 internal constant _HOOK_TYPE_VALIDATION = bytes32(uint256(1) << 56);
// Exec hook flags constants
// Pre hook has 1 in 4's bit in the 25th byte
bytes32 internal constant _EXEC_HOOK_HAS_PRE = bytes32(uint256(1) << 58);
// Post hook has 1 in 2's bit in the 25th byte
bytes32 internal constant _EXEC_HOOK_HAS_POST = bytes32(uint256(1) << 57);
function packValidationHook(ModuleEntity _hookFunction) internal pure returns (HookConfig) {
return
HookConfig.wrap(bytes25(bytes25(ModuleEntity.unwrap(_hookFunction)) | bytes25(_HOOK_TYPE_VALIDATION)));
}
function packValidationHook(address _module, uint32 _entityId) internal pure returns (HookConfig) {
return HookConfig.wrap(
bytes25(
// module address stored in the first 20 bytes
bytes25(bytes20(_module))
// entityId stored in the 21st - 24th byte
| bytes25(bytes24(uint192(_entityId))) | bytes25(_HOOK_TYPE_VALIDATION)
)
);
}
function packExecHook(ModuleEntity _hookFunction, bool _hasPre, bool _hasPost)
internal
pure
returns (HookConfig)
{
return HookConfig.wrap(
bytes25(
bytes25(ModuleEntity.unwrap(_hookFunction))
// | bytes25(_HOOK_TYPE_EXEC) // Can omit because exec type is 0
| bytes25(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0))
| bytes25(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0))
)
);
}
function packExecHook(address _module, uint32 _entityId, bool _hasPre, bool _hasPost)
internal
pure
returns (HookConfig)
{
return HookConfig.wrap(
bytes25(
// module address stored in the first 20 bytes
bytes25(bytes20(_module))
// entityId stored in the 21st - 24th byte
| bytes25(bytes24(uint192(_entityId)))
// | bytes25(_HOOK_TYPE_EXEC) // Can omit because exec type is 0
| bytes25(_hasPre ? _EXEC_HOOK_HAS_PRE : bytes32(0))
| bytes25(_hasPost ? _EXEC_HOOK_HAS_POST : bytes32(0))
)
);
}
function unpackValidationHook(HookConfig _config) internal pure returns (ModuleEntity _hookFunction) {
bytes25 configBytes = HookConfig.unwrap(_config);
_hookFunction = ModuleEntity.wrap(bytes24(configBytes));
}
function unpackExecHook(HookConfig _config)
internal
pure
returns (ModuleEntity _hookFunction, bool _hasPre, bool _hasPost)
{
bytes25 configBytes = HookConfig.unwrap(_config);
_hookFunction = ModuleEntity.wrap(bytes24(configBytes));
_hasPre = configBytes & _EXEC_HOOK_HAS_PRE != 0;
_hasPost = configBytes & _EXEC_HOOK_HAS_POST != 0;
}
function module(HookConfig _config) internal pure returns (address) {
return address(bytes20(HookConfig.unwrap(_config)));
}
function entityId(HookConfig _config) internal pure returns (uint32) {
return uint32(bytes4(HookConfig.unwrap(_config) << 160));
}
function moduleEntity(HookConfig _config) internal pure returns (ModuleEntity) {
return ModuleEntity.wrap(bytes24(HookConfig.unwrap(_config)));
}
// Check if the hook is a validation hook
// If false, it is an exec hook
function isValidationHook(HookConfig _config) internal pure returns (bool) {
return HookConfig.unwrap(_config) & _HOOK_TYPE_VALIDATION != 0;
}
// Check if the exec hook has a pre hook
// Undefined behavior if the hook is not an exec hook
function hasPreHook(HookConfig _config) internal pure returns (bool) {
return HookConfig.unwrap(_config) & _EXEC_HOOK_HAS_PRE != 0;
}
// Check if the exec hook has a post hook
// Undefined behavior if the hook is not an exec hook
function hasPostHook(HookConfig _config) internal pure returns (bool) {
return HookConfig.unwrap(_config) & _EXEC_HOOK_HAS_POST != 0;
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {HookConfig, ValidationFlags} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {LinkedListSet, SetValue} from "../libraries/LinkedListSetLib.sol";
import {ValidationLookupKey} from "../libraries/ValidationLocatorLib.sol";
// ERC-7201 derived storage slot.
// keccak256(abi.encode(uint256(keccak256("Alchemy.ModularAccount.Storage_V2")) - 1)) & ~bytes32(uint256(0xff))
bytes32 constant _ACCOUNT_STORAGE_SLOT = 0x596912a710dec01bac203cb0ed2c7e56a2ce6b2a68276967fff6dd57561bdd00;
/// @notice Represents data associated with a specific function selector.
struct ExecutionStorage {
// The module that implements this execution function.
// If this is a native function, the address should remain address(0).
address module;
// Whether or not the function needs runtime validation, or can be called without any validation. The function
// can still be state changing if this flag is set to true.
// Note that even if this is set to true, user op validation will still be required, otherwise any caller could
// drain the account of native tokens by wasting gas.
bool skipRuntimeValidation;
// Whether or not a global validation function may be used to validate this function.
bool allowGlobalValidation;
// The execution hooks for this function selector.
LinkedListSet executionHooks;
}
/// @notice Represents data associated with a specific validation function.
struct ValidationStorage {
// The address of the validation module.
address module;
// ValidationFlags layout:
// 0b00000___ // unused
// 0b_____A__ // isGlobal
// 0b______B_ // isSignatureValidation
// 0b_______C // isUserOpValidation
ValidationFlags validationFlags;
// Length of the validation hooks for this validation function. The length is stored here, in the same storage
// slot as the flags, to save an `sload` when putting the hooks into memory.
uint8 validationHookCount;
// Length of execution hooks for this validation function. The length is stored here, in the same storage slot
// as the flags, to save an `sload` when putting the hooks into memory.
uint8 executionHookCount;
// The validation hooks for this validation function.
LinkedListSet validationHooks;
// Execution hooks to run with this validation function.
LinkedListSet executionHooks;
// The set of selectors that may be validated by this validation function.
LinkedListSet selectors;
}
/// @custom:storage-location erc7201:Alchemy.ModularAccount.Storage_V2
struct AccountStorage {
// AccountStorageInitializable variables.
uint64 initialized;
bool initializing;
// Execution functions and their associated functions.
mapping(bytes4 selector => ExecutionStorage) executionStorage;
// Validation functions and their associated functions.
mapping(ValidationLookupKey lookupKey => ValidationStorage) validationStorage;
// Module-defined ERC-165 interfaces installed on the account.
mapping(bytes4 => uint256) supportedIfaces;
}
function getAccountStorage() pure returns (AccountStorage storage _storage) {
assembly ("memory-safe") {
_storage.slot := _ACCOUNT_STORAGE_SLOT
}
}
function toSetValue(HookConfig hookConfig) pure returns (SetValue) {
return SetValue.wrap(bytes31(HookConfig.unwrap(hookConfig)));
}
function toSetValue(bytes4 selector) pure returns (SetValue) {
return SetValue.wrap(bytes31(selector));
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol";
import {IExecutionModule} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol";
import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol";
import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol";
import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol";
import {IAggregator} from "@eth-infinitism/account-abstraction/interfaces/IAggregator.sol";
import {IPaymaster} from "@eth-infinitism/account-abstraction/interfaces/IPaymaster.sol";
/// @title Known Selectors Library
/// @author Alchemy
/// @notice Library to help to check if a selector is an ERC-6900 module function or a an ERC-4337 contract
/// function.
library KnownSelectorsLib {
/// @notice Check if a selector is an ERC-4337 function.
/// @param selector The selector to check.
/// @return True if the selector is an ERC-4337 function, false otherwise.
function isERC4337Function(uint32 selector) internal pure returns (bool) {
return selector == uint32(IAggregator.validateSignatures.selector)
|| selector == uint32(IAggregator.validateUserOpSignature.selector)
|| selector == uint32(IAggregator.aggregateSignatures.selector)
|| selector == uint32(IPaymaster.validatePaymasterUserOp.selector)
|| selector == uint32(IPaymaster.postOp.selector);
}
/// @notice Check if a selector is an ERC-6900 module function.
/// @param selector The selector to check.
/// @return True if the selector is an ERC-6900 module function, false otherwise.
function isIModuleFunction(uint32 selector) internal pure returns (bool) {
return selector == uint32(IModule.onInstall.selector) || selector == uint32(IModule.onUninstall.selector)
|| selector == uint32(IModule.moduleId.selector)
|| selector == uint32(IExecutionModule.executionManifest.selector)
|| selector == uint32(IExecutionHookModule.preExecutionHook.selector)
|| selector == uint32(IExecutionHookModule.postExecutionHook.selector)
|| selector == uint32(IValidationModule.validateUserOp.selector)
|| selector == uint32(IValidationModule.validateRuntime.selector)
|| selector == uint32(IValidationModule.validateSignature.selector)
|| selector == uint32(IValidationHookModule.preUserOpValidationHook.selector)
|| selector == uint32(IValidationHookModule.preRuntimeValidationHook.selector)
|| selector == uint32(IValidationHookModule.preSignatureValidationHook.selector);
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
type SetValue is bytes31;
/// @dev The sentinel value is used to indicate the head and tail of the list.
bytes32 constant SENTINEL_VALUE = bytes32(uint256(1));
struct LinkedListSet {
// Byte Layout
// | value | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__ |
// | meta | 0x______________________________________________________________BB |
// Bit-layout of the meta byte
// | unused | 0b0000000_ |
// | sentinel | 0b_______A |
// Key excludes the meta bytes, except for the sentinel value, which is 0x1
mapping(bytes32 => bytes32) map;
}
/// @title Linked List Set Library
/// @author Alchemy
/// @notice This library provides a set of functions for managing enumerable sets of bytes31 values. It is a fork
/// of the LinkedListSet library in modular-account-libs, with the following changes:
/// - The flags feature has been removed, so the library no longer supports both the "has next" flag, and the
/// user-defined flags.
/// - The library has been modified to work with bytes31 values instead of bytes30 values.
library LinkedListSetLib {
/// @notice Add a value to a set.
/// @param set The set to add the value to.
/// @param value The value to add.
/// @return True if the value was added, false if the value cannot be added (already exists or is zero).
function tryAdd(LinkedListSet storage set, SetValue value) internal returns (bool) {
mapping(bytes32 => bytes32) storage map = set.map;
bytes32 unwrappedKey = SetValue.unwrap(value);
if (unwrappedKey == bytes32(0) || map[unwrappedKey] != bytes32(0)) return false;
bytes32 prev = map[SENTINEL_VALUE];
if (prev == bytes32(0) || isSentinel(prev)) {
// Set is empty
map[SENTINEL_VALUE] = unwrappedKey;
map[unwrappedKey] = SENTINEL_VALUE;
} else {
// set is not empty
map[SENTINEL_VALUE] = unwrappedKey;
map[unwrappedKey] = prev;
}
return true;
}
/// @notice Remove a value from a set.
/// @dev This is an O(n) operation, where n is the number of elements in the set.
/// @param set The set to remove the value from.
/// @param value The value to remove.
/// @return True if the value was removed, false if the value does not exist.
function tryRemove(LinkedListSet storage set, SetValue value) internal returns (bool) {
mapping(bytes32 => bytes32) storage map = set.map;
bytes32 unwrappedKey = SetValue.unwrap(value);
bytes32 nextValue = map[unwrappedKey];
if (unwrappedKey == bytes32(0) || nextValue == bytes32(0)) return false;
bytes32 prevKey = SENTINEL_VALUE;
bytes32 currentKey;
do {
currentKey = map[prevKey];
if (currentKey == unwrappedKey) {
// Set the previous value's next value to the next value,
// and the flags to the current value's flags.
// and the next value's `hasNext` flag to determine whether or not the next value is (or points to)
// the sentinel value.
map[prevKey] = nextValue;
map[currentKey] = bytes32(0);
return true;
}
prevKey = currentKey;
} while (!isSentinel(currentKey) && currentKey != bytes32(0));
return false;
}
/// @notice Remove a value from a set, given the previous value in the set.
/// @dev This is an O(1) operation but requires additional knowledge.
/// @param set The set to remove the value from.
/// @param value The value to remove.
/// @param prev The previous value in the set.
/// @return True if the value was removed, false if the value does not exist, or if the wrong prev was
/// specified.
function tryRemoveKnown(LinkedListSet storage set, SetValue value, bytes32 prev) internal returns (bool) {
mapping(bytes32 => bytes32) storage map = set.map;
bytes32 unwrappedKey = SetValue.unwrap(value);
if (prev == bytes32(0) || unwrappedKey == bytes32(0)) {
return false;
}
// assert that the previous value's next value is the value to be removed
bytes32 currentValue = map[prev];
if (currentValue != unwrappedKey) {
return false;
}
bytes32 next = map[unwrappedKey];
if (next == bytes32(0)) {
// The set didn't actually contain the value
return false;
}
map[prev] = next;
map[unwrappedKey] = bytes32(0);
return true;
}
/// @notice Remove all values from a set.
/// @dev This is an O(n) operation, where n is the number of elements in the set.
/// @param set The set to remove the values from.
function clear(LinkedListSet storage set) internal {
mapping(bytes32 => bytes32) storage map = set.map;
bytes32 cursor = SENTINEL_VALUE;
do {
bytes32 next = map[cursor];
map[cursor] = bytes32(0);
cursor = next;
} while (!isSentinel(cursor) && cursor != bytes32(0));
}
/// @notice Check if a set contains a value.
/// @dev This method does not clear the upper bits of `value`, that is expected to be done as part of casting
/// to the correct type. If this function is provided the sentinel value by using the upper bits, this function
/// may returns `true`.
/// @param set The set to check.
/// @param value The value to check for.
/// @return True if the set contains the value, false otherwise.
function contains(LinkedListSet storage set, SetValue value) internal view returns (bool) {
mapping(bytes32 => bytes32) storage map = set.map;
return map[SetValue.unwrap(value)] != bytes32(0);
}
/// @notice Check if a set is empty.
/// @param set The set to check.
/// @return True if the set is empty, false otherwise.
function isEmpty(LinkedListSet storage set) internal view returns (bool) {
mapping(bytes32 => bytes32) storage map = set.map;
bytes32 val = map[SENTINEL_VALUE];
return val == bytes32(0) || isSentinel(val); // either the sentinel is unset, or points to itself
}
/// @notice Get all elements in a set.
/// @dev This is an O(n) operation, where n is the number of elements in the set.
/// @param set The set to get the elements of.
/// @return ret An array of all elements in the set.
function getAll(LinkedListSet storage set) internal view returns (SetValue[] memory ret) {
mapping(bytes32 => bytes32) storage map = set.map;
uint256 size;
bytes32 cursor = map[SENTINEL_VALUE];
// Dynamically allocate the returned array as we iterate through the set, since we don't know the size
// beforehand.
// This is accomplished by first writing to memory after the free memory pointer,
// then updating the free memory pointer to cover the newly-allocated data.
// To the compiler, writes to memory after the free memory pointer are considered "memory safe".
// See https://docs.soliditylang.org/en/v0.8.22/assembly.html#memory-safety
// Stack variable lifting done when compiling with via-ir will only ever place variables into memory
// locations below the current free memory pointer, so it is safe to compile this library with via-ir.
// See https://docs.soliditylang.org/en/v0.8.22/yul.html#memoryguard
assembly ("memory-safe") {
// It is critical that no other memory allocations occur between:
// - loading the value of the free memory pointer into `ret`
// - updating the free memory pointer to point to the newly-allocated data, which is done after all
// the values have been written.
ret := mload(0x40)
}
while (!isSentinel(cursor) && cursor != bytes32(0)) {
unchecked {
++size;
}
// Place the item into the return array manually. Since the size was just incremented, it will point to
// the next location to write to.
assembly ("memory-safe") {
mstore(add(ret, mul(size, 0x20)), cursor)
}
cursor = map[cursor];
}
assembly ("memory-safe") {
// Update the free memory pointer with the now-known length of the array.
mstore(0x40, add(ret, mul(add(size, 1), 0x20)))
// Set the length of the array.
mstore(ret, size)
}
}
function isSentinel(bytes32 value) internal pure returns (bool ret) {
assembly ("memory-safe") {
ret := and(value, 1)
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {HookConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol";
import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import {toSetValue} from "../account/AccountStorage.sol";
import {ExecutionLib} from "./ExecutionLib.sol";
import {LinkedListSet, LinkedListSetLib} from "./LinkedListSetLib.sol";
/// @title Module Install Commons Library
/// @author Alchemy
/// @notice This is an internal library which holds module installation-related functions relevant to both the
/// ExecutionInstallDelegate and the ModuleManagerInternals contracts.
library ModuleInstallCommonsLib {
using LinkedListSetLib for LinkedListSet;
error InterfaceNotSupported(address module);
error ModuleInstallCallbackFailed(address module, bytes revertReason);
error ExecutionHookAlreadySet(HookConfig hookConfig);
// Internal Functions
// We don't need to bring the exec hook removal function here since it's only ever used in the
// ExecutionInstallLib
/// @dev adds an execution hook to a specific set of hooks.
function addExecHooks(LinkedListSet storage hooks, HookConfig hookConfig) internal {
if (!hooks.tryAdd(toSetValue(hookConfig))) {
revert ExecutionHookAlreadySet(hookConfig);
}
}
/// @dev setup the module storage for the account, reverts are bubbled up into a custom
/// ModuleInstallCallbackFailed
function onInstall(address module, bytes calldata data, bytes4 interfaceId) internal {
if (data.length > 0) {
if (!ERC165Checker.supportsERC165InterfaceUnchecked(module, interfaceId)) {
revert InterfaceNotSupported(module);
}
// solhint-disable-next-line no-empty-blocks
try IModule(module).onInstall(data) {}
catch {
bytes memory revertReason = ExecutionLib.collectReturnData();
revert ModuleInstallCallbackFailed(module, revertReason);
}
}
}
/// @dev clear the module storage for the account, reverts are IGNORED. Status is included in emitted event.
function onUninstall(address module, bytes calldata data) internal returns (bool onUninstallSuccess) {
onUninstallSuccess = true;
if (data.length > 0) {
// Clear the module storage for the account.
// solhint-disable-next-line no-empty-blocks
try IModule(module).onUninstall(data) {}
catch {
onUninstallSuccess = false;
}
}
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {ModuleEntity} from "../interfaces/IModularAccount.sol";
// ModuleEntity is a packed representation of a module function
// Layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address
// 0x________________________________________BBBBBBBB________________ // Entity ID
// 0x________________________________________________0000000000000000 // unused
library ModuleEntityLib {
function pack(address addr, uint32 entityId) internal pure returns (ModuleEntity) {
return ModuleEntity.wrap(bytes24(bytes20(addr)) | bytes24(uint192(entityId)));
}
function unpack(ModuleEntity moduleEntity) internal pure returns (address addr, uint32 entityId) {
bytes24 underlying = ModuleEntity.unwrap(moduleEntity);
addr = address(bytes20(underlying));
entityId = uint32(bytes4(underlying << 160));
}
function isEmpty(ModuleEntity moduleEntity) internal pure returns (bool) {
return ModuleEntity.unwrap(moduleEntity) == bytes24(0);
}
function notEmpty(ModuleEntity moduleEntity) internal pure returns (bool) {
return ModuleEntity.unwrap(moduleEntity) != bytes24(0);
}
function eq(ModuleEntity a, ModuleEntity b) internal pure returns (bool) {
return ModuleEntity.unwrap(a) == ModuleEntity.unwrap(b);
}
function notEq(ModuleEntity a, ModuleEntity b) internal pure returns (bool) {
return ModuleEntity.unwrap(a) != ModuleEntity.unwrap(b);
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {ModuleEntity, ValidationConfig, ValidationFlags} from "../interfaces/IModularAccount.sol";
// Validation config is a packed representation of a validation function and flags for its configuration.
// Layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________________ // Address
// 0x________________________________________BBBBBBBB________________ // Entity ID
// 0x________________________________________________CC______________ // ValidationFlags
// 0x__________________________________________________00000000000000 // unused
// ValidationFlags layout:
// 0b00000___ // unused
// 0b_____A__ // isGlobal
// 0b______B_ // isSignatureValidation
// 0b_______C // isUserOpValidation
library ValidationConfigLib {
// is user op validation flag stored in last bit of the 25th byte
bytes32 internal constant _VALIDATION_FLAG_IS_USER_OP = bytes32(uint256(1) << 56);
// is signature validation flag stored in second to last bit of the 25th byte
bytes32 internal constant _VALIDATION_FLAG_IS_SIGNATURE = bytes32(uint256(1) << 57);
// is global flag stored in the third to last bit of the 25th byte
bytes32 internal constant _VALIDATION_FLAG_IS_GLOBAL = bytes32(uint256(1) << 58);
function pack(
ModuleEntity _validationFunction,
bool _isGlobal,
bool _isSignatureValidation,
bool _isUserOpValidation
) internal pure returns (ValidationConfig) {
return ValidationConfig.wrap(
bytes25(
bytes25(ModuleEntity.unwrap(_validationFunction))
| bytes25(bytes32(_isGlobal ? _VALIDATION_FLAG_IS_GLOBAL : bytes32(0)))
| bytes25(bytes32(_isSignatureValidation ? _VALIDATION_FLAG_IS_SIGNATURE : bytes32(0)))
| bytes25(bytes32(_isUserOpValidation ? _VALIDATION_FLAG_IS_USER_OP : bytes32(0)))
)
);
}
function pack(
address _module,
uint32 _entityId,
bool _isGlobal,
bool _isSignatureValidation,
bool _isUserOpValidation
) internal pure returns (ValidationConfig) {
return ValidationConfig.wrap(
bytes25(
// module address stored in the first 20 bytes
bytes25(bytes20(_module))
// entityId stored in the 21st - 24th byte
| bytes25(bytes24(uint192(_entityId)))
| bytes25(bytes32(_isGlobal ? _VALIDATION_FLAG_IS_GLOBAL : bytes32(0)))
| bytes25(bytes32(_isSignatureValidation ? _VALIDATION_FLAG_IS_SIGNATURE : bytes32(0)))
| bytes25(bytes32(_isUserOpValidation ? _VALIDATION_FLAG_IS_USER_OP : bytes32(0)))
)
);
}
function unpackUnderlying(ValidationConfig config)
internal
pure
returns (address _module, uint32 _entityId, ValidationFlags flags)
{
bytes25 configBytes = ValidationConfig.unwrap(config);
_module = address(bytes20(configBytes));
_entityId = uint32(bytes4(configBytes << 160));
flags = ValidationFlags.wrap(uint8(configBytes[24]));
}
function unpack(ValidationConfig config)
internal
pure
returns (ModuleEntity _validationFunction, ValidationFlags flags)
{
bytes25 configBytes = ValidationConfig.unwrap(config);
_validationFunction = ModuleEntity.wrap(bytes24(configBytes));
flags = ValidationFlags.wrap(uint8(configBytes[24]));
}
function module(ValidationConfig config) internal pure returns (address) {
return address(bytes20(ValidationConfig.unwrap(config)));
}
function entityId(ValidationConfig config) internal pure returns (uint32) {
return uint32(bytes4(ValidationConfig.unwrap(config) << 160));
}
function moduleEntity(ValidationConfig config) internal pure returns (ModuleEntity) {
return ModuleEntity.wrap(bytes24(ValidationConfig.unwrap(config)));
}
function isGlobal(ValidationConfig config) internal pure returns (bool) {
return ValidationConfig.unwrap(config) & _VALIDATION_FLAG_IS_GLOBAL != 0;
}
function isGlobal(ValidationFlags flags) internal pure returns (bool) {
return ValidationFlags.unwrap(flags) & 0x04 != 0;
}
function isSignatureValidation(ValidationConfig config) internal pure returns (bool) {
return ValidationConfig.unwrap(config) & _VALIDATION_FLAG_IS_SIGNATURE != 0;
}
function isSignatureValidation(ValidationFlags flags) internal pure returns (bool) {
return ValidationFlags.unwrap(flags) & 0x02 != 0;
}
function isUserOpValidation(ValidationConfig config) internal pure returns (bool) {
return ValidationConfig.unwrap(config) & _VALIDATION_FLAG_IS_USER_OP != 0;
}
function isUserOpValidation(ValidationFlags flags) internal pure returns (bool) {
return ValidationFlags.unwrap(flags) & 0x01 != 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError, bytes32) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/SignatureChecker.sol)
pragma solidity ^0.8.20;
import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
/**
* @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
* signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like
* Argent and Safe Wallet (previously Gnosis Safe).
*/
library SignatureChecker {
/**
* @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
* signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
(address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature);
return
(error == ECDSA.RecoverError.NoError && recovered == signer) ||
isValidERC1271SignatureNow(signer, hash, signature);
}
/**
* @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
* against the signer smart contract using ERC1271.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidERC1271SignatureNow(
address signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {ModuleEntity} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {ValidationLookupKey} from "../libraries/ValidationLocatorLib.sol";
// Magic value for the ModuleEntity of the fallback validation for SemiModularAccount.
ModuleEntity constant FALLBACK_VALIDATION = ModuleEntity.wrap(bytes24(0));
// Magic value for the validation entity id of the fallback validation for SemiModularAccount.
uint32 constant FALLBACK_VALIDATION_ID = uint32(0);
// Magic value for the ValidationLookupKey of the fallback validation for SemiModularAccount.
ValidationLookupKey constant FALLBACK_VALIDATION_LOOKUP_KEY = ValidationLookupKey.wrap(uint168(0));// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
/// @notice An enum that is prepended to signatures to differentiate between EOA and contract owner signatures.
enum SignatureType {
EOA,
CONTRACT_OWNER
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol";
import {HookConfig} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol";
import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol";
import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol";
import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol";
import {ModuleEntity} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
// The following types alias a `bytes memory`, to protect the user from doing anything unexpected with it. We can't
// actually alias a `bytes memory` type, so we use a `bytes32` type instead, and cast it to `bytes memory` within
// this library.
type UOCallBuffer is bytes32;
type RTCallBuffer is bytes32;
type PHCallBuffer is bytes32;
type SigCallBuffer is bytes32;
type DensePostHookData is bytes32;
using HookConfigLib for HookConfig;
/// @title Execution Library
/// @author Alchemy
/// @notice A library for performing external calls. This library is used for the external calls of `execute` and
/// `executeBatch`, for any account self-calls, and for any call to a module function.
/// @dev This library uses "call buffers", or reusable memory buffers that hold the abi-encoded data to be sent to
/// a module function. These buffers are used to avoid the overhead of encoding the same data multiple times.
library ExecutionLib {
// solhint-disable ordering
// Functions are more readable in original order.
error PostExecHookReverted(ModuleEntity moduleFunction, bytes revertReason);
error PreExecHookReverted(ModuleEntity moduleFunction, bytes revertReason);
error PreRuntimeValidationHookReverted(ModuleEntity moduleFunction, bytes revertReason);
error PreSignatureValidationHookReverted(ModuleEntity moduleFunction, bytes revertReason);
error PreUserOpValidationHookReverted(ModuleEntity moduleFunction, bytes revertReason);
error RuntimeValidationFunctionReverted(ModuleEntity moduleFunction, bytes revertReason);
error SignatureValidationFunctionReverted(ModuleEntity moduleFunction, bytes revertReason);
error UserOpValidationFunctionReverted(ModuleEntity moduleFunction, bytes revertReason);
/// @notice Perform the following call, without capturing any return data.
/// If the call reverts, the revert message will be directly bubbled up.
/// @param target The address to call.
/// @param value The value to send with the call.
/// @param callData The data to send with the call.
function callBubbleOnRevert(address target, uint256 value, bytes memory callData) internal {
// Manually call, without collecting return data unless there's a revert.
assembly ("memory-safe") {
let success :=
call(
gas(),
target,
/*value*/
value,
/*argOffset*/
add(callData, 0x20),
/*argSize*/
mload(callData),
/*retOffset*/
codesize(),
/*retSize*/
0
)
// directly bubble up revert messages, if any.
if iszero(success) {
// For memory safety, copy this revert data to scratch space past the end of used memory. Because
// we immediately revert, we can omit storing the length as we normally would for a `bytes memory`
// type, as well as omit finalizing the allocation by updating the free memory pointer.
let revertDataLocation := mload(0x40)
returndatacopy(revertDataLocation, 0, returndatasize())
revert(revertDataLocation, returndatasize())
}
}
}
/// @notice Transiently copy the call data to a memory, and perform a self-call.
/// If the call reverts, the revert message will be directly bubbled up.
/// @param target The address to call.
/// @param value The value to send with the call.
/// @param callData The data to send with the call.
function callBubbleOnRevertTransient(address target, uint256 value, bytes calldata callData) internal {
bytes memory encodedCall;
assembly ("memory-safe") {
// Store the length of the call
encodedCall := mload(0x40)
mstore(encodedCall, callData.length)
// Copy in the calldata
calldatacopy(add(encodedCall, 0x20), callData.offset, callData.length)
}
callBubbleOnRevert(target, value, encodedCall);
// Memory is discarded afterwards
}
// Transiently copy the call data to a memory, and perform a self-call.
function delegatecallBubbleOnRevertTransient(address target) internal {
assembly ("memory-safe") {
// Store the length of the call
let fmp := mload(0x40)
// Copy in the entire calldata
calldatacopy(fmp, 0, calldatasize())
let success :=
delegatecall(
gas(),
target,
/*argOffset*/
fmp,
/*argSize*/
calldatasize(),
/*retOffset*/
codesize(),
/*retSize*/
0
)
// directly bubble up revert messages, if any.
if iszero(success) {
// For memory safety, copy this revert data to scratch space past the end of used memory. Because
// we immediately revert, we can omit storing the length as we normally would for a `bytes memory`
// type, as well as omit finalizing the allocation by updating the free memory pointer.
let revertDataLocation := mload(0x40)
returndatacopy(revertDataLocation, 0, returndatasize())
revert(revertDataLocation, returndatasize())
}
}
// Memory is discarded afterwards
}
/// @notice Manually collect and store the return data from the most recent external call into a `bytes
/// memory`.
/// @return returnData The return data from the most recent external call.
function collectReturnData() internal pure returns (bytes memory returnData) {
assembly ("memory-safe") {
// Allocate a buffer of that size, advancing the memory pointer to the nearest word
returnData := mload(0x40)
mstore(returnData, returndatasize())
mstore(0x40, and(add(add(returnData, returndatasize()), 0x3f), not(0x1f)))
// Copy over the return data
returndatacopy(add(returnData, 0x20), 0, returndatasize())
}
}
// Allocate a buffer to call user op validation and validation hook functions. Both of these take the form of
// - bytes4 selector
// - uint32 entityId
// - PackedUserOperation userOp
// - bytes32 userOpHash
// The buffer starts with the selector for `preUserOpValidationHook`, and can be updated later to
// `validateUserOp`. When perfomring the actual function calls later, update the entityId field and selector,
// as as needed.
function allocateUserOpValidationCallBuffer(PackedUserOperation calldata userOp, bytes32 userOpHash)
internal
pure
returns (UOCallBuffer result)
{
bytes memory buffer =
abi.encodeCall(IValidationHookModule.preUserOpValidationHook, (uint32(0), userOp, userOpHash));
assembly ("memory-safe") {
result := buffer
}
// Buffer contents:
// 0xAAAAAAAA // selector
// 0x000: 0x________________________________________________________BBBBBBBB // entityId
// 0x020: 0x______________________________________________________________60 // userOp offset
// 0x040: 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC // userOp hash
// 0x060: 0x________________________DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD // userOp sender
// 0x080: 0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE // userOp nonce
// 0x0a0: 0x_____________________________________________________________FFF // userOp initCode offset
// 0x0c0: 0x_____________________________________________________________GGG // userOp callData offset
// 0x0e0: 0xHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH // userOp accountGasLimits
// 0x100: 0xIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII // userOp preVerificationGas
// 0x120: 0xJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ // userOp gasFees
// 0x140: 0x_____________________________________________________________KKK // userOp pmData offset
// 0x160: 0x_____________________________________________________________LLL // userOp signature offset
// 0x180... // dynamic fields
}
// Converts a user op call buffer from pre user op validation hooks to user op validation.
// Performs this by writing over the selector stored in the buffer.
function convertToValidationBuffer(UOCallBuffer buffer) internal pure {
// Selector is treated as a uint32 to be right-aligned in the word.
uint32 selector = uint32(IValidationModule.validateUserOp.selector);
assembly ("memory-safe") {
// We want to write in the selector without writing over anything else in the buffer, so we save the
// length, write over a portion of the length, and restore it.
let bufferLength := mload(buffer)
mstore(add(buffer, 4), selector)
mstore(buffer, bufferLength)
}
}
// Invokes either a user op validation hook, or validation function.
function invokeUserOpCallBuffer(
UOCallBuffer buffer,
ModuleEntity moduleEntity,
bytes calldata signatureSegment
) internal returns (uint256 validationData) {
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// Load the module address and entity Id
entityId := and(shr(64, moduleEntity), 0xffffffff)
moduleAddress := shr(96, moduleEntity)
// Update the buffer with the entity Id
mstore(add(buffer, 0x24), entityId)
// Get the offset of the user op signature in the buffer.
// The PackedUserOperation starts at the 5th word in the buffer (0x20 * 4 = 0x80).
// It is the 9th element in PackedUserOp (so add 0x20 * 8 = 0x100 to the buffer start).
// So we start at 0x184, to include the selector length.
// Then, to convert from a relative to an absolute offset, we need to add the buffer start, selector,
// and to skip over the entityId, offset, and hash.
let userOpSigRelativeOffset := mload(add(buffer, 0x184))
let userOpSigAbsOffset := add(add(buffer, userOpSigRelativeOffset), 0x84)
// Copy in the signature segment
// Since the buffer's copy of the signature exceeds the length of any sub-segments, we can safely write
// over it.
mstore(userOpSigAbsOffset, signatureSegment.length)
// If there is a nonzero signature segment length, copy in the data.
if signatureSegment.length {
// Because we will be sending the data with word-aligned padding ("strict ABI encoding"), we need
// to zero out the last word of the buffer to prevent sending garbage data.
let roundedDownSignatureLength := and(signatureSegment.length, not(0x1f))
mstore(add(userOpSigAbsOffset, add(roundedDownSignatureLength, 0x20)), 0)
calldatacopy(add(userOpSigAbsOffset, 0x20), signatureSegment.offset, signatureSegment.length)
}
// The data amount we actually want to call with is:
// buffer length - word-align(oldSignature length) + word-align(newSignature length)
// Which is equivalent to:
// 4 (selector length) + 0x80 (entityId, user op offset, user op hash, signature length field)
// + userOpSigRelativeOffset + word-align(newSignature length)
let actualCallLength := add(userOpSigRelativeOffset, 0x84)
// Add in the new signature length, with word alignment. This is safe to do because the signature
// segment length is guaranteed to be less than the size of the previous entire signature length.
actualCallLength := add(actualCallLength, and(add(signatureSegment.length, 0x1f), not(0x1f)))
// Perform the call, reverting on failure or insufficient return data.
success :=
and(
// Yul evaluates expressions from right to left, so `returndatasize` will evaluate after `call`.
gt(returndatasize(), 0x1f),
call(
// If gas is the leftmost item before the call, it *should* be placed immediately before the
// call opcode and be allowed in validation.
gas(),
moduleAddress,
/*value*/
0,
/*argOffset*/
add(buffer, 0x20), // jump over 32 bytes for length
/*argSize*/
actualCallLength,
/*retOffset*/
0,
/*retSize*/
0x20
)
)
}
if (success) {
assembly ("memory-safe") {
// If the call was successful, we return the first word of the return data as the validation data.
validationData := mload(0)
}
} else {
// Revert with the appropriate error type for the selector used.
uint32 selectorUsed;
uint32 errorSelector;
assembly ("memory-safe") {
selectorUsed := and(mload(add(buffer, 0x4)), 0xffffffff)
}
if (selectorUsed == uint32(IValidationHookModule.preUserOpValidationHook.selector)) {
errorSelector = uint32(PreUserOpValidationHookReverted.selector);
} else {
errorSelector = uint32(UserOpValidationFunctionReverted.selector);
}
_revertModuleFunction(errorSelector, moduleAddress, entityId);
}
}
function allocateRuntimeValidationCallBuffer(bytes calldata callData, bytes calldata authorization)
internal
returns (RTCallBuffer result)
{
// Allocate a call to regular runtime validation. Pre runtime validation hooks lack the `account` field, so
// they won't touch the selector portion of this buffer.
bytes memory buffer = abi.encodeCall(
IValidationModule.validateRuntime,
(address(0), uint32(0), msg.sender, msg.value, callData, authorization)
);
assembly ("memory-safe") {
result := buffer
}
// Buffer contents, before update:
// 0xAAAAAAAA // selector
// 0x000: 0x________________________BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB // account
// 0x020: 0x________________________________________________________CCCCCCCC // entityId
// 0x040: 0x________________________DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD // msg.sender
// 0x060: 0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE // msg.value
// 0x080: 0x______________________________________________________________c0 // callData offset
// 0x0a0: 0x_____________________________________________________________FFF // authorization offset
// 0x0c0... // dynamic fields
// Prepare the buffer for pre-runtime validation hooks.
_prepareRuntimeCallBufferPreValidationHooks(result);
}
function invokeRuntimeCallBufferPreValidationHook(
RTCallBuffer buffer,
HookConfig hookEntity,
bytes calldata authorizationSegment
) internal {
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// Load the module address and entity Id
entityId := and(shr(64, hookEntity), 0xffffffff)
moduleAddress := shr(96, hookEntity)
// Update the buffer with the entity Id
mstore(add(buffer, 0x44), entityId)
// Get the offset of the authorization in the buffer.
// The authorization offset is the 6th word in the buffer (0x20 * 5 = 0xa0).
// We need to add the buffer length and selector length (0x24) to get the start of the authorization.
let authorizationRelativeOffset := mload(add(buffer, 0xc4))
// Convert to an absolute offset
// Add the lengths of the selector, buffer length field, and the authorization length field.
let authorizationAbsOffset := add(add(buffer, authorizationRelativeOffset), 0x44)
// Copy in the authorization segment
// Since the buffer's copy of the authorization exceeds the length of any sub-segments, we can safely
// write over it.
mstore(authorizationAbsOffset, authorizationSegment.length)
// If there is a nonzero authorization segment length, copy in the data.
if authorizationSegment.length {
// Because we will be sending the data with word-aligned padding ("strict ABI encoding"), we need
// to zero out the last word of the buffer to prevent sending garbage data.
let roundedDownAuthorizationLength := and(authorizationSegment.length, not(0x1f))
mstore(add(authorizationAbsOffset, add(roundedDownAuthorizationLength, 0x20)), 0)
// Copy the authorization segment from calldata into the correct location in the buffer.
calldatacopy(
add(authorizationAbsOffset, 0x20), authorizationSegment.offset, authorizationSegment.length
)
}
// The data amount we actually want to call with is:
// buffer length - word-align(oldAuthorization length) + word-align(newAuthorization length) - 0x20 (to
// skip `account`),
// This is equivalent to:
// 4 (selector length) + 0x20 (authorization length field) + authorizationRelativeOffset +
// word-align(newAuthorization length)
let actualCallLength := add(authorizationRelativeOffset, 0x24)
// Add in the new authorization length, with word alignment. This is safe to do because the
// authorization segment length is guaranteed to be less than the size of the previous entire
// authorization length.
actualCallLength := add(actualCallLength, and(add(authorizationSegment.length, 0x1f), not(0x1f)))
// Perform the call
success :=
call(
gas(),
moduleAddress,
/*value*/
0,
/*argOffset*/
add(buffer, 0x40), // jump over 32 bytes for length, and another 32 bytes for the account
/*argSize*/
actualCallLength,
/*retOffset*/
codesize(),
/*retSize*/
0
)
}
if (!success) {
_revertModuleFunction(uint32(PreRuntimeValidationHookReverted.selector), moduleAddress, entityId);
}
}
// Note: we need to add an extra check for codesize > 0 on the module, otherwise EOAs added as runtime
// validation would authorize all calls.
function invokeRuntimeCallBufferValidation(
RTCallBuffer buffer,
ModuleEntity moduleEntity,
bytes calldata authorizationSegment
) internal {
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// Load the module address and entity Id
entityId := and(shr(64, moduleEntity), 0xffffffff)
moduleAddress := shr(96, moduleEntity)
// Store the account in the `account` field.
mstore(add(buffer, 0x24), address())
// Update the buffer with the entity Id
mstore(add(buffer, 0x44), entityId)
// Fix the calldata offsets of `callData` and `authorization`, due to including the `account` field for
// runtime validation.
// The offset of calldata should be reset back to 0x0c0. For pre-validation hooks, it was set to 0x0a0.
mstore(add(buffer, 0xa4), 0xc0)
// Get the offset of the authorization in the buffer.
// The authorization offset is the 6th word in the buffer (0x20 * 5 = 0xa0).
let authorizationOffsetPtr := add(buffer, 0xc4)
// Get the stored value. This will be the edited value for preRuntimeValidationHooks.
let authorizationRelativeOffset := mload(authorizationOffsetPtr)
// Fix the stored offset value by adding 0x20.
authorizationRelativeOffset := add(authorizationRelativeOffset, 0x20)
// Correct the authorization relative offset
mstore(authorizationOffsetPtr, authorizationRelativeOffset)
// Convert to an absolute offset
// Add the lengths of the selector and buffer length field.
let authorizationAbsOffset := add(add(buffer, authorizationRelativeOffset), 0x24)
// Copy in the authorization segment
// Since the buffer's copy of the authorization exceeds the length of any sub-segments, we can safely
// write over it.
mstore(authorizationAbsOffset, authorizationSegment.length)
// If there is a nonzero authorization segment length, copy in the data.
if authorizationSegment.length {
// Because we will be sending the data with word-aligned padding ("strict ABI encoding"), we need
// to zero out the last word of the buffer to prevent sending garbage data.
let roundedDownAuthorizationLength := and(authorizationSegment.length, not(0x1f))
mstore(add(authorizationAbsOffset, add(roundedDownAuthorizationLength, 0x20)), 0)
// Copy the authorization segment from calldata into the correct location in the buffer.
calldatacopy(
add(authorizationAbsOffset, 0x20), authorizationSegment.offset, authorizationSegment.length
)
}
// The data amount we actually want to call with is:
// buffer length - word-align(oldAuthorization length) + word-align(newAuthorization length) - 0x20 (to
// skip `account`),
// This is equivalent to:
// 4 (selector length) + 0x20 (authorization length field) + authorizationRelativeOffset +
// word-align(newAuthorization length)
let actualCallLength := add(authorizationRelativeOffset, 0x24)
// Add in the new authorization length, with word alignment.
// This is safe to do because the authorization segment length is guaranteed to be less than the size
// of the previous entire authorization length.
actualCallLength := add(actualCallLength, and(add(authorizationSegment.length, 0x1f), not(0x1f)))
// Before performing the call, we need to check that the module has code.
// IValidationModule.validateRuntime has no return value, so an EOA added as a validation (perhaps for
// direct call validation) would authorize all calls, which is unsafe. Solidity inserts this check by
// default, but when we're making calls manually via call buffers, we need to do the check ourselves.
if iszero(extcodesize(moduleAddress)) { revert(0, 0) }
// Perform the call
success :=
call(
gas(),
moduleAddress,
/*value*/
0,
/*argOffset*/
add(buffer, 0x20), // jump over 32 bytes for length
/*argSize*/
actualCallLength,
/*retOffset*/
codesize(),
/*retSize*/
0
)
}
if (!success) {
_revertModuleFunction(uint32(RuntimeValidationFunctionReverted.selector), moduleAddress, entityId);
}
}
function executeRuntimeSelfCall(RTCallBuffer buffer, bytes calldata data) internal {
bool bufferExists;
assembly ("memory-safe") {
bufferExists := iszero(iszero(buffer))
}
if (bufferExists) {
// We don’t know whether the RTCallBuffer was called only with pre-hooks (skipping RT validation
// updates because of SMA), or if it was called with a buffer after a module based RT validation, which
// would cause the relative offset of calldata to change. So, when loading the callData for self-exec,
// we must not load the relative calldata offset, and must instead use an absolute offset from the
// start of the buffer. Using an absolute offset is safe because the abi encoder will only generate
// “strict encoding mode” encodings, so it is guaranteed to be in that location.
bytes memory callData;
assembly ("memory-safe") {
// Get the memory address of the length of callData in the buffer.
// Because we don't know whether this will be invoked with the RTCallBuffer still as a
callData := add(buffer, 0xe4)
}
// Perform the call, bubbling up revert data on failure.
callBubbleOnRevert(address(this), msg.value, callData);
} else {
// No buffer exists yet, just copy the data to memory transiently and execute it.
callBubbleOnRevertTransient(address(this), msg.value, data);
}
}
// Convert a RTCallBuffer to a pre hook call buffer, if the RTCallBuffer exists. If not, allocate a new one.
function convertToPreHookCallBuffer(RTCallBuffer buffer, bytes calldata data)
internal
view
returns (PHCallBuffer result)
{
bool bufferExists;
assembly ("memory-safe") {
bufferExists := iszero(iszero(buffer))
}
if (bufferExists) {
// The buffer already has most of what we need, but we need to update the pointer, length, and data
// offset.
// Buffer transformation:
// 0xAAAAAAAA // selector
// 0x000: 0x________________________BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB account -> discarded
// 0x020: 0x________________________________________________________CCCCCCCC entityId -> selector
// 0x040: 0x________________________DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD msg.sender -> entityId
// 0x060: 0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE msg.value -> sender
// 0x080: 0x______________________________________________________________c0 callData offset -> value
// 0x0a0: 0x_____________________________________________________________FFF auth offset -> cd offset
// 0x0c0: 0x_____________________________________________________________GGG callData length -> stays
// This new buffer will be a subset of the existing buffer.
PHCallBuffer newBuffer;
// Right-align the selector
uint32 selector = uint32(IExecutionHookModule.preExecutionHook.selector);
assembly ("memory-safe") {
// We don’t know whether the RTCallBuffer was called only with pre-hooks (skipping RT validation
// updates because of SMA), or if it was called with a buffer after a module based RT validation,
// which would cause the relative offset of calldata to change. So, when converting to a pre hook
// buffer, we must not load the relative calldata offset, and must instead use an absolute offset
// from the start of the buffer. Using an absolute offset is safe because the abi encoder will only
// generate “strict encoding mode” encodings, so it is guaranteed to be in that location.
let callDataAbsOffset := add(buffer, 0xe4)
let callDataSize := mload(callDataAbsOffset)
// We must squash existing elements, because the stored offset of authorization causes the other
// fields to not be aligned.
// We need to copy in the selector, entityId, sender, value, and relative callData offset.
// Step back 5 words, to start pasting in the new data.
let workingPtr := add(buffer, 0x44)
// Paste in the selector
mstore(workingPtr, selector)
// skip pasting in the entity ID, the caller will squash this later
workingPtr := add(workingPtr, 0x40)
// Paste in msg.sender
mstore(workingPtr, caller())
workingPtr := add(workingPtr, 0x20)
// Paste in msg.value
mstore(workingPtr, callvalue())
workingPtr := add(workingPtr, 0x20)
// Paste in the relative callData offset. This is now 0xa0, to show that it is after the entityId,
// sender, value, and offset fields.
mstore(workingPtr, 0x80)
// Now store the buffer length. This will be directly before the selector, and the returned pointer
// will point to this word in memory.
newBuffer := add(buffer, 0x40)
// word-align the callDataSize
callDataSize := and(add(callDataSize, 0x1f), not(0x1f))
mstore(newBuffer, add(callDataSize, 0xa4))
// See `allocateRuntimeCallBuffer` for the buffer layout.
}
return newBuffer;
} else {
// We need to allocate and return a new buffer.
return allocatePreExecHookCallBuffer(data);
}
}
// Allocate a buffer to call a pre-execution hook.
function allocatePreExecHookCallBuffer(bytes calldata data) internal view returns (PHCallBuffer) {
bytes memory newBuffer =
abi.encodeCall(IExecutionHookModule.preExecutionHook, (uint32(0), msg.sender, msg.value, data));
PHCallBuffer result;
assembly ("memory-safe") {
result := newBuffer
}
return result;
// Buffer contents:
// 0xAAAAAAAA // selector
// 0x000: 0x________________________________________________________BBBBBBBB // entityId
// 0x020: 0x________________________CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC // sender
// 0x040: 0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD // value
// 0x060: 0x______________________________________________________________80 // callData offset
// 0x080... // dynamic fields
}
function invokePreExecHook(PHCallBuffer buffer, HookConfig hookEntity)
internal
returns (uint256 returnedBytesSize)
{
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// Load the module address and entity Id
entityId := and(shr(64, hookEntity), 0xffffffff)
moduleAddress := shr(96, hookEntity)
// Update the buffer with the entity Id
mstore(add(buffer, 0x24), entityId)
// Perform the call, storing the first two words of return data into scratch space.
success :=
call(
gas(),
moduleAddress,
/*value*/
0,
/*argOffset*/
add(buffer, 0x20), // jump over 32 bytes for length
/*argSize*/
mload(buffer),
/*retOffset*/
0,
/*retSize*/
0x40
)
// Need at least 64 bytes of return data to be considered successful.
success := and(success, gt(returndatasize(), 0x3f))
// Only accept return data of "strict encoding" form, where the relative offset is exactly 0x20.
success := and(success, eq(mload(0), 0x20))
// Ensure that the reported length of return data does not exceed the actual length.
// aka the stored length <= retundatasize() - 0x40 (for the first two values)
// No opcode for lte, so the expression equals:
// stored length < retundatasize() - 0x3f
// Underflow doesn't matter, because success is false anyways if length < 0x40.
returnedBytesSize := mload(0x20)
success := and(success, lt(returnedBytesSize, sub(returndatasize(), 0x3f)))
}
if (!success) {
_revertModuleFunction(uint32(PreExecHookReverted.selector), moduleAddress, entityId);
}
}
// Converts a PreHookCallBuffer to a `bytes memory`, to use for a self-call in `executeUserOp`.
// Handles skipping ahead an extra 4 bytes to omit the `executeUserOp` selector, and updates the stored length
// to do so. This will edit the buffer.
function getExecuteUOCallData(PHCallBuffer buffer, bytes calldata callData)
internal
pure
returns (bytes memory)
{
bool bufferExists;
assembly ("memory-safe") {
bufferExists := iszero(iszero(buffer))
}
if (bufferExists) {
// At this point, the buffer contains the encoded call to the pre-exec hook, but the data being sent is
// `msg.data`, not `userOp.callData`. Re-decoding the user op struct's callData is error-prone, so
// instead we just copy-in the provided userOp.callData, squashing the buffer. This is fine because the
// buffer will not be reused after this operation.
bytes memory result;
assembly ("memory-safe") {
// Safe to do unchecked because there must have been at least 4 bytes of callData for the
// EntryPoint to call `executeUserOp`.
let actualCallDataLength := sub(callData.length, 4)
// Write over the existing buffer
result := buffer
// Store the new length
mstore(result, actualCallDataLength)
if actualCallDataLength {
// We don't need to write a zero word because this data will not be word-aligned before sending
// Copy in the callData
calldatacopy(add(result, 0x20), add(callData.offset, 4), actualCallDataLength)
}
}
return result;
} else {
// No buffer exists yet, just copy the data to memory and return it.
// Skip the first 4 bytes in this function to save the computation on the buffer reuse case.
return callData[4:];
}
}
// DensePostHookData layout
// Very tricky to navigate, because we must do so backwards.
// type ~= struct[] but in reverse, the caller must advance through it backwards
// N instances of:
// - post hook address (will be squashed with the selector later, during invocation)
// - post hook entity Id
// - fixed preExecHookData offset (always 0x40)
// - preExecHookData length
// - var-length data (right-padded with zeros to be word aligned)
// - segment (struct) length (not counting this word, to traverse backwards)
// 1 count of post hooks to run. The returned memory pointer will point to this value.
function doPreHooks(HookConfig[] memory hooks, PHCallBuffer callBuffer)
internal
returns (DensePostHookData result)
{
uint256 hooksLength = hooks.length;
// How many "post hooks to run" there are.
uint256 resultCount;
// Where in memory to start writing the next "post hook to run".
bytes32 workingMemPtr;
// Start allocating the dense buffer. From this point out, avoid any high-level memory allocations,
// otherwise the data-in-flight may be corrupted.
assembly ("memory-safe") {
workingMemPtr := mload(0x40)
}
// Run the pre hooks and copy their return data to the dense post hooks data buffer array, if an associated
// post exec hook exists.
for (uint256 i = hooksLength; i > 0;) {
// Decrement here, instead of in the loop update step, to handle the case where the length is 0.
unchecked {
--i;
}
HookConfig hookConfig = hooks[i];
if (hookConfig.hasPreHook()) {
uint256 returnedBytesSize = invokePreExecHook(callBuffer, hookConfig);
// If there is an associated post exec hook, save the return data.
if (hookConfig.hasPostHook()) {
// Case: both pre and post exec hook, need to save hook info, and pre hook return data
workingMemPtr = _appendPostHookToRun(workingMemPtr, hookConfig, returnedBytesSize);
++resultCount;
}
} else if (hookConfig.hasPostHook()) {
// If there is no pre hook, but there is a post hook, we still need to save a placeholder for the
// post hook return data.
// Case: only post exec hook, need to save hook info, and no pre hook return data
// Call the append function with legnth 0 to put no pre hook return data.
workingMemPtr = _appendPostHookToRun(workingMemPtr, hookConfig, 0);
++resultCount;
}
}
// Save the length, return a pointer to the length, and update the FMP
assembly ("memory-safe") {
mstore(workingMemPtr, resultCount)
result := workingMemPtr
workingMemPtr := add(workingMemPtr, 0x20)
mstore(0x40, workingMemPtr)
}
}
function doCachedPostHooks(DensePostHookData postHookData) internal {
uint256 postHookCount;
uint256 workingMemPtr;
assembly ("memory-safe") {
postHookCount := mload(postHookData)
workingMemPtr := sub(postHookData, 0x20)
}
uint32 selector = uint32(IExecutionHookModule.postExecutionHook.selector);
// Run the post hooks.
// This is tricky, unlike normal, we must traverse the data backwards, because the post exec hooks should
// be executed in reverse order of the pre exec hooks.
for (uint256 i = 0; i < postHookCount; ++i) {
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// The last word of each segment is the segment length
let segmentLength := mload(workingMemPtr)
// Step the working memory pointer back to the start of the segment, and preserve a copy to
// continue the loop
workingMemPtr := sub(workingMemPtr, segmentLength)
let segmentStart := workingMemPtr
// Load the post hook address
moduleAddress := mload(workingMemPtr)
// Load the entity id, just for the revert message
entityId := mload(add(workingMemPtr, 0x20))
// Squash the post hook address field with the selector
mstore(workingMemPtr, selector)
// Advance the working mem pointer to just before the selector, to prepare to make the call.
workingMemPtr := add(workingMemPtr, 0x1c)
// Compute the total call length, including the selector
// This will be seggment length - 0x1c (28), to take out the space not used in the selector
let callLength := sub(segmentLength, 0x1c)
// Perform the call
success :=
call(
gas(),
moduleAddress,
/*value*/
0,
/*argOffset*/
workingMemPtr,
/*argSize*/
callLength,
/*retOffset*/
codesize(),
/*retSize*/
0
)
// Step the working mem pointer back to the previous segment
workingMemPtr := sub(segmentStart, 0x20)
}
if (!success) {
_revertModuleFunction(uint32(PostExecHookReverted.selector), moduleAddress, entityId);
}
}
}
function allocateSigCallBuffer(bytes32 hash, bytes calldata signature)
internal
view
returns (SigCallBuffer result)
{
bytes memory buffer = abi.encodeCall(
IValidationModule.validateSignature, (address(0), uint32(0), msg.sender, hash, signature)
);
assembly ("memory-safe") {
result := buffer
}
// Buffer contents, before update:
// 0xAAAAAAAA // selector
// 0x000: 0x________________________BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB // account
// 0x020: 0x________________________________________________________CCCCCCCC // entityId
// 0x040: 0x________________________DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD // msg.sender
// 0x060: 0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE // hash
// 0x080: 0x______________________________________________________________a0 // signature offset
// 0x0a0... // dynamic fields
// Prepare the buffer for pre-signature validation hooks.
_prepareSigValidationCallBufferPreSigValidationHooks(result);
}
function invokePreSignatureValidationHook(
SigCallBuffer buffer,
HookConfig hookEntity,
bytes calldata signatureSegment
) internal view {
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// Load the module address and entity id
entityId := and(shr(64, hookEntity), 0xffffffff)
moduleAddress := shr(96, hookEntity)
// Update the buffer with the entity Id
mstore(add(buffer, 0x44), entityId)
// Copy in the signature segment
// Since the buffer's copy of the signature exceeds the length of any sub-segments, we can safely write
// over it.
mstore(add(buffer, 0xc4), signatureSegment.length)
// If there is a nonzero signature segment length, copy in the data.
if signatureSegment.length {
// Because we will be sending the data with word-aligned padding ("strict ABI encoding"), we need
// to zero out the last word of the buffer to prevent sending garbage data.
let roundedDownSignatureLength := and(signatureSegment.length, not(0x1f))
mstore(add(add(buffer, 0xe4), roundedDownSignatureLength), 0)
// Copy in the data
calldatacopy(add(buffer, 0xe4), signatureSegment.offset, signatureSegment.length)
}
// The data amount we actually want to call with is:
// 0xa4 (4 byte selector + 5 words of data: entity id, sender, hash, signature offset, signature
// length) + word-align(signature length)
let actualCallLength := add(0xa4, and(add(signatureSegment.length, 0x1f), not(0x1f)))
// Perform the call
success :=
staticcall(
gas(),
moduleAddress,
/*argOffset*/
add(buffer, 0x40), // jump over 32 bytes for length, and another 32 bytes for the account
/*argSize*/
actualCallLength,
/*retOffset*/
0,
/*retSize*/
0x20
)
}
if (!success) {
_revertModuleFunction(uint32(PreSignatureValidationHookReverted.selector), moduleAddress, entityId);
}
}
function invokeSignatureValidation(
SigCallBuffer buffer,
ModuleEntity validationFunction,
bytes calldata signatureSegment
) internal view returns (bytes4 result) {
bool success;
address moduleAddress;
uint32 entityId;
assembly ("memory-safe") {
// Load the module address and entity id
entityId := and(shr(64, validationFunction), 0xffffffff)
moduleAddress := shr(96, validationFunction)
// Store the account in the `account` field.
mstore(add(buffer, 0x24), address())
// Update the buffer with the entity Id
mstore(add(buffer, 0x44), entityId)
// Fix the calldata offsets of `signature`, due to including the `account` field for signature
// validation.
mstore(add(buffer, 0xa4), 0xa0)
// Copy in the signature segment
// Since the buffer's copy of the signature exceeds the length of any sub-segments, we can safely write
// over it.
mstore(add(buffer, 0xc4), signatureSegment.length)
// If there is a nonzero signature segment length, copy in the data.
if signatureSegment.length {
// Because we will be sending the data with word-aligned padding ("strict ABI encoding"), we need
// to zero out the last word of the buffer to prevent sending garbage data.
let roundedDownSignatureLength := and(signatureSegment.length, not(0x1f))
mstore(add(add(buffer, 0xe4), roundedDownSignatureLength), 0)
// Copy in the data
calldatacopy(add(buffer, 0xe4), signatureSegment.offset, signatureSegment.length)
}
// The data amount we actually want to call with is:
// 0xc4 (4 byte selector + 6 words of data: account, entity id, sender, hash, signature offset,
// signature length) + word-align(signature length)
let actualCallLength := add(0xc4, and(add(signatureSegment.length, 0x1f), not(0x1f)))
// Perform the call
success :=
and(
// Yul evaluates expressions from right to left, so `returndatasize` will evaluate after
// `staticcall`.
gt(returndatasize(), 0x1f),
staticcall(
gas(),
moduleAddress,
/*argOffset*/
add(buffer, 0x20), // jump over 32 bytes for length, and another 32 bytes for the account
/*argSize*/
actualCallLength,
/*retOffset*/
0,
/*retSize*/
0x20
)
)
}
if (success) {
assembly ("memory-safe") {
// Otherwise, we return the first word of the return data as the signature validation result
result := mload(0)
// If any of the lower 28 bytes are nonzero, it would be an abi decoding failure.
if shl(32, result) { revert(0, 0) }
}
} else {
_revertModuleFunction(uint32(SignatureValidationFunctionReverted.selector), moduleAddress, entityId);
}
}
/// @notice Appends a post hook to run to the dense post hook data buffer.
/// @param workingMemPtr The current working memory pointer
/// @param hookConfig The hook configuration
/// @param returnedBytesSize The size of the returned bytes from the pre hook
/// @return The new working memory pointer
function _appendPostHookToRun(bytes32 workingMemPtr, HookConfig hookConfig, uint256 returnedBytesSize)
private
pure
returns (bytes32)
{
// Each segment starts out at a length of 4 words:
// - post hook address
// - post hook entity Id
// - fixed preExecHookData offset (always 0x40)
// - preHookReturnData length
// Add to this the word-aligned length of the pre hook return data.
uint256 segmentLength = 0x80;
assembly ("memory-safe") {
// Load the module address and entity Id
let entityId := and(shr(64, hookConfig), 0xffffffff)
let moduleAddress := shr(96, hookConfig)
// Get the word-aligned data to copy length
let alignedDataLength := and(add(returnedBytesSize, 0x1f), not(0x1f))
segmentLength := add(segmentLength, alignedDataLength)
// Start writing to memory:
// Store the post hook address
mstore(workingMemPtr, moduleAddress)
workingMemPtr := add(workingMemPtr, 0x20)
// Store the post hook entity Id
mstore(workingMemPtr, entityId)
workingMemPtr := add(workingMemPtr, 0x20)
// Store the fixed preExecHookData offset
mstore(workingMemPtr, 0x40)
workingMemPtr := add(workingMemPtr, 0x20)
// Store the preHookReturnData length
mstore(workingMemPtr, returnedBytesSize)
workingMemPtr := add(workingMemPtr, 0x20)
// Copy in the pre hook return data, if any exists
if returnedBytesSize {
// Zero out the last memory word to encode in strict ABI mode
let roundedDownDataLength := and(returnedBytesSize, not(0x1f))
mstore(add(workingMemPtr, roundedDownDataLength), 0)
// Copy in the data
returndatacopy(workingMemPtr, 0x40, returnedBytesSize)
workingMemPtr := add(workingMemPtr, alignedDataLength)
}
// Store the overall segment length at the end
mstore(workingMemPtr, segmentLength)
workingMemPtr := add(workingMemPtr, 0x20)
}
return workingMemPtr;
}
function _revertModuleFunction(uint32 errorSelector, address moduleAddress, uint32 entityId) private pure {
// All of the module function reverts have the same parameter layout:
// - module address
// - entity Id
// - revert data
assembly ("memory-safe") {
let m := mload(0x40)
// Write in order of entityId -> address -> selector, to avoid masking or shifts.
mstore(add(m, 0x18), entityId)
mstore(add(m, 0x14), moduleAddress)
mstore(m, errorSelector)
mstore(add(m, 0x40), 0x40) // fixed offset for the revert data
mstore(add(m, 0x60), returndatasize())
if returndatasize() {
// Store a zero in the last word of the revert data, to do strict ABI-encoding for the error.
let roundedDownDataLength := and(returndatasize(), not(0x1f))
mstore(add(m, add(0x80, roundedDownDataLength)), 0)
returndatacopy(add(m, 0x80), 0, returndatasize())
}
let roundedUpDataLength := and(add(returndatasize(), 0x1f), not(0x1f))
// 4 bytes for the selector, and 0x60 for the 3 words of fixed-size data.
let totalRevertDataLength := add(0x64, roundedUpDataLength)
revert(add(m, 0x1c), totalRevertDataLength)
}
}
function _prepareRuntimeCallBufferPreValidationHooks(RTCallBuffer buffer) private pure {
uint32 selector = uint32(IValidationHookModule.preRuntimeValidationHook.selector);
assembly ("memory-safe") {
// Update the buffer with the selector. This will squash a portion of the `account` param for runtime
// validation, but that will be restored before calling.
mstore(add(buffer, 0x24), selector)
// Fix the calldata offsets of `callData` and `authorization`, due to excluding the `account` field.
// The offset of calldata starts out as 0x0c0, but for pre-validation hooks, it should be 0x0a0.
mstore(add(buffer, 0xa4), 0xa0)
// The offset of authorization should be decremented by one word.
let authorizationOffsetPtr := add(buffer, 0xc4)
// Get the stored value. This will be wrong for preRuntimeValidationHooks, because the buffer size is
// smaller by 1 word.
let authorizationOffset := mload(authorizationOffsetPtr)
// Fix the stored offset value
mstore(authorizationOffsetPtr, sub(authorizationOffset, 0x20))
}
}
function _prepareSigValidationCallBufferPreSigValidationHooks(SigCallBuffer buffer) private pure {
uint32 selector = uint32(IValidationHookModule.preSignatureValidationHook.selector);
assembly ("memory-safe") {
// Update the buffer with the selector. This will squash a portion of the `account` param for signature
// validation, but that will be restored before calling.
mstore(add(buffer, 0x24), selector)
// Fix the calldata offset of `signature`, due to excluding the `account` field.
// The offset of the signature starts out as 0xa0, but for pre-validation hooks, it should be 0x80.
mstore(add(buffer, 0xa4), 0x80)
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {DIRECT_CALL_VALIDATION_ENTITY_ID} from "@erc6900/reference-implementation/helpers/Constants.sol";
import {ModuleEntity, ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol";
import {
ValidationConfig,
ValidationConfigLib
} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol";
import {ValidationStorage} from "../account/AccountStorage.sol";
// A type representing a validation lookup key and flags for validation options.
// The validation lookup key is a tagged union between a direct call validation address and a validation entity ID.
type ValidationLocator is uint168;
// Layout:
// Unused
// 0x0000000000000000000000__________________________________________
// Either the direct call validation's address, or the entity ID for non-direct-call validation.
// 0x______________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__
// Validation options
// 0x______________________________________________________________BB
// ValidationOptions layout:
// 0b00000___ // Unused
// 0b_____A__ // is direct call validation (union tag)
// 0b______B_ // has deferred action
// 0b_______C // is global validation
// A type representing only the validation lookup key, with validation options masked out except for the
// direct call validation flag.
type ValidationLookupKey is uint168;
using ValidationLocatorLib for ValidationLocator global;
using ValidationLocatorLib for ValidationLookupKey global;
library ValidationLocatorLib {
using ValidationConfigLib for ValidationConfig;
uint8 internal constant _VALIDATION_TYPE_GLOBAL = 1;
uint8 internal constant _HAS_DEFERRED_ACTION = 2;
uint8 internal constant _IS_DIRECT_CALL_VALIDATION = 4;
function moduleEntity(ValidationLookupKey _lookupKey, ValidationStorage storage validationStorage)
internal
view
returns (ModuleEntity result)
{
if (_lookupKey.isDirectCallValidation()) {
result = ModuleEntityLib.pack(_lookupKey.directCallAddress(), DIRECT_CALL_VALIDATION_ENTITY_ID);
} else {
result = ModuleEntityLib.pack(validationStorage.module, _lookupKey.entityId());
}
}
// User op nonce, 4337 mandated layout:
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA________________ // Parallel Nonce Key
// 0x________________________________________________BBBBBBBBBBBBBBBB // Sequential Nonce Key
// User op nonce, Alchemy MA usage:
// With non-direct call validation
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__________________________ // Parallel Nonce Key
// 0x______________________________________BBBBBBBB__________________ // Validation Entity ID
// 0x______________________________________________CC________________ // Options byte
// 0x________________________________________________BBBBBBBBBBBBBBBB // Sequential Nonce Key
// With direct call validation
// 0xAAAAAA__________________________________________________________ // Parallel Nonce Key
// 0x______BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB__________________ // Caller address of direct-call
// validation
// 0x______________________________________________CC________________ // Options byte
// 0x________________________________________________BBBBBBBBBBBBBBBB // Sequential Nonce Key
function loadFromNonce(uint256 nonce) internal pure returns (ValidationLocator result) {
assembly ("memory-safe") {
nonce := shr(64, nonce)
let validationType := and(nonce, _IS_DIRECT_CALL_VALIDATION)
switch validationType
case 0 {
// If not using direct call validation, the validation locator contains a 4-byte entity ID
// Mask it to the lower 5 bytes
result := and(nonce, 0xFFFFFFFFFF)
}
default {
// If using direct call validation, the validation locator contains a 20-byte address
// Mask it to the lower 21 bytes
result := and(nonce, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
}
// executeRuntimeValidation authorization layout, and isValidSignature signature layout
// [1-byte options][4-byte validation id OR 20-byte address of direct call validation][remainder]
// With non-direct call validation
// 0xAA______________ // Validation Type
// 0x__BBBBBBBB______ // Validation Entity ID
// 0x__________CCC... // Remainder
// With direct call validation
// 0xAA______________________________________________ // Validation Type
// 0x__BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB______ // Caller address of direct-call validation
// 0x__________________________________________CCC... // Remainder
function loadFromSignature(bytes calldata signature)
internal
pure
returns (ValidationLocator result, bytes calldata remainder)
{
assembly ("memory-safe") {
// Regular validation requires at least 5 bytes. Direct call validation requires at least 21 bytes,
// checked later.
if lt(signature.length, 5) { revert(0, 0) }
result := calldataload(signature.offset)
let validationOptions := shr(248, result)
switch and(validationOptions, _IS_DIRECT_CALL_VALIDATION)
case 0 {
// If not using direct call validation, the validation locator contains a 32-byte entity ID
// Result contains:
// 0xAA______________________________________________________________ // Validation Type
// 0x__BBBBBBBB______________________________________________________ // Validation Entity ID
// 0x__________CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC // Remainder bits and/or
// zeros
// We need to clear the upper byte by shifting left 1 bytes (8 bits), then shift right 28 bytes
// (224 bits), leaving only the entity ID.
result := shr(224, shl(8, result))
// Next, we need to set the validation type, which is 0 in this branch
result := or(shl(8, result), validationOptions)
// Advance the remainder by 5 bytes
remainder.offset := add(signature.offset, 5)
remainder.length := sub(signature.length, 5)
}
default {
// Direct call validation requires at least 21 bytes
if lt(signature.length, 21) { revert(0, 0) }
// If using direct call validation, the validation locator contains a 20-byte address
// Result contains:
// 0xAA______________________________________________________________ // Validation Type
// 0x__BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB______________________ // Caller address of
// direct-call validation
// 0x__________________________________________CCCCCCCCCCCCCCCCCCCCCC // Remainder bits and/or
// zeros
// So we need to clear the upper byte by shifting left 1 bytes (8 bits), then shift right 12
// bytes (96 bits) to get the address.
result := shr(96, shl(8, result))
// Next, we need to set the validation type
result := or(shl(8, result), validationOptions)
// Advance the remainder by 21 bytes
remainder.offset := add(signature.offset, 21)
remainder.length := sub(signature.length, 21)
}
}
}
// Only safe to call if the lookup has been asserted to be a direct call validation.
function directCallAddress(ValidationLookupKey _lookupKey) internal pure returns (address result) {
assembly ("memory-safe") {
result := and(shr(8, _lookupKey), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
// Only safe to call if the lookup has been asserted to be a non-direct call validation.
function entityId(ValidationLookupKey _lookupKey) internal pure returns (uint32 result) {
assembly ("memory-safe") {
result := and(shr(8, _lookupKey), 0xFFFFFFFF)
}
}
function isGlobal(ValidationLocator locator) internal pure returns (bool) {
return (ValidationLocator.unwrap(locator) & _VALIDATION_TYPE_GLOBAL) != 0;
}
function hasDeferredAction(ValidationLocator locator) internal pure returns (bool) {
return (ValidationLocator.unwrap(locator) & _HAS_DEFERRED_ACTION) != 0;
}
function isDirectCallValidation(ValidationLookupKey _lookupKey) internal pure returns (bool) {
return (ValidationLookupKey.unwrap(_lookupKey) & _IS_DIRECT_CALL_VALIDATION) != 0;
}
function configToLookupKey(ValidationConfig validationConfig)
internal
pure
returns (ValidationLookupKey result)
{
if (validationConfig.entityId() == DIRECT_CALL_VALIDATION_ENTITY_ID) {
result = ValidationLookupKey.wrap(
uint168(uint160(validationConfig.module())) << 8 | _IS_DIRECT_CALL_VALIDATION
);
} else {
result = ValidationLookupKey.wrap(uint168(uint160(validationConfig.entityId())) << 8);
}
}
function moduleEntityToLookupKey(ModuleEntity _moduleEntity)
internal
pure
returns (ValidationLookupKey result)
{
(address module, uint32 _entityId) = ModuleEntityLib.unpack(_moduleEntity);
if (_entityId == DIRECT_CALL_VALIDATION_ENTITY_ID) {
result = ValidationLookupKey.wrap(uint168(uint160(module)) << 8 | _IS_DIRECT_CALL_VALIDATION);
} else {
result = ValidationLookupKey.wrap(uint168(uint160(_entityId)) << 8);
}
}
function directCallLookupKey(address directCallValidation)
internal
pure
returns (ValidationLookupKey result)
{
result = ValidationLookupKey.wrap(uint168(uint160(directCallValidation)) << 8 | _IS_DIRECT_CALL_VALIDATION);
}
function lookupKey(ValidationLocator locator) internal pure returns (ValidationLookupKey result) {
assembly ("memory-safe") {
result := and(locator, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04)
}
}
// Packing functions. These should not be used in the account, but in scripts and tests.
function pack(uint32 _entityId, bool _isGlobal, bool _hasDeferredAction)
internal
pure
returns (ValidationLocator)
{
uint168 result = uint168(_entityId) << 8;
if (_isGlobal) {
result |= _VALIDATION_TYPE_GLOBAL;
}
if (_hasDeferredAction) {
result |= _HAS_DEFERRED_ACTION;
}
return ValidationLocator.wrap(result);
}
function packDirectCall(address directCallValidation, bool _isGlobal, bool _hasDeferredAction)
internal
pure
returns (ValidationLocator)
{
uint168 result = uint168(uint160(directCallValidation)) << 8 | _IS_DIRECT_CALL_VALIDATION;
if (_isGlobal) {
result |= _VALIDATION_TYPE_GLOBAL;
}
if (_hasDeferredAction) {
result |= _HAS_DEFERRED_ACTION;
}
return ValidationLocator.wrap(result);
}
function packNonce(uint32 validationEntityId, bool _isGlobal, bool _hasDeferredAction)
internal
pure
returns (uint256 result)
{
result = uint256(validationEntityId) << 8;
if (_isGlobal) {
result |= _VALIDATION_TYPE_GLOBAL;
}
if (_hasDeferredAction) {
result |= _HAS_DEFERRED_ACTION;
}
// Finally, shift left to make space for the sequential nonce key
result <<= 64;
}
function packNonceDirectCall(address directCallValidation, bool _isGlobal, bool _hasDeferredAction)
internal
pure
returns (uint256 result)
{
result = uint256(uint160(directCallValidation)) << 8 | _IS_DIRECT_CALL_VALIDATION;
if (_isGlobal) {
result |= _VALIDATION_TYPE_GLOBAL;
}
if (_hasDeferredAction) {
result |= _HAS_DEFERRED_ACTION;
}
// Finally, shift left to make space for the sequential nonce key
result <<= 64;
}
function packSignature(uint32 validationEntityId, bool _isGlobal, bytes memory signature)
internal
pure
returns (bytes memory result)
{
uint8 options = 0;
if (_isGlobal) {
options |= _VALIDATION_TYPE_GLOBAL;
}
return bytes.concat(abi.encodePacked(options, uint32(validationEntityId)), signature);
}
function packSignatureDirectCall(
address directCallValidation,
bool _isGlobal,
bool _hasDeferredAction,
bytes memory signature
) internal pure returns (bytes memory result) {
uint8 options = _IS_DIRECT_CALL_VALIDATION;
if (_isGlobal) {
options |= _VALIDATION_TYPE_GLOBAL;
}
if (_hasDeferredAction) {
options |= _HAS_DEFERRED_ACTION;
}
return bytes.concat(abi.encodePacked(options, uint160(directCallValidation)), signature);
}
// Converts a module entity to a locator.
function packFromModuleEntity(ModuleEntity _moduleEntity, bool _isGlobal, bool _hasDeferredAction)
internal
pure
returns (ValidationLocator)
{
uint168 result;
(address module, uint32 _entityId) = ModuleEntityLib.unpack(_moduleEntity);
if (_entityId == DIRECT_CALL_VALIDATION_ENTITY_ID) {
result = uint168(uint160(module)) << 8 | _IS_DIRECT_CALL_VALIDATION;
} else {
result = uint168(_entityId) << 8;
}
if (_isGlobal) {
result |= _VALIDATION_TYPE_GLOBAL;
}
if (_hasDeferredAction) {
result |= _HAS_DEFERRED_ACTION;
}
return ValidationLocator.wrap(result);
}
// Operators
function eq(ValidationLookupKey a, ValidationLookupKey b) internal pure returns (bool) {
return ValidationLookupKey.unwrap(a) == ValidationLookupKey.unwrap(b);
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {getEmptyCalldataSlice} from "@erc6900/reference-implementation/helpers/EmptyCalldataSlice.sol";
import {ExecutionManifest} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol";
import {
Call,
HookConfig,
IModularAccount,
ModuleEntity,
ValidationConfig,
ValidationFlags
} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {HookConfig, HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol";
import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol";
import {SparseCalldataSegmentLib} from "@erc6900/reference-implementation/libraries/SparseCalldataSegmentLib.sol";
import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol";
import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol";
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol";
import {ExecutionInstallDelegate} from "../helpers/ExecutionInstallDelegate.sol";
import {_coalescePreValidation, _coalesceValidation} from "../helpers/ValidationResHelpers.sol";
import {IModularAccountBase} from "../interfaces/IModularAccountBase.sol";
import {
DensePostHookData,
ExecutionLib,
PHCallBuffer,
RTCallBuffer,
SigCallBuffer,
UOCallBuffer
} from "../libraries/ExecutionLib.sol";
import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol";
import {MemManagementLib, MemSnapshot} from "../libraries/MemManagementLib.sol";
import {
ValidationLocator, ValidationLocatorLib, ValidationLookupKey
} from "../libraries/ValidationLocatorLib.sol";
import {AccountBase} from "./AccountBase.sol";
import {AccountStorage, ValidationStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol";
import {AccountStorageInitializable} from "./AccountStorageInitializable.sol";
import {ModularAccountView} from "./ModularAccountView.sol";
import {ModuleManagerInternals} from "./ModuleManagerInternals.sol";
import {TokenReceiver} from "./TokenReceiver.sol";
/// @title Modular Account Base
/// @author Alchemy
/// @notice This abstract contract is a modular account that is compliant with ERC-6900 standard. It supports
/// deferred actions during validation.
abstract contract ModularAccountBase is
IModularAccount,
IModularAccountBase,
ModularAccountView,
AccountStorageInitializable,
AccountBase,
IERC1271,
IERC165,
IAccountExecute,
ModuleManagerInternals,
UUPSUpgradeable,
TokenReceiver
{
using LinkedListSetLib for LinkedListSet;
using ModuleEntityLib for ModuleEntity;
using ValidationConfigLib for ValidationFlags;
using HookConfigLib for HookConfig;
using SparseCalldataSegmentLib for bytes;
enum ValidationCheckingType {
GLOBAL,
SELECTOR,
EITHER
}
// keccak256("EIP712Domain(uint256 chainId,address verifyingContract)")
bytes32 internal constant _DOMAIN_SEPARATOR_TYPEHASH =
0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;
// keccak256("DeferredAction(uint256 nonce,uint48 deadline,bytes call)")
bytes32 internal constant _DEFERRED_ACTION_TYPEHASH =
0x9b23e06584efc6b65fc854cee55011d89f86485487b6db36aed7d23884711ea3;
// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 internal constant _INTERFACE_ID_INVALID = 0xffffffff;
// bytes4(keccak256("isValidSignature(bytes32,bytes)"))
bytes4 internal constant _1271_MAGIC_VALUE = 0x1626ba7e;
bytes4 internal constant _1271_INVALID = 0xffffffff;
address internal immutable _EXECUTION_INSTALL_DELEGATE;
error CreateFailed();
error DeferredActionSignatureInvalid();
error RequireUserOperationContext();
error SelfCallRecursionDepthExceeded();
error SignatureValidationInvalid(ModuleEntity validationFunction);
error UserOpValidationInvalid(ModuleEntity validationFunction);
error UnexpectedAggregator(ModuleEntity validationFunction, address aggregator);
error UnrecognizedFunction(bytes4 selector);
error ValidationFunctionMissing(bytes4 selector);
error DeferredValidationHasValidationHooks();
// Wraps execution of a native function with runtime validation and hooks
// Used for performCreate, execute, executeBatch, installExecution, uninstallExecution, installValidation,
// uninstallValidation, upgradeToAndCall, updateFallbackSignerData.
modifier wrapNativeFunction() {
DensePostHookData postHookData = _checkPermittedCallerAndAssociatedHooks();
_;
ExecutionLib.doCachedPostHooks(postHookData);
}
constructor(IEntryPoint entryPoint, ExecutionInstallDelegate executionInstallDelegate)
AccountBase(entryPoint)
{
_disableInitializers();
_EXECUTION_INSTALL_DELEGATE = address(executionInstallDelegate);
}
// EXTERNAL FUNCTIONS
receive() external payable {}
/// @notice Fallback function
/// @dev Routes calls to execution functions based on the incoming msg.sig. If there's no module associated
/// with this function selector, revert.
///
/// @return The raw returned data from the invoked execution function.
fallback(bytes calldata) external payable returns (bytes memory) {
address execModule = getAccountStorage().executionStorage[msg.sig].module;
if (execModule == address(0)) {
revert UnrecognizedFunction(msg.sig);
}
DensePostHookData postHookData = _checkPermittedCallerAndAssociatedHooks();
// execute the function, bubbling up any reverts
ExecutionLib.callBubbleOnRevertTransient(execModule, 0 wei, msg.data);
bytes memory execReturnData = ExecutionLib.collectReturnData();
ExecutionLib.doCachedPostHooks(postHookData);
return execReturnData;
}
/// @inheritdoc IModularAccountBase
function performCreate(uint256 value, bytes calldata initCode, bool isCreate2, bytes32 salt)
external
payable
virtual
override
wrapNativeFunction
returns (address createdAddr)
{
assembly ("memory-safe") {
// Load the free memory pointer.
let fmp := mload(0x40)
// Get the initCode length.
let len := initCode.length
// Copy the initCode from callata to memory at the free memory pointer.
calldatacopy(fmp, initCode.offset, len)
switch isCreate2
case 1 { createdAddr := create2(value, fmp, len, salt) }
default { createdAddr := create(value, fmp, len) }
if iszero(createdAddr) {
// If creation failed (the address returned is zero), revert with CreateFailed().
mstore(0x00, 0x7e16b8cd)
revert(0x1c, 0x04)
}
}
}
/// @inheritdoc IAccountExecute
/// @notice Execution function that allows UO context to be passed to execution hooks
/// @dev This function is only callable by the EntryPoint
function executeUserOp(PackedUserOperation calldata userOp, bytes32) external override {
_requireFromEntryPoint();
ValidationLocator locator = ValidationLocatorLib.loadFromNonce(userOp.nonce);
HookConfig[] memory validationAssocExecHooks =
MemManagementLib.loadExecHooks(getAccountStorage().validationStorage[locator.lookupKey()]);
PHCallBuffer callBuffer;
if (validationAssocExecHooks.length > 0) {
callBuffer = ExecutionLib.allocatePreExecHookCallBuffer(msg.data);
}
DensePostHookData postHookData = ExecutionLib.doPreHooks(validationAssocExecHooks, callBuffer);
bytes memory callData = ExecutionLib.getExecuteUOCallData(callBuffer, userOp.callData);
// Manually call self, without collecting return data unless there's a revert.
ExecutionLib.callBubbleOnRevert(address(this), 0, callData);
ExecutionLib.doCachedPostHooks(postHookData);
}
/// @inheritdoc IModularAccount
/// @notice May be validated by a global validation.
function execute(address target, uint256 value, bytes calldata data)
external
payable
override
wrapNativeFunction
returns (bytes memory result)
{
ExecutionLib.callBubbleOnRevertTransient(target, value, data);
// Only return data if not called by the EntryPoint
if (msg.sender != address(_ENTRY_POINT)) {
result = ExecutionLib.collectReturnData();
}
}
/// @inheritdoc IModularAccount
/// @notice May be validated by a global validation function.
function executeBatch(Call[] calldata calls)
external
payable
override
wrapNativeFunction
returns (bytes[] memory results)
{
uint256 callsLength = calls.length;
if (msg.sender != address(_ENTRY_POINT)) {
results = new bytes[](callsLength);
for (uint256 i = 0; i < callsLength; ++i) {
ExecutionLib.callBubbleOnRevertTransient(calls[i].target, calls[i].value, calls[i].data);
results[i] = ExecutionLib.collectReturnData();
}
} else {
for (uint256 i = 0; i < callsLength; ++i) {
ExecutionLib.callBubbleOnRevertTransient(calls[i].target, calls[i].value, calls[i].data);
}
}
}
/// @inheritdoc IModularAccount
function executeWithRuntimeValidation(bytes calldata data, bytes calldata authorization)
external
payable
returns (bytes memory)
{
(ValidationLocator locator, bytes calldata authorizationData) =
ValidationLocatorLib.loadFromSignature(authorization);
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[locator.lookupKey()];
// Check if the runtime validation function is allowed to be called
_checkIfValidationAppliesCallData(
data,
locator.lookupKey(),
// Unfortunately, have to avoid declaring a `bool isGlobalValidation` to avoid stack too deep issues.
locator.isGlobal() ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR
);
RTCallBuffer rtCallBuffer = _doRuntimeValidation(locator.lookupKey(), data, authorizationData);
// If runtime validation passes, run exec hooks associated with the validator
HookConfig[] memory validationAssocExecHooks = MemManagementLib.loadExecHooks(_validationStorage);
PHCallBuffer phCallBuffer;
if (validationAssocExecHooks.length > 0) {
phCallBuffer = ExecutionLib.convertToPreHookCallBuffer(rtCallBuffer, data);
}
DensePostHookData postHookData = ExecutionLib.doPreHooks(validationAssocExecHooks, phCallBuffer);
// Execute the call, reusing the already-allocated RT call buffers, if it exists.
// In practice, this is cheaper than attempting to coalesce the (possibly two) buffers.
ExecutionLib.executeRuntimeSelfCall(rtCallBuffer, data);
bytes memory returnData = ExecutionLib.collectReturnData();
ExecutionLib.doCachedPostHooks(postHookData);
return returnData;
}
/// @inheritdoc IModularAccount
/// @notice May be validated by a global validation.
function installExecution(
address module,
ExecutionManifest calldata manifest,
bytes calldata moduleInstallData
) external override wrapNativeFunction {
// Access params to prevent compiler unused parameter flags.
(module, manifest, moduleInstallData);
address delegate = _EXECUTION_INSTALL_DELEGATE;
ExecutionLib.delegatecallBubbleOnRevertTransient(delegate);
}
/// @inheritdoc IModularAccount
/// @notice May be validated by a global validation.
function uninstallExecution(
address module,
ExecutionManifest calldata manifest,
bytes calldata moduleUninstallData
) external override wrapNativeFunction {
// Access params to prevent compiler unused parameter flags.
(module, manifest, moduleUninstallData);
address delegate = _EXECUTION_INSTALL_DELEGATE;
ExecutionLib.delegatecallBubbleOnRevertTransient(delegate);
}
/// @inheritdoc IModularAccount
/// @notice May be validated by a global validation.
/// @dev This function can be used to update (to a certain degree) previously installed validation functions.
/// - preValidationHook, executionHooks, and selectors can be added later. Though they won't be deleted.
/// - isGlobal and isSignatureValidation can also be updated later.
function installValidation(
ValidationConfig validationConfig,
bytes4[] calldata selectors,
bytes calldata installData,
bytes[] calldata hooks
) external virtual wrapNativeFunction {
_installValidation(validationConfig, selectors, installData, hooks);
}
/// @inheritdoc IModularAccount
/// @notice May be validated by a global validation.
function uninstallValidation(
ModuleEntity validationFunction,
bytes calldata uninstallData,
bytes[] calldata hookUninstallData
) external wrapNativeFunction {
_uninstallValidation(validationFunction, uninstallData, hookUninstallData);
}
/// @inheritdoc IERC1271
function isValidSignature(bytes32 hash, bytes calldata signature) external view override returns (bytes4) {
(ValidationLocator locator, bytes calldata signatureRemainder) =
ValidationLocatorLib.loadFromSignature(signature);
return _isValidSignature(locator.lookupKey(), hash, signatureRemainder);
}
/// @inheritdoc IERC165
/// @notice ERC-165 introspection
/// @dev returns true for `IERC165.interfaceId` and false for `0xFFFFFFFF`
/// @param interfaceId interface id to check against
/// @return bool support for specific interface
function supportsInterface(bytes4 interfaceId) external view override returns (bool) {
if (interfaceId == _INTERFACE_ID_INVALID) {
return false;
}
if (
interfaceId == type(IERC721Receiver).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId
|| interfaceId == type(IERC165).interfaceId
) {
return true;
}
return getAccountStorage().supportedIfaces[interfaceId] > 0;
}
/// @inheritdoc IModularAccount
function accountId() external pure virtual returns (string memory);
/// @inheritdoc UUPSUpgradeable
/// @notice May be validated by a global validation.
function upgradeToAndCall(address newImplementation, bytes calldata data)
public
payable
virtual
override
onlyProxy
wrapNativeFunction
{
super.upgradeToAndCall(newImplementation, data);
}
// INTERNAL FUNCTIONS
// Parent function validateUserOp enforces that this call can only be made by the EntryPoint
function _validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
internal
override
returns (uint256 validationData)
{
ValidationLocator locator = ValidationLocatorLib.loadFromNonce(userOp.nonce);
bytes calldata userOpSignature = userOp.signature;
/// The calldata layout is unique for deferred validation installation.
/// Byte indices are [inclusive, exclusive] and relative to the start of the signature after the locator is
/// decoded and removed.
/// [0:4] : uint32, encodedDatalength.
/// [4:(4 + encodedDatalength)] : bytes, abi-encoded deferred action data.
/// [(4 + encodedDataLength):(8 + encodedDataLength)] : uint32, deferredActionSigLength.
/// [(8 + encodedDataLength):(8 + deferredActionSigLength + encodedDataLength)] : bytes,
/// deferred action sig. This is the signature passed to the outer validation decoded earlier.
/// [(8 + deferredActionSigLength + encodedDataLength):] : bytes, userOpSignature. This is the
/// signature passed to the inner validation.
if (locator.hasDeferredAction()) {
// Use inner validation as a 1271 validation for the deferred action, then use the outer
// validation to validate the UO.
// Get the length of the deferred action data.
uint256 encodedDataLength = uint32(bytes4(userOpSignature[:4]));
// Load the pointer to the encoded data.
bytes calldata encodedData = userOpSignature[4:4 + encodedDataLength];
// Get the deferred action signature length.
uint256 deferredActionSigLength =
uint32(bytes4(userOpSignature[4 + encodedDataLength:8 + encodedDataLength]));
// Get the deferred installation signature, which is passed to the outer validation to handle the
// deferred action.
bytes calldata deferredActionSig =
userOpSignature[8 + encodedDataLength:8 + encodedDataLength + deferredActionSigLength];
//Validate the signature.
// Freeze the free-memory pointer, since we won't need to use anything from the deferred action
// validation memory.
MemSnapshot memSnapshot = MemManagementLib.freezeFMP();
uint48 deadline = _handleDeferredAction(userOp.nonce, encodedData, deferredActionSig);
// Restore the free memory pointer.
MemManagementLib.restoreFMP(memSnapshot);
// Update the validation data with the deadline.
validationData = uint256(deadline) << 160;
// Update the UserOp signature to the remaining bytes.
userOpSignature = userOpSignature[8 + encodedDataLength + deferredActionSigLength:];
}
_checkIfValidationAppliesCallData(
userOp.callData,
locator.lookupKey(),
locator.isGlobal() ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR
);
// Check if there are execution hooks associated with the validator, and revert if the call isn't to
// `executeUserOp`. This check must be here because if context isn't passed, we can't tell in execution
// which hooks should have ran.
if (
getAccountStorage().validationStorage[locator.lookupKey()].executionHookCount > 0
&& bytes4(userOp.callData[:4]) != this.executeUserOp.selector
) {
revert RequireUserOperationContext();
}
uint256 userOpValidationRes = _doUserOpValidation(userOp, userOpHash, locator.lookupKey(), userOpSignature);
// We only coalesce validations if the validation data from deferred installation is nonzero.
if (validationData != 0) {
validationData = _coalesceValidation(validationData, userOpValidationRes);
} else {
validationData = userOpValidationRes;
}
}
/// @return The deadline of the deferred action
function _handleDeferredAction(uint256 userOpNonce, bytes calldata encodedData, bytes calldata sig)
internal
returns (uint48)
{
// The inner validation, deadline, and deferred call bytes are all at fixed positions in the encoded data.
// [:21] = ValidationLocator defActionValidationLocator
// [21:27] = uint48 deadline
// [27:] = bytes deferredCall
ValidationLocator defActionValidationLocator = ValidationLocator.wrap(uint168(bytes21(encodedData[:21])));
ValidationStorage storage _validationStorage =
getAccountStorage().validationStorage[defActionValidationLocator.lookupKey()];
// Because this bypasses UO validation hooks, we require that the validation used does not include any
// validation hooks.
if (_validationStorage.validationHookCount != 0) {
revert DeferredValidationHasValidationHooks();
}
uint48 deadline = uint48(bytes6(encodedData[21:27]));
bytes32 typedDataHash = _computeDeferredActionHash(userOpNonce, deadline, encodedData[27:]);
// Check if the outer validation applies to the function call
_checkIfValidationAppliesCallData(
encodedData[27:],
defActionValidationLocator.lookupKey(),
defActionValidationLocator.isGlobal() ? ValidationCheckingType.GLOBAL : ValidationCheckingType.SELECTOR
);
// Handle the signature validation
_validateDeferredActionSignature(typedDataHash, sig, defActionValidationLocator.lookupKey());
// Run the validation associated execution hooks, allocating a call buffer as needed.
HookConfig[] memory validationAssocExecHooks = MemManagementLib.loadExecHooks(_validationStorage);
PHCallBuffer callBuffer;
if (validationAssocExecHooks.length > 0) {
callBuffer = ExecutionLib.allocatePreExecHookCallBuffer(encodedData[27:]);
}
DensePostHookData postHookData = ExecutionLib.doPreHooks(validationAssocExecHooks, callBuffer);
// Perform the deferred action's self call on the account.
ExecutionLib.callBubbleOnRevertTransient(address(this), 0, encodedData[27:]);
// Do the cached post hooks
ExecutionLib.doCachedPostHooks(postHookData);
return deadline;
}
// To support gas estimation, we don't fail early when the failure is caused by a signature failure
function _doUserOpValidation(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
ValidationLookupKey validationLookupKey,
bytes calldata signature
) internal returns (uint256) {
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationLookupKey];
// Do preUserOpValidation hooks
HookConfig[] memory preUserOpValidationHooks = MemManagementLib.loadValidationHooks(_validationStorage);
uint256 validationRes;
UOCallBuffer userOpCallBuffer;
if (!_validationIsNative(validationLookupKey) || preUserOpValidationHooks.length > 0) {
userOpCallBuffer = ExecutionLib.allocateUserOpValidationCallBuffer(userOp, userOpHash);
}
bytes calldata currentSignatureSlice;
for (uint256 i = preUserOpValidationHooks.length; i > 0; i) {
// Decrement here, instead of in the loop body, to convert from length to an index.
unchecked {
--i;
}
(currentSignatureSlice, signature) =
signature.advanceSegmentIfAtIndex(uint8(preUserOpValidationHooks.length - i - 1));
ModuleEntity uoValidationHook = preUserOpValidationHooks[i].moduleEntity();
uint256 currentValidationRes =
ExecutionLib.invokeUserOpCallBuffer(userOpCallBuffer, uoValidationHook, currentSignatureSlice);
if (uint160(currentValidationRes) > 1) {
// If the aggregator is not 0 or 1, it is an unexpected value
revert UnexpectedAggregator(uoValidationHook, address(uint160(currentValidationRes)));
}
validationRes = _coalescePreValidation(validationRes, currentValidationRes);
}
// Run the user op validation function
{
currentSignatureSlice = signature.getFinalSegment();
uint256 currentValidationRes =
_execUserOpValidation(validationLookupKey, userOpHash, currentSignatureSlice, userOpCallBuffer);
if (preUserOpValidationHooks.length != 0) {
// If we have other validation data we need to coalesce with
validationRes = _coalesceValidation(validationRes, currentValidationRes);
} else {
validationRes = currentValidationRes;
}
}
return validationRes;
}
function _doRuntimeValidation(
ValidationLookupKey validationLookupKey,
bytes calldata callData,
bytes calldata authorizationData
) internal returns (RTCallBuffer) {
ValidationStorage storage _validationData = getAccountStorage().validationStorage[validationLookupKey];
// run all preRuntimeValidation hooks
HookConfig[] memory preRuntimeValidationHooks = MemManagementLib.loadValidationHooks(_validationData);
RTCallBuffer callBuffer;
if (!_validationIsNative(validationLookupKey) || preRuntimeValidationHooks.length > 0) {
callBuffer = ExecutionLib.allocateRuntimeValidationCallBuffer(callData, authorizationData);
}
for (uint256 i = preRuntimeValidationHooks.length; i > 0;) {
// Decrement here, instead of in the loop update step, to handle the case where the length is 0.
unchecked {
--i;
}
bytes calldata currentAuthSegment;
(currentAuthSegment, authorizationData) =
authorizationData.advanceSegmentIfAtIndex(uint8(preRuntimeValidationHooks.length - i - 1));
ExecutionLib.invokeRuntimeCallBufferPreValidationHook(
callBuffer, preRuntimeValidationHooks[i], currentAuthSegment
);
}
authorizationData = authorizationData.getFinalSegment();
_execRuntimeValidation(validationLookupKey, callBuffer, authorizationData);
return callBuffer;
}
// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address newImplementation) internal override {}
/**
* Order of operations:
* 1. Check if the sender is the entry point, the account itself, or the selector called is public.
* - Yes: Return an empty array, there are no post executionHooks.
* - No: Continue
* 2. Check if the called selector (msg.sig) is included in the set of selectors the msg.sender can
* directly call.
* - Yes: Continue
* - No: Revert, the caller is not allowed to call this selector
* 3. If there are runtime validation hooks associated with this caller-sig combination, run them.
* 4. Run the pre executionHooks associated with this caller-sig combination, and return the
* post executionHooks to run later.
*/
function _checkPermittedCallerAndAssociatedHooks() internal returns (DensePostHookData) {
AccountStorage storage _storage = getAccountStorage();
HookConfig[] memory execHooks;
RTCallBuffer rtCallBuffer;
// We only need to handle execution hooks when the sender is not the entry point or the account itself,
// and the selector isn't public.
if (
msg.sender != address(_ENTRY_POINT) && msg.sender != address(this)
&& !_storage.executionStorage[msg.sig].skipRuntimeValidation
) {
ValidationLookupKey directCallValidationKey = ValidationLocatorLib.directCallLookupKey(msg.sender);
_checkIfValidationAppliesCallData(msg.data, directCallValidationKey, ValidationCheckingType.EITHER);
// Direct call is allowed, run associated execution & validation hooks
// Validation hooks
HookConfig[] memory preRuntimeValidationHooks =
MemManagementLib.loadValidationHooks(_storage.validationStorage[directCallValidationKey]);
uint256 preRuntimeValidationHooksLength = preRuntimeValidationHooks.length;
if (preRuntimeValidationHooksLength > 0) {
rtCallBuffer = ExecutionLib.allocateRuntimeValidationCallBuffer(msg.data, getEmptyCalldataSlice());
}
for (uint256 i = preRuntimeValidationHooksLength; i > 0;) {
// Decrement here, instead of in the loop body, to convert from length to an index.
unchecked {
--i;
}
ExecutionLib.invokeRuntimeCallBufferPreValidationHook(
rtCallBuffer, preRuntimeValidationHooks[i], getEmptyCalldataSlice()
);
}
//Load all execution hooks: both associated with the selector and the validation function.
execHooks = MemManagementLib.loadExecHooks(
_storage.executionStorage[msg.sig], _storage.validationStorage[directCallValidationKey]
);
} else {
// If the sender is the entry point or the account itself, or the selector is public, this indicates
// that validation was done elsewhere. We only need to run selector-associated execution hooks.
execHooks = MemManagementLib.loadExecHooks(_storage.executionStorage[msg.sig]);
}
PHCallBuffer preHookCallBuffer;
if (execHooks.length > 0) {
preHookCallBuffer = ExecutionLib.convertToPreHookCallBuffer(rtCallBuffer, msg.data);
}
// Exec hooks associated with the selector
DensePostHookData postHookData = ExecutionLib.doPreHooks(execHooks, preHookCallBuffer);
return postHookData;
}
function _execUserOpValidation(
ValidationLookupKey validationLookupKey,
bytes32 hash,
bytes calldata signatureSegment,
UOCallBuffer callBuffer
) internal virtual returns (uint256) {
(hash); // unused in ModularAccountBase, but used in SemiModularAccountBase
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationLookupKey];
ModuleEntity userOpValidationFunction = validationLookupKey.moduleEntity(_validationStorage);
if (!_validationStorage.validationFlags.isUserOpValidation()) {
revert UserOpValidationInvalid(userOpValidationFunction);
}
ExecutionLib.convertToValidationBuffer(callBuffer);
return ExecutionLib.invokeUserOpCallBuffer(callBuffer, userOpValidationFunction, signatureSegment);
}
function _execRuntimeValidation(
ValidationLookupKey validationLookupKey,
RTCallBuffer callBuffer,
bytes calldata authorization
) internal virtual {
ValidationStorage storage _validationData = getAccountStorage().validationStorage[validationLookupKey];
ModuleEntity runtimeValidationFunction = validationLookupKey.moduleEntity(_validationData);
ExecutionLib.invokeRuntimeCallBufferValidation(callBuffer, runtimeValidationFunction, authorization);
}
function _computeDeferredActionHash(uint256 userOpNonce, uint48 deadline, bytes calldata selfCall)
internal
view
returns (bytes32)
{
// Note:
// - A zero deadline translates to "no deadline"
// - The user op nonce also includes the data for:
// - Which validation function to use
// - Whether or not a deferred action is included
// - Whether or not the validation is used as a global validation.
// Compute the hash without permanently allocating memory for each step.
// The following is equivalent to:
// keccak256(
// abi.encode(
// _DEFERRED_ACTION_TYPEHASH,
// nonce,
// deadline,
// keccak256(selfCall)
// )
// )
// Compute the struct hash, then convert it to a typed data hash.
bytes32 structHash;
assembly ("memory-safe") {
// Get the hash of the dynamic-length encoded install call
let fmp := mload(0x40)
calldatacopy(fmp, selfCall.offset, selfCall.length)
let selfCallHash := keccak256(fmp, selfCall.length)
// Compute the struct hash
let ptr := fmp
mstore(ptr, _DEFERRED_ACTION_TYPEHASH)
ptr := add(ptr, 0x20)
mstore(ptr, userOpNonce)
ptr := add(ptr, 0x20)
// Clear the upper bits of the deadline, in case the caller didn't.
mstore(ptr, and(deadline, 0xffffffffffff))
ptr := add(ptr, 0x20)
mstore(ptr, selfCallHash)
// Compute the struct hash
structHash := keccak256(fmp, 0x80)
}
bytes32 typedDataHash = MessageHashUtils.toTypedDataHash(_domainSeparator(), structHash);
return typedDataHash;
}
function _validateDeferredActionSignature(
bytes32 defActionTypedDataHash,
bytes calldata signature,
ValidationLookupKey deferredSigValidationLookupKey
) internal view {
// Validate the 1271 signature.
SigCallBuffer sigCallBuffer;
if (!_validationIsNative(deferredSigValidationLookupKey)) {
sigCallBuffer = ExecutionLib.allocateSigCallBuffer(defActionTypedDataHash, signature);
}
if (
_exec1271Validation(sigCallBuffer, defActionTypedDataHash, deferredSigValidationLookupKey, signature)
!= _1271_MAGIC_VALUE
) {
revert DeferredActionSignatureInvalid();
}
}
function _isValidSignature(ValidationLookupKey validationLookupKey, bytes32 hash, bytes calldata signature)
internal
view
returns (bytes4)
{
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationLookupKey];
HookConfig[] memory preSignatureValidationHooks = MemManagementLib.loadValidationHooks(_validationStorage);
SigCallBuffer sigCallBuffer;
if (!_validationIsNative(validationLookupKey) || preSignatureValidationHooks.length > 0) {
sigCallBuffer = ExecutionLib.allocateSigCallBuffer(hash, signature);
}
for (uint256 i = preSignatureValidationHooks.length; i > 0;) {
// Decrement here, instead of in the loop body, to convert from length to an index.
unchecked {
--i;
}
bytes calldata currentSignatureSegment;
(currentSignatureSegment, signature) =
signature.advanceSegmentIfAtIndex(uint8(preSignatureValidationHooks.length - i - 1));
ExecutionLib.invokePreSignatureValidationHook(
sigCallBuffer, preSignatureValidationHooks[i], currentSignatureSegment
);
}
signature = signature.getFinalSegment();
return _exec1271Validation(sigCallBuffer, hash, validationLookupKey, signature);
}
function _exec1271Validation(
SigCallBuffer buffer,
bytes32 hash,
ValidationLookupKey validationLookupKey,
bytes calldata signatureSegment
) internal view virtual returns (bytes4) {
(hash); // unused in ModularAccountBase, but used in SemiModularAccountBase
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationLookupKey];
ModuleEntity sigValidation = validationLookupKey.moduleEntity(_validationStorage);
if (!_validationStorage.validationFlags.isSignatureValidation()) {
revert SignatureValidationInvalid(sigValidation);
}
if (ExecutionLib.invokeSignatureValidation(buffer, sigValidation, signatureSegment) == _1271_MAGIC_VALUE) {
return _1271_MAGIC_VALUE;
}
return _1271_INVALID;
}
function _isValidationGlobal(ValidationLookupKey validationFunction) internal view virtual returns (bool) {
return getAccountStorage().validationStorage[validationFunction].validationFlags.isGlobal();
}
function _checkIfValidationAppliesCallData(
bytes calldata callData,
ValidationLookupKey validationFunction,
ValidationCheckingType checkingType
) internal view {
if (callData.length < 4) {
revert UnrecognizedFunction(bytes4(callData));
}
bytes4 outerSelector = bytes4(callData);
if (outerSelector == this.executeUserOp.selector) {
// If the selector is executeUserOp, pull the actual selector from the following data,
// and trim the calldata to ensure the self-call decoding is still accurate.
callData = callData[4:];
outerSelector = bytes4(callData[:4]);
}
_checkIfValidationAppliesSelector(outerSelector, validationFunction, checkingType);
if (outerSelector == IModularAccount.execute.selector) {
address target = MemManagementLib.getExecuteTarget(callData);
if (target == address(this)) {
// There is no point to call `execute` to recurse exactly once - this is equivalent to just having
// the calldata as a top-level call.
revert SelfCallRecursionDepthExceeded();
}
} else if (outerSelector == IModularAccount.executeBatch.selector) {
// executeBatch may be used to batch account actions together, by targetting the account itself.
// If this is done, we must ensure all of the inner calls are allowed by the provided validation
// function.
_checkExecuteBatchValidationApplicability(callData[4:], validationFunction, checkingType);
}
}
/// @notice Checks if the validation function is allowed to perform this call to `executeBatch`.
/// @param callData The calldata to check, excluding the `executeBatch` selector.
/// @param validationFunction The validation function to check against.
/// @param checkingType The type of validation checking to perform.
function _checkExecuteBatchValidationApplicability(
bytes calldata callData,
ValidationLookupKey validationFunction,
ValidationCheckingType checkingType
) internal view {
// Equivalent to the following code, but without using memory.
// (Call[] memory calls) = abi.decode(callData, (Call[]));
// for (uint256 i = 0; i < calls.length; ++i) {
// if (calls[i].target == address(this)) {
// bytes4 nestedSelector = bytes4(calls[i].data[:4]);
// if (
// nestedSelector == IModularAccount.execute.selector
// || nestedSelector == IModularAccount.executeBatch.selector
// ) {
//
// revert SelfCallRecursionDepthExceeded();
// }
// _checkIfValidationAppliesSelector(nestedSelector, validationFunction, checkingType);
// }
// }
// The following is adapted from the compiler-generated ABI decoder for the `Call[] calldata` parameter
// type. See test/mocks/MockDecoder.sol for more info.
// This allows the decoding behavior here, in the validation step, to match what would happen during the
// actual execution of `executeBatch`.
// This follows the compiler-generated behavior of:
// - asserting the data to load fits in the remaining space of the current `bytes calldata`.
// - asserting that the ABI-encoded offsets and lengths do not exceed the constant value
// 0xffffffffffffffff.
// The end of allowed calldata to read. Declared in an outer context to make available to multiple code
// blocks.
uint256 dataEnd;
// The absolute offset of the start of the `Call[]` array.
uint256 arrayPos;
// The length of the `Call[]` array.
uint256 callsLength;
// This block is retrieving the actual Call[] location and length, asserting it doesn't go out of bounds.
assembly ("memory-safe") {
// Set up the "safe data decoding range"
let headStart := callData.offset
dataEnd := add(headStart, callData.length)
// Assert it is safe to load the offset
if slt(sub(dataEnd, headStart), 32) { revert(0, 0) }
// Load and sanitize the offset
let relOffset := calldataload(callData.offset)
if gt(relOffset, 0xffffffffffffffff) { revert(0, 0) }
// Convert from a relative offset to an absolute offset.
let absOffset := add(headStart, relOffset)
// Assert it is safe to load the length
if iszero(slt(add(absOffset, 0x1f), dataEnd)) { revert(0, 0) }
// Load and sanitize the length
callsLength := calldataload(absOffset)
if gt(callsLength, 0xffffffffffffffff) { revert(0, 0) }
// Load the array position, and check that it fits within the alloted length.
arrayPos := add(absOffset, 0x20)
if gt(add(arrayPos, mul(callsLength, 0x20)), dataEnd) { revert(0, 0) }
}
// Now, we have the array length and data bounds.
// Iterate through the array elements, checking:
// - If the target is this account, assert that:
// - the selector in the data field is not `execute` or `executeBatch`.
// - the provided validation is allowed to call the selector.
for (uint256 i = 0; i < callsLength; ++i) {
address callTarget;
uint256 structAbsOffset;
// This block is retrieving the actual calls[i] struct location and contents, asserting it doesn't go
// out of bounds.
assembly ("memory-safe") {
// Load and sanitize the struct offset.
// This is still safe to load, from the bounds check above.
let structRelOffset := calldataload(add(arrayPos, mul(i, 0x20)))
if gt(structRelOffset, 0xffffffffffffffff) { revert(0, 0) }
// Validate struct offset. If the offset points to a location with < 3 words of space before the
// end of data, revert.
if iszero(slt(structRelOffset, sub(sub(dataEnd, arrayPos), sub(0x60, 1)))) { revert(0, 0) }
structAbsOffset := add(arrayPos, structRelOffset)
// Load the address from the struct, and sanitize its contents, to mirror the behavior of the ABI
// decoder.
callTarget := calldataload(structAbsOffset)
if iszero(eq(and(callTarget, 0xffffffffffffffffffffffffffffffffffffffff), callTarget)) {
revert(0, 0)
}
}
if (callTarget == address(this)) {
// In this case, we must load the selector, deny if it's `execute` or `executeBatch`, and check
// validation applicability.
uint32 selector;
// This block is retrieving the selector from the first 4 bytes of calls[i].data, asserting it
// doesn't go out of bounds and that the data is at least 4 bytes long.
assembly ("memory-safe") {
// Load and sanitize the data offset.
let dataRelOffset := calldataload(add(structAbsOffset, 0x40))
if gt(dataRelOffset, 0xffffffffffffffff) { revert(0, 0) }
// Validate data offset. If the offset points to a location with < 1 words of space before the
// end of data, revert.
if iszero(slt(dataRelOffset, sub(sub(dataEnd, structAbsOffset), sub(0x20, 1)))) {
revert(0, 0)
}
let dataAbsOffset := add(structAbsOffset, dataRelOffset)
// Load and sanitize the data length.
let dataLength := calldataload(dataAbsOffset)
if gt(dataLength, 0xffffffff) { revert(0, 0) }
// Get the data offset, and assert that the following data fits into the bounded calldata
// range.
let dataOffset := add(dataAbsOffset, 0x20)
if sgt(dataOffset, sub(dataEnd, mul(dataLength, 0x01))) { revert(0, 0) }
// Finally, load the selector being called. This will be the first 4 bytes of the data.
// If the data length is less than 4, revert.
if slt(dataLength, 4) { revert(0, 0) }
selector := shr(224, calldataload(dataOffset))
}
if (selector == uint32(this.execute.selector) || selector == uint32(this.executeBatch.selector)) {
// To prevent arbitrarily-deep recursive checking, we limit the depth of self-calls to one
// for the purposes of batching.
// This means that all self-calls must occur at the top level of the batch.
// Note that modules of other contracts using `executeWithRuntimeValidation` may still
// independently call into this account with a different validation function, allowing
// composition of multiple batches.
revert SelfCallRecursionDepthExceeded();
}
_checkIfValidationAppliesSelector(bytes4(selector), validationFunction, checkingType);
}
}
}
function _checkIfValidationAppliesSelector(
bytes4 selector,
ValidationLookupKey validationFunction,
ValidationCheckingType checkingType
) internal view {
// Check that the provided validation function is applicable to the selector
if (checkingType == ValidationCheckingType.GLOBAL) {
if (!_globalValidationApplies(selector, validationFunction)) {
revert ValidationFunctionMissing(selector);
}
} else if (checkingType == ValidationCheckingType.SELECTOR) {
if (!_selectorValidationApplies(selector, validationFunction)) {
revert ValidationFunctionMissing(selector);
}
} else {
if (
!_globalValidationApplies(selector, validationFunction)
&& !_selectorValidationApplies(selector, validationFunction)
) {
revert ValidationFunctionMissing(selector);
}
}
}
function _globalValidationApplies(bytes4 selector, ValidationLookupKey validationFunction)
internal
view
returns (bool)
{
return _globalValidationAllowed(selector) && _isValidationGlobal(validationFunction);
}
function _globalValidationAllowed(bytes4 selector) internal view returns (bool) {
return _isGlobalValidationAllowedNativeFunction(uint32(selector))
|| getAccountStorage().executionStorage[selector].allowGlobalValidation;
}
function _selectorValidationApplies(bytes4 selector, ValidationLookupKey validationFunction)
internal
view
returns (bool)
{
return getAccountStorage().validationStorage[validationFunction].selectors.contains(toSetValue(selector));
}
function _domainSeparator() internal view returns (bytes32) {
bytes32 result;
// Compute the hash without permanently allocating memory
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, _DOMAIN_SEPARATOR_TYPEHASH)
mstore(add(fmp, 0x20), chainid())
mstore(add(fmp, 0x40), address())
result := keccak256(fmp, 0x60)
}
return result;
}
// A virtual function to detect if a validation function is natively implemented. Used for determining call
// buffer allocation.
function _validationIsNative(ValidationLookupKey) internal pure virtual returns (bool) {
return false;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {IModule} from "./IModule.sol";
interface IExecutionHookModule is IModule {
/// @notice Run the pre execution hook specified by the `entityId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param sender The caller address.
/// @param value The call value.
/// @param data The calldata sent. For `executeUserOp` calls, hook modules should receive the full msg.data.
/// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned.
function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data)
external
returns (bytes memory);
/// @notice Run the post execution hook specified by the `entityId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param preExecHookData The context returned by its associated pre execution hook.
function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external;
}// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
import {IModule} from "./IModule.sol";
interface IValidationHookModule is IModule {
/// @notice Run the pre user operation validation hook specified by the `entityId`.
/// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param userOp The user operation.
/// @param userOpHash The user operation hash.
/// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes).
function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash)
external
returns (uint256);
/// @notice Run the pre runtime validation hook specified by the `entityId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param sender The caller address.
/// @param value The call value.
/// @param data The calldata sent.
/// @param authorization Additional data for the hook to use.
function preRuntimeValidationHook(
uint32 entityId,
address sender,
uint256 value,
bytes calldata data,
bytes calldata authorization
) external;
/// @notice Run the pre signature validation hook specified by the `entityId`.
/// @dev To indicate the call should revert, the function MUST revert.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param sender The caller address.
/// @param hash The hash of the message being signed.
/// @param signature The signature of the message.
function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata signature)
external
view;
}// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
import {IModule} from "./IModule.sol";
interface IValidationModule is IModule {
/// @notice Run the user operation validation function specified by the `entityId`.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param userOp The user operation.
/// @param userOpHash The user operation hash.
/// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes).
function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash)
external
returns (uint256);
/// @notice Run the runtime validation function specified by the `entityId`.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param account the account to validate for.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param sender The caller address.
/// @param value The call value.
/// @param data The calldata sent.
/// @param authorization Additional data for the validation function to use.
function validateRuntime(
address account,
uint32 entityId,
address sender,
uint256 value,
bytes calldata data,
bytes calldata authorization
) external;
/// @notice Validates a signature using ERC-1271.
/// @dev To indicate the entire call should revert, the function MUST revert.
/// @param account the account to validate for.
/// @param entityId An identifier that routes the call to different internal implementations, should there
/// be more than one.
/// @param sender the address that sent the ERC-1271 request to the smart account
/// @param hash the hash of the ERC-1271 request
/// @param signature the signature of the ERC-1271 request
/// @return The ERC-1271 `MAGIC_VALUE` if the signature is valid, or 0xFFFFFFFF if invalid.
function validateSignature(
address account,
uint32 entityId,
address sender,
bytes32 hash,
bytes calldata signature
) external view returns (bytes4);
}// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
/**
* The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations.
* A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction.
*/
interface IPaymaster {
enum PostOpMode {
// User op succeeded.
opSucceeded,
// User op reverted. Still has to pay for gas.
opReverted,
// Only used internally in the EntryPoint (cleanup after postOp reverts). Never calling paymaster with this value
postOpReverted
}
/**
* Payment validation: check if paymaster agrees to pay.
* Must verify sender is the entryPoint.
* Revert to reject this request.
* Note that bundlers will reject this method if it changes the state, unless the paymaster is trusted (whitelisted).
* The paymaster pre-pays using its deposit, and receive back a refund after the postOp method returns.
* @param userOp - The user operation.
* @param userOpHash - Hash of the user's request data.
* @param maxCost - The maximum cost of this transaction (based on maximum gas and gas price from userOp).
* @return context - Value to send to a postOp. Zero length to signify postOp is not required.
* @return validationData - Signature and time-range of this operation, encoded the same as the return
* value of validateUserOperation.
* <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure,
* other values are invalid for paymaster.
* <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite"
* <6-byte> validAfter - first timestamp this operation is valid
* Note that the validation code cannot use block.timestamp (or block.number) directly.
*/
function validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) external returns (bytes memory context, uint256 validationData);
/**
* Post-operation handler.
* Must verify sender is the entryPoint.
* @param mode - Enum with the following options:
* opSucceeded - User operation succeeded.
* opReverted - User op reverted. The paymaster still has to pay for gas.
* postOpReverted - never passed in a call to postOp().
* @param context - The context value returned by validatePaymasterUserOp
* @param actualGasCost - Actual gas used so far (without this postOp call).
* @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas
* and maxPriorityFee (and basefee)
* It is not the same as tx.gasprice, which is what the bundler pays.
*/
function postOp(
PostOpMode mode,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165Checker.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Library used to query support of an interface declared via {IERC165}.
*
* Note that these functions return the actual result of the query: they do not
* `revert` if an interface is not supported. It is up to the caller to decide
* what to do in these cases.
*/
library ERC165Checker {
// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;
/**
* @dev Returns true if `account` supports the {IERC165} interface.
*/
function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return
supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
!supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
}
/**
* @dev Returns true if `account` supports the interface defined by
* `interfaceId`. Support for {IERC165} itself is queried automatically.
*
* See {IERC165-supportsInterface}.
*/
function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
// query support of both ERC165 as per the spec and support of _interfaceId
return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
}
/**
* @dev Returns a boolean array where each value corresponds to the
* interfaces passed in and whether they're supported or not. This allows
* you to batch check interfaces for a contract where your expectation
* is that some interfaces may not be supported.
*
* See {IERC165-supportsInterface}.
*/
function getSupportedInterfaces(
address account,
bytes4[] memory interfaceIds
) internal view returns (bool[] memory) {
// an array of booleans corresponding to interfaceIds and whether they're supported or not
bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
// query support of ERC165 itself
if (supportsERC165(account)) {
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
}
}
return interfaceIdsSupported;
}
/**
* @dev Returns true if `account` supports all the interfaces defined in
* `interfaceIds`. Support for {IERC165} itself is queried automatically.
*
* Batch-querying can lead to gas savings by skipping repeated checks for
* {IERC165} support.
*
* See {IERC165-supportsInterface}.
*/
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
// query support of ERC165 itself
if (!supportsERC165(account)) {
return false;
}
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
return false;
}
}
// all interfaces supported
return true;
}
/**
* @notice Query if a contract implements an interface, does not check ERC165 support
* @param account The address of the contract to query for support of an interface
* @param interfaceId The interface identifier, as specified in ERC-165
* @return true if the contract at account indicates support of the interface with
* identifier interfaceId, false otherwise
* @dev Assumes that account contains a contract that supports ERC165, otherwise
* the behavior of this method is undefined. This precondition can be checked
* with {supportsERC165}.
*
* Some precompiled contracts will falsely indicate support for a given interface, so caution
* should be exercised when using this function.
*
* Interface identification is specified in ERC-165.
*/
function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
// prepare call
bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));
// perform static call
bool success;
uint256 returnSize;
uint256 returnValue;
assembly {
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
returnSize := returndatasize()
returnValue := mload(0x00)
}
return success && returnSize >= 0x20 && returnValue > 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
*/
interface IERC1271 {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param hash Hash of the data to be signed
* @param signature Signature byte array associated with _data
*/
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
import {HookConfig, ModuleEntity, ValidationFlags} from "../interfaces/IModularAccount.sol";
/// @dev Represents data associated with a specific function selector.
struct ExecutionDataView {
// The module that implements this execution function.
// If this is a native function, the address must be the address of the account.
address module;
// Whether or not the function needs runtime validation, or can be called by anyone. The function can still be
// state changing if this flag is set to true.
// Note that even if this is set to true, user op validation will still be required, otherwise anyone could
// drain the account of native tokens by wasting gas.
bool skipRuntimeValidation;
// Whether or not a global validation function may be used to validate this function.
bool allowGlobalValidation;
// The execution hooks for this function selector.
HookConfig[] executionHooks;
}
struct ValidationDataView {
// ValidationFlags layout:
// 0b00000___ // unused
// 0b_____A__ // isGlobal
// 0b______B_ // isSignatureValidation
// 0b_______C // isUserOpValidation
ValidationFlags validationFlags;
// The validation hooks for this validation function.
HookConfig[] validationHooks;
// Execution hooks to run with this validation function.
HookConfig[] executionHooks;
// The set of selectors that may be validated by this validation function.
bytes4[] selectors;
}
interface IModularAccountView {
/// @notice Get the execution data for a selector.
/// @dev If the selector is a native function, the module address will be the address of the account.
/// @param selector The selector to get the data for.
/// @return The execution data for this selector.
function getExecutionData(bytes4 selector) external view returns (ExecutionDataView memory);
/// @notice Get the validation data for a validation function.
/// @dev If the selector is a native function, the module address will be the address of the account.
/// @param validationFunction The validation function to get the data for.
/// @return The validation data for this validation function.
function getValidationData(ModuleEntity validationFunction)
external
view
returns (ValidationDataView memory);
}// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; // Index marking the start of the data for the validation function. uint8 constant RESERVED_VALIDATION_DATA_INDEX = type(uint8).max; // Maximum number of validation-associated hooks that can be registered. uint8 constant MAX_VALIDATION_ASSOC_HOOKS = type(uint8).max; // Magic value for the Entity ID of direct call validation. uint32 constant DIRECT_CALL_VALIDATION_ENTITY_ID = type(uint32).max;
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
function getEmptyCalldataSlice() pure returns (bytes calldata) {
bytes calldata empty;
assembly ("memory-safe") {
empty.length := 0
}
return empty;
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {RESERVED_VALIDATION_DATA_INDEX} from "../helpers/Constants.sol";
import {getEmptyCalldataSlice} from "../helpers/EmptyCalldataSlice.sol";
/// @title Sparse Calldata Segment Library
/// @notice Library for working with sparsely-packed calldata segments, identified with an index.
/// @dev The first byte of each segment is the index of the segment.
/// To prevent accidental stack-to-deep errors, the body and index of the segment are extracted separately, rather
/// than inline as part of the tuple returned by `getNextSegment`.
library SparseCalldataSegmentLib {
error NonCanonicalEncoding();
error SegmentOutOfOrder();
error ValidationSignatureSegmentMissing();
/// @notice Splits out a segment of calldata, sparsely-packed.
/// The expected format is:
/// [uint8(index0), uint32(len(segment0)), segment0, uint8(index1), uint32(len(segment1)), segment1,
/// ... uint8(indexN), uint32(len(segmentN)), segmentN]
/// @param source The calldata to extract the segment from.
/// @return segment The extracted segment. Using the above example, this would be segment0.
/// @return remainder The remaining calldata. Using the above example,
/// this would start at uint8(index1) and continue to the end at segmentN.
function getNextSegment(bytes calldata source)
internal
pure
returns (bytes calldata segment, bytes calldata remainder)
{
// The first byte of the segment is the index.
// The next 4 bytes hold the length of the segment, excluding the index.
uint32 length = uint32(bytes4(source[1:5]));
// The offset of the remainder of the calldata.
uint256 remainderOffset = 5 + length;
// The segment is the next `length` bytes after the first 5 bytes.
segment = source[5:remainderOffset];
// The remainder is the rest of the calldata.
remainder = source[remainderOffset:];
}
/// @notice If the index of the next segment in the source equals the provided index, return the next body and
/// advance the source by one segment.
/// @dev Reverts if the index of the next segment is less than the provided index, or if the extracted segment
/// has length 0.
/// @param source The calldata to extract the segment from.
/// @param index The index of the segment to extract.
/// @return A tuple containing the extracted segment's body, or an empty buffer if the index is not found, and
/// the remaining calldata.
function advanceSegmentIfAtIndex(bytes calldata source, uint8 index)
internal
pure
returns (bytes calldata, bytes calldata)
{
uint8 nextIndex = getIndex(source);
if (nextIndex < index) {
revert SegmentOutOfOrder();
}
if (nextIndex == index) {
(bytes calldata segment, bytes calldata remainder) = getNextSegment(source);
if (segment.length == 0) {
revert NonCanonicalEncoding();
}
return (segment, remainder);
}
return (getEmptyCalldataSlice(), source);
}
/// @notice Extracts the final segment from the source.
/// @dev Reverts if the index of the segment is not RESERVED_VALIDATION_DATA_INDEX.
/// @param source The calldata to extract the segment from.
/// @return The final segment.
function getFinalSegment(bytes calldata source) internal pure returns (bytes calldata) {
if (getIndex(source) != RESERVED_VALIDATION_DATA_INDEX) {
revert ValidationSignatureSegmentMissing();
}
return source[1:];
}
/// @notice Extracts the index from a segment.
/// @dev The first byte of the segment is the index.
/// @param segment The segment to extract the index from
/// @return The index of the segment
function getIndex(bytes calldata segment) internal pure returns (uint8) {
return uint8(segment[0]);
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
interface IAccountExecute {
/**
* Account may implement this execute method.
* passing this methodSig at the beginning of callData will cause the entryPoint to pass the full UserOp (and hash)
* to the account.
* The account should skip the methodSig, and use the callData (and optionally, other UserOp fields)
*
* @param userOp - The operation that was just validated.
* @param userOpHash - Hash of the user's request data.
*/
function executeUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1155Receiver.sol)
pragma solidity ^0.8.20;
import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice UUPS proxy mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol)
/// @author Modified from OpenZeppelin
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol)
///
/// @dev Note:
/// - This implementation is intended to be used with ERC1967 proxies.
/// See: `LibClone.deployERC1967` and related functions.
/// - This implementation is NOT compatible with legacy OpenZeppelin proxies
/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`.
abstract contract UUPSUpgradeable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The upgrade failed.
error UpgradeFailed();
/// @dev The call is from an unauthorized call context.
error UnauthorizedCallContext();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For checking if the context is a delegate call.
uint256 private immutable __self = uint256(uint160(address(this)));
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when the proxy's implementation is upgraded.
event Upgraded(address indexed implementation);
/// @dev `keccak256(bytes("Upgraded(address)"))`.
uint256 private constant _UPGRADED_EVENT_SIGNATURE =
0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* UUPS OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Please override this function to check if `msg.sender` is authorized
/// to upgrade the proxy to `newImplementation`, reverting if not.
/// ```
/// function _authorizeUpgrade(address) internal override onlyOwner {}
/// ```
function _authorizeUpgrade(address newImplementation) internal virtual;
/// @dev Returns the storage slot used by the implementation,
/// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822).
///
/// Note: The `notDelegated` modifier prevents accidental upgrades to
/// an implementation that is a proxy contract.
function proxiableUUID() public view virtual notDelegated returns (bytes32) {
// This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967.
return _ERC1967_IMPLEMENTATION_SLOT;
}
/// @dev Upgrades the proxy's implementation to `newImplementation`.
/// Emits a {Upgraded} event.
///
/// Note: Passing in empty `data` skips the delegatecall to `newImplementation`.
function upgradeToAndCall(address newImplementation, bytes calldata data)
public
payable
virtual
onlyProxy
{
_authorizeUpgrade(newImplementation);
/// @solidity memory-safe-assembly
assembly {
newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits.
mstore(0x01, 0x52d1902d) // `proxiableUUID()`.
let s := _ERC1967_IMPLEMENTATION_SLOT
// Check if `newImplementation` implements `proxiableUUID` correctly.
if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) {
mstore(0x01, 0x55299b49) // `UpgradeFailed()`.
revert(0x1d, 0x04)
}
// Emit the {Upgraded} event.
log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation)
sstore(s, newImplementation) // Updates the implementation.
// Perform a delegatecall to `newImplementation` if `data` is non-empty.
if data.length {
// Forwards the `data` to `newImplementation` via delegatecall.
let m := mload(0x40)
calldatacopy(m, data.offset, data.length)
if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00))
{
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
}
}
/// @dev Requires that the execution is performed through a proxy.
modifier onlyProxy() {
uint256 s = __self;
/// @solidity memory-safe-assembly
assembly {
// To enable use cases with an immutable default implementation in the bytecode,
// (see: ERC6551Proxy), we don't require that the proxy address must match the
// value stored in the implementation slot, which may not be initialized.
if eq(s, address()) {
mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`.
revert(0x1c, 0x04)
}
}
_;
}
/// @dev Requires that the execution is NOT performed via delegatecall.
/// This is the opposite of `onlyProxy`.
modifier notDelegated() {
uint256 s = __self;
/// @solidity memory-safe-assembly
assembly {
if iszero(eq(s, address())) {
mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`.
revert(0x1c, 0x04)
}
}
_;
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
// solhint-disable-next-line private-vars-leading-underscore
function _coalescePreValidation(uint256 validationRes1, uint256 validationRes2)
pure
returns (uint256 resValidationData)
{
resValidationData = _coalesceValidationResTime(validationRes1, validationRes2);
// Once we know that the authorizer field is 0 or 1, we can safely bubble up SIG_FAIL with bitwise OR
resValidationData |= uint160(validationRes1) | uint160(validationRes2);
}
// solhint-disable-next-line private-vars-leading-underscore
function _coalesceValidation(uint256 preValidationData, uint256 validationRes)
pure
returns (uint256 resValidationData)
{
resValidationData = _coalesceValidationResTime(preValidationData, validationRes);
// If prevalidation failed, bubble up failure, and ignore authorizer
resValidationData |= uint160(preValidationData) == 1 ? 1 : uint160(validationRes);
}
// solhint-disable-next-line private-vars-leading-underscore
function _coalesceValidationResTime(uint256 validationRes1, uint256 validationRes2)
pure
returns (uint256 resValidationData)
{
uint48 validUntil1 = uint48(validationRes1 >> 160);
if (validUntil1 == 0) {
validUntil1 = type(uint48).max;
}
uint48 validUntil2 = uint48(validationRes2 >> 160);
if (validUntil2 == 0) {
validUntil2 = type(uint48).max;
}
resValidationData = ((validUntil1 > validUntil2) ? uint256(validUntil2) << 160 : uint256(validUntil1) << 160);
uint48 validAfter1 = uint48(validationRes1 >> 208);
uint48 validAfter2 = uint48(validationRes2 >> 208);
resValidationData |= ((validAfter1 < validAfter2) ? uint256(validAfter2) << 208 : uint256(validAfter1) << 208);
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
interface IModularAccountBase {
/// @notice Create a contract.
/// @param value The value to send to the new contract constructor
/// @param initCode The initCode to deploy.
/// @param isCreate2 The bool to indicate which method to use to deploy.
/// @param salt The salt for deployment.
/// @return createdAddr The created contract address.
function performCreate(uint256 value, bytes calldata initCode, bool isCreate2, bytes32 salt)
external
payable
returns (address createdAddr);
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {HookConfig} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {ExecutionStorage, ValidationStorage} from "../account/AccountStorage.sol";
import {LinkedListSet, LinkedListSetLib, SENTINEL_VALUE, SetValue} from "./LinkedListSetLib.sol";
type MemSnapshot is uint256;
/// @title Memory Management Library
/// @author Alchemy
/// @notice A library for managing memory in ModularAccount. Handles loading data from storage into memory, and
/// manipulating the free memory pointer.
library MemManagementLib {
/// @notice Load execution hooks associated both with a validation function and an execution selector.
/// @param execData The execution storage struct to load from.
/// @param valData The validation storage struct to load from.
/// @return hooks An array of `HookConfig` items, representing the execution hooks.
function loadExecHooks(ExecutionStorage storage execData, ValidationStorage storage valData)
internal
view
returns (HookConfig[] memory hooks)
{
// Load selector-assoc hooks first, then validation-assoc, because execution order is reversed
// This next code segment is adapted from the function LinkedListSetLib.getAll.
mapping(bytes32 => bytes32) storage llsMap = execData.executionHooks.map;
uint256 size = 0;
bytes32 cursor = llsMap[SENTINEL_VALUE];
// Dynamically allocate the returned array as we iterate through the set, since we don't know the size
// beforehand.
// This is accomplished by first writing to memory after the free memory pointer,
// then updating the free memory pointer to cover the newly-allocated data.
// To the compiler, writes to memory after the free memory pointer are considered "memory safe".
// See https://docs.soliditylang.org/en/v0.8.22/assembly.html#memory-safety
// Stack variable lifting done when compiling with via-ir will only ever place variables into memory
// locations below the current free memory pointer, so it is safe to compile this library with via-ir.
// See https://docs.soliditylang.org/en/v0.8.22/yul.html#memoryguard
assembly ("memory-safe") {
// It is critical that no other memory allocations occur between:
// - loading the value of the free memory pointer into `ret`
// - updating the free memory pointer to point to the newly-allocated data, which is done after all
// the values have been written.
hooks := mload(0x40)
}
while (!LinkedListSetLib.isSentinel(cursor) && cursor != bytes32(0)) {
unchecked {
++size;
}
// Place the item into the return array manually. Since the size was just incremented, it will point to
// the next location to write to.
assembly ("memory-safe") {
mstore(add(hooks, mul(size, 0x20)), cursor)
}
cursor = llsMap[cursor];
}
// Load validation-assoc hooks
uint256 validationAssocHooksLength = valData.executionHookCount;
llsMap = valData.executionHooks.map;
// Notably, we invert the mapping lookup ordering for validation-assoc hooks, because we know the length
// ahead-of-time, thus saving an `sload`. This is why the cursor starts at SENTINEL_VALUE.
cursor = SENTINEL_VALUE;
for (uint256 i = 0; i < validationAssocHooksLength; ++i) {
unchecked {
++size;
}
cursor = llsMap[cursor];
assembly ("memory-safe") {
mstore(add(hooks, mul(size, 0x20)), cursor)
}
}
assembly ("memory-safe") {
// Update the free memory pointer with the now-known length of the array.
mstore(0x40, add(hooks, mul(add(size, 1), 0x20)))
// Set the length of the array.
mstore(hooks, size)
}
return hooks;
}
/// @notice Load execution hooks associated with an execution selector.
/// @param execData The execution storage struct to load from.
/// @return hooks An array of `HookConfig` items, representing the execution hooks.
function loadExecHooks(ExecutionStorage storage execData) internal view returns (HookConfig[] memory) {
HookConfig[] memory hooks;
SetValue[] memory hooksSet = LinkedListSetLib.getAll(execData.executionHooks);
// SetValue is internally a bytes31, and HookConfig is a bytes25, which are both left-aligned. This cast is
// safe so long as only HookConfig entries are added to the set.
assembly ("memory-safe") {
hooks := hooksSet
}
return hooks;
}
/// @notice Load execution hooks associated with a validation function.
/// @param valData The validation storage struct to load from.
/// @return hooks An array of `HookConfig` items, representing the execution hooks.
function loadExecHooks(ValidationStorage storage valData) internal view returns (HookConfig[] memory) {
uint256 validationAssocHooksLength = valData.executionHookCount;
return _loadValidationAssociatedHooks(validationAssocHooksLength, valData.executionHooks);
}
/// @notice Load validation hooks associated with a validation function.
/// @param valData The validation storage struct to load from.
/// @return hooks An array of `HookConfig` items, representing the validation hooks.
function loadValidationHooks(ValidationStorage storage valData) internal view returns (HookConfig[] memory) {
uint256 validationHookCount = valData.validationHookCount;
return _loadValidationAssociatedHooks(validationHookCount, valData.validationHooks);
}
/// @notice Load all selectors that have been added to a validation function.
/// @param valData The validation storage struct to load from.
/// @return selectors An array of the selectors the validation function is allowed to validate.
function loadSelectors(ValidationStorage storage valData) internal view returns (bytes4[] memory selectors) {
SetValue[] memory selectorsSet = LinkedListSetLib.getAll(valData.selectors);
// SetValue is internally a bytes31, and both bytes4 and bytes31 are left-aligned. This cast is safe so
// long as only bytes4 entries are added to the set.
assembly ("memory-safe") {
selectors := selectorsSet
}
return selectors;
}
/// @notice Reverses an array of `HookConfig` items in place.
function reverseArr(HookConfig[] memory hooks) internal pure {
bytes32[] memory casted;
// Cast to bytes32[] to use the shared reverseArr function
assembly ("memory-safe") {
casted := hooks
}
_reverseArr(casted);
}
/// @notice Reverses an array of `bytes4` items in place.
function reverseArr(bytes4[] memory selectors) internal pure {
bytes32[] memory casted;
// Cast to bytes32[] to use the shared reverseArr function
assembly ("memory-safe") {
casted := selectors
}
_reverseArr(casted);
}
/// @notice If the callData is an encoded function call to IModularAccount.execute, retrieves the target of the
/// call.
/// @param callData The calldata to check.
/// @return target The target of the call.
function getExecuteTarget(bytes calldata callData) internal pure returns (address) {
address target;
assembly ("memory-safe") {
target := and(calldataload(add(callData.offset, 4)), 0xffffffffffffffffffffffffffffffffffffffff)
}
return target;
}
/// @notice Captures a snapshot of the free memory pointer.
/// @return The snapshot of the free memory pointer.
function freezeFMP() internal pure returns (MemSnapshot) {
MemSnapshot snapshot;
assembly ("memory-safe") {
snapshot := mload(0x40)
}
return snapshot;
}
/// @notice Restores the free memory pointer to a previous snapshot.
/// @dev This invalidates any memory allocated since the snapshot was taken.
/// @param snapshot The snapshot to restore to.
function restoreFMP(MemSnapshot snapshot) internal pure {
assembly ("memory-safe") {
mstore(0x40, snapshot)
}
}
/// @notice Used to load both pre validation hooks and pre execution hooks, associated with a validation
/// function. The caller must first get the length of the hooks from the ValidationStorage struct.
function _loadValidationAssociatedHooks(uint256 hookCount, LinkedListSet storage hooks)
private
view
returns (HookConfig[] memory)
{
HookConfig[] memory hookArr = new HookConfig[](hookCount);
mapping(bytes32 => bytes32) storage llsMap = hooks.map;
bytes32 cursor = SENTINEL_VALUE;
for (uint256 i = 0; i < hookCount; ++i) {
cursor = llsMap[cursor];
hookArr[i] = HookConfig.wrap(bytes25(cursor));
}
return hookArr;
}
function _reverseArr(bytes32[] memory hooks) private pure {
uint256 len = hooks.length;
uint256 halfLen = len / 2;
for (uint256 i = 0; i < halfLen; ++i) {
uint256 j = len - i - 1;
(hooks[i], hooks[j]) = (hooks[j], hooks[i]);
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol";
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
/// @title Account Base
/// @author Alchemy
/// @dev An optimized implementation of a base account contract for ERC-4337.
/// Provides a public view function for getting the EntryPoint address, but does not provide one for getting the
/// nonce. The nonce may be retrieved from the EntryPoint contract.
/// Implementing contracts should override the _validateUserOp function to provide account-specific validation
/// logic.
abstract contract AccountBase is IAccount {
IEntryPoint internal immutable _ENTRY_POINT;
error NotEntryPoint();
constructor(IEntryPoint _entryPoint) {
_ENTRY_POINT = _entryPoint;
}
/// @inheritdoc IAccount
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
external
override
returns (uint256 validationData)
{
_requireFromEntryPoint();
validationData = _validateUserOp(userOp, userOpHash);
// Pay the prefund if necessary.
assembly ("memory-safe") {
if missingAccountFunds {
// Ignore failure (it's EntryPoint's job to verify, not the account's).
pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00))
}
}
}
/// @notice Gets the entry point for this account
/// @return entryPoint The entry point for this account
function entryPoint() external view returns (IEntryPoint) {
return _ENTRY_POINT;
}
/// @notice Account-specific implementation of user op validation. Override this function to define the
/// account's validation logic.
function _validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
internal
virtual
returns (uint256 validationData);
/// @notice Revert if the sender is not the EntryPoint.
function _requireFromEntryPoint() internal view {
if (msg.sender != address(_ENTRY_POINT)) {
revert NotEntryPoint();
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {AccountStorage, getAccountStorage} from "./AccountStorage.sol";
/// @title Account Storage Initializable
/// @author Alchemy
/// @notice A contract mixin that provides the functionality of OpenZeppelin's Initializable contract, using the
/// custom storage layout defined by the AccountStorage struct.
/// @dev The implementation logic here is modified from OpenZeppelin's Initializable contract from v5.0.
abstract contract AccountStorageInitializable {
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/// @notice Modifier to put on function intended to be called only once per implementation
/// @dev Reverts if the contract has already been initialized
modifier initializer() {
AccountStorage storage $ = getAccountStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$.initializing;
uint64 initialized = $.initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$.initialized = 1;
if (isTopLevelCall) {
$.initializing = true;
}
_;
if (isTopLevelCall) {
$.initializing = false;
emit Initialized(1);
}
}
/// @notice Internal function to disable calls to initialization functions
/// @dev Reverts if the contract is currently initializing.
function _disableInitializers() internal virtual {
AccountStorage storage $ = getAccountStorage();
if ($.initializing) {
revert InvalidInitialization();
}
if ($.initialized != type(uint8).max) {
$.initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {
HookConfig,
IModularAccount,
ModuleEntity
} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {
ExecutionDataView,
IModularAccountView,
ValidationDataView
} from "@erc6900/reference-implementation/interfaces/IModularAccountView.sol";
import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IModularAccountBase} from "../interfaces/IModularAccountBase.sol";
import {MemManagementLib} from "../libraries/MemManagementLib.sol";
import {ValidationLocatorLib} from "../libraries/ValidationLocatorLib.sol";
import {AccountBase} from "./AccountBase.sol";
import {ExecutionStorage, ValidationStorage, getAccountStorage} from "./AccountStorage.sol";
/// @title Modular Account View
/// @author Alchemy
/// @notice This abstract contract implements the two view functions to get validation and execution data for an
/// account.
abstract contract ModularAccountView is IModularAccountView {
/// @inheritdoc IModularAccountView
function getExecutionData(bytes4 selector) external view override returns (ExecutionDataView memory data) {
ExecutionStorage storage executionStorage = getAccountStorage().executionStorage[selector];
if (_isNativeFunction(uint32(selector))) {
bool isGlobalValidationAllowed = _isGlobalValidationAllowedNativeFunction(uint32(selector));
data.module = address(this);
data.skipRuntimeValidation = !isGlobalValidationAllowed;
data.allowGlobalValidation = isGlobalValidationAllowed;
if (!_isWrappedNativeFunction(uint32(selector))) {
// The native function does not run execution hooks associated with its selector, so
// we can return early.
return data;
}
} else {
data.module = executionStorage.module;
data.skipRuntimeValidation = executionStorage.skipRuntimeValidation;
data.allowGlobalValidation = executionStorage.allowGlobalValidation;
}
HookConfig[] memory hooks = MemManagementLib.loadExecHooks(executionStorage);
MemManagementLib.reverseArr(hooks);
data.executionHooks = hooks;
}
/// @inheritdoc IModularAccountView
function getValidationData(ModuleEntity validationFunction)
external
view
override
returns (ValidationDataView memory data)
{
ValidationStorage storage validationStorage =
getAccountStorage().validationStorage[ValidationLocatorLib.moduleEntityToLookupKey(validationFunction)];
data.validationFlags = validationStorage.validationFlags;
data.validationHooks = MemManagementLib.loadValidationHooks(validationStorage);
MemManagementLib.reverseArr(data.validationHooks);
HookConfig[] memory hooks = MemManagementLib.loadExecHooks(validationStorage);
MemManagementLib.reverseArr(hooks);
data.executionHooks = hooks;
bytes4[] memory selectors = MemManagementLib.loadSelectors(validationStorage);
MemManagementLib.reverseArr(selectors);
data.selectors = selectors;
}
function _isNativeFunction(uint32 selector) internal pure virtual returns (bool) {
return (
_isGlobalValidationAllowedNativeFunction(selector)
|| selector == uint32(AccountBase.entryPoint.selector)
|| selector == uint32(AccountBase.validateUserOp.selector)
|| selector == uint32(IERC1155Receiver.onERC1155BatchReceived.selector)
|| selector == uint32(IERC1155Receiver.onERC1155Received.selector)
|| selector == uint32(IERC1271.isValidSignature.selector)
|| selector == uint32(IERC165.supportsInterface.selector)
|| selector == uint32(IERC721Receiver.onERC721Received.selector)
|| selector == uint32(IModularAccount.accountId.selector)
|| selector == uint32(IModularAccountView.getExecutionData.selector)
|| selector == uint32(IModularAccountView.getValidationData.selector)
|| selector == uint32(UUPSUpgradeable.proxiableUUID.selector)
);
}
/// @dev Check whether a function is a native function that allows global validation.
function _isGlobalValidationAllowedNativeFunction(uint32 selector) internal pure virtual returns (bool) {
return (
_isWrappedNativeFunction(selector) || selector == uint32(IAccountExecute.executeUserOp.selector)
|| selector == uint32(IModularAccount.executeWithRuntimeValidation.selector)
);
}
/// @dev Check whether a function is a native function that has the `wrapNativeFunction` modifier applied,
/// which means it runs execution hooks associated with its selector.
function _isWrappedNativeFunction(uint32 selector) internal pure virtual returns (bool) {
return (
selector == uint32(IModularAccount.execute.selector)
|| selector == uint32(IModularAccount.executeBatch.selector)
|| selector == uint32(IModularAccount.installExecution.selector)
|| selector == uint32(IModularAccount.installValidation.selector)
|| selector == uint32(IModularAccount.uninstallExecution.selector)
|| selector == uint32(IModularAccount.uninstallValidation.selector)
|| selector == uint32(IModularAccountBase.performCreate.selector)
|| selector == uint32(UUPSUpgradeable.upgradeToAndCall.selector)
);
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {MAX_VALIDATION_ASSOC_HOOKS} from "@erc6900/reference-implementation/helpers/Constants.sol";
import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol";
import {
HookConfig,
IModularAccount,
ModuleEntity,
ValidationConfig,
ValidationFlags
} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol";
import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol";
import {HookConfigLib} from "@erc6900/reference-implementation/libraries/HookConfigLib.sol";
import {ModuleEntityLib} from "@erc6900/reference-implementation/libraries/ModuleEntityLib.sol";
import {ValidationConfigLib} from "@erc6900/reference-implementation/libraries/ValidationConfigLib.sol";
import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol";
import {MemManagementLib} from "../libraries/MemManagementLib.sol";
import {ModuleInstallCommonsLib} from "../libraries/ModuleInstallCommonsLib.sol";
import {ValidationLocatorLib} from "../libraries/ValidationLocatorLib.sol";
import {ValidationStorage, getAccountStorage, toSetValue} from "./AccountStorage.sol";
/// @title Module Manager Internals
/// @author Alchemy
/// @notice This abstract contract hosts the internal installation and uninstallation methods of execution and
/// validation functions. Methods here update the account storage.
abstract contract ModuleManagerInternals is IModularAccount {
using LinkedListSetLib for LinkedListSet;
using ModuleEntityLib for ModuleEntity;
using ValidationConfigLib for ValidationConfig;
using HookConfigLib for HookConfig;
error ArrayLengthMismatch();
error PreValidationHookDuplicate();
error ValidationEntityIdInUse();
error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction);
error ValidationAssocHookLimitExceeded();
function _setValidationFunction(
ValidationStorage storage validationStorage,
ValidationConfig validationConfig,
bytes4[] calldata selectors
) internal {
// To allow for flag updates and appending hooks and selectors, two cases should be considered:
// - stored module address is zero - store the new validation module address
// - stored module address already holds the address of the validation module being installed - update
// flags and selectors.
// If the stored module address does not match, revert, as the validation entity ID must be unique over the
// account.
address storedAddress = validationStorage.module;
(address moduleAddress,, ValidationFlags validationFlags) = validationConfig.unpackUnderlying();
if (storedAddress == address(0)) {
validationStorage.module = moduleAddress;
} else if (storedAddress != moduleAddress) {
revert ValidationEntityIdInUse();
}
validationStorage.validationFlags = validationFlags;
uint256 length = selectors.length;
for (uint256 i = 0; i < length; ++i) {
bytes4 selector = selectors[i];
if (!validationStorage.selectors.tryAdd(toSetValue(selector))) {
revert ValidationAlreadySet(selector, validationConfig.moduleEntity());
}
}
}
function _removeValidationFunction(ValidationStorage storage validationStorage) internal {
validationStorage.module = address(0);
validationStorage.validationFlags = ValidationFlags.wrap(0);
validationStorage.validationHookCount = 0;
validationStorage.executionHookCount = 0;
}
function _installValidation(
ValidationConfig validationConfig,
bytes4[] calldata selectors,
bytes calldata installData,
bytes[] calldata hooks
) internal {
ValidationStorage storage _validationStorage =
getAccountStorage().validationStorage[ValidationLocatorLib.configToLookupKey(validationConfig)];
_setValidationFunction(_validationStorage, validationConfig, selectors);
uint256 length = hooks.length;
for (uint256 i = 0; i < length; ++i) {
HookConfig hookConfig = HookConfig.wrap(bytes25(hooks[i][:25]));
bytes calldata hookData = hooks[i][25:];
if (hookConfig.isValidationHook()) {
// Increment the stored length of validation hooks, and revert if the limit is exceeded.
// Safety:
// validationHookCount is uint8, so math operations here should never overflow
unchecked {
if (uint256(_validationStorage.validationHookCount) + 1 > MAX_VALIDATION_ASSOC_HOOKS) {
revert ValidationAssocHookLimitExceeded();
}
++_validationStorage.validationHookCount;
}
if (!_validationStorage.validationHooks.tryAdd(toSetValue(hookConfig))) {
revert PreValidationHookDuplicate();
}
ModuleInstallCommonsLib.onInstall(
hookConfig.module(), hookData, type(IValidationHookModule).interfaceId
);
} else {
// Hook is an execution hook
// Safety:
// validationHookCount is uint8, so math operations here should never overflow
unchecked {
if (uint256(_validationStorage.executionHookCount) + 1 > MAX_VALIDATION_ASSOC_HOOKS) {
revert ValidationAssocHookLimitExceeded();
}
++_validationStorage.executionHookCount;
}
ModuleInstallCommonsLib.addExecHooks(_validationStorage.executionHooks, hookConfig);
ModuleInstallCommonsLib.onInstall(
hookConfig.module(), hookData, type(IExecutionHookModule).interfaceId
);
}
}
ModuleInstallCommonsLib.onInstall(
validationConfig.module(), installData, type(IValidationModule).interfaceId
);
emit ValidationInstalled(validationConfig.module(), validationConfig.entityId());
}
function _uninstallValidation(
ModuleEntity validationFunction,
bytes calldata uninstallData,
bytes[] calldata hookUninstallDatas
) internal {
ValidationStorage storage _validationStorage =
getAccountStorage().validationStorage[ValidationLocatorLib.moduleEntityToLookupKey(validationFunction)];
bool onUninstallSuccess = true;
// Send `onUninstall` to hooks
if (hookUninstallDatas.length > 0) {
HookConfig[] memory execHooks = MemManagementLib.loadExecHooks(_validationStorage);
HookConfig[] memory validationHooks = MemManagementLib.loadValidationHooks(_validationStorage);
// If any uninstall data is provided, assert it is of the correct length.
if (hookUninstallDatas.length != validationHooks.length + execHooks.length) {
revert ArrayLengthMismatch();
}
// Hook uninstall data is provided in the order of pre validation hooks, then execution hooks.
uint256 hookIndex = 0;
uint256 length = validationHooks.length;
for (uint256 i = 0; i < length; ++i) {
bytes calldata hookData = hookUninstallDatas[hookIndex];
(address hookModule,) = ModuleEntityLib.unpack(validationHooks[i].moduleEntity());
onUninstallSuccess =
onUninstallSuccess && ModuleInstallCommonsLib.onUninstall(hookModule, hookData);
hookIndex++;
}
length = execHooks.length;
for (uint256 i = 0; i < length; ++i) {
bytes calldata hookData = hookUninstallDatas[hookIndex];
address hookModule = execHooks[i].module();
onUninstallSuccess =
onUninstallSuccess && ModuleInstallCommonsLib.onUninstall(hookModule, hookData);
hookIndex++;
}
}
// Clear all stored hooks. The lengths of the hooks are cleared in `_removeValidationFunction`.
_validationStorage.validationHooks.clear();
_validationStorage.executionHooks.clear();
// Clear selectors
_validationStorage.selectors.clear();
// Clear validation function data.
// Must be done at the end, because the hook lengths are accessed in the loop above.
_removeValidationFunction(_validationStorage);
(address module, uint32 entityId) = ModuleEntityLib.unpack(validationFunction);
onUninstallSuccess = onUninstallSuccess && ModuleInstallCommonsLib.onUninstall(module, uninstallData);
emit ValidationUninstalled(module, entityId, onUninstallSuccess);
}
}// This file is part of Modular Account.
//
// Copyright 2024 Alchemy Insights, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.
pragma solidity ^0.8.26;
import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
/// @title Token Receiver
/// @author Alchemy
/// @notice Token receiver, supports tokens callbacks to allow the account to receive supported tokens.
abstract contract TokenReceiver is IERC721Receiver, IERC1155Receiver {
/// @inheritdoc IERC721Receiver
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
/// @inheritdoc IERC1155Receiver
function onERC1155Received(address, address, uint256, uint256, bytes calldata)
external
pure
override
returns (bytes4)
{
return IERC1155Receiver.onERC1155Received.selector;
}
/// @inheritdoc IERC1155Receiver
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
external
pure
override
returns (bytes4)
{
return IERC1155Receiver.onERC1155BatchReceived.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Interface that must be implemented by smart contracts in order to receive
* ERC-1155 token transfers.
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
interface IAccount {
/**
* Validate user's signature and nonce
* the entryPoint will make the call to the recipient only if this validation call returns successfully.
* signature failure should be reported by returning SIG_VALIDATION_FAILED (1).
* This allows making a "simulation call" without a valid signature
* Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure.
*
* @dev Must validate caller is the entryPoint.
* Must validate the signature and nonce
* @param userOp - The operation that is about to be executed.
* @param userOpHash - Hash of the user's request data. can be used as the basis for signature.
* @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint.
* This is the minimum amount to transfer to the sender(entryPoint) to be
* able to make the call. The excess is left as a deposit in the entrypoint
* for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()".
* In case there is a paymaster in the request (or the current deposit is high
* enough), this value will be zero.
* @return validationData - Packaged ValidationData structure. use `_packValidationData` and
* `_unpackValidationData` to encode and decode.
* <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure,
* otherwise, an address of an "authorizer" contract.
* <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite"
* <6-byte> validAfter - First timestamp this operation is valid
* If an account doesn't use time-range, it is enough to
* return SIG_VALIDATION_FAILED value (1) for signature failure.
* Note that the validation code cannot use block.timestamp (or block.number) directly.
*/
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.20;
import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
/**
* @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC1967-compliant implementation pointing to self.
* See {_onlyProxy}.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.20;
import {IBeacon} from "../beacon/IBeacon.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*/
library ERC1967Utils {
// We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
// This will be fixed in Solidity 0.8.21. At that point we should remove these events.
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.20;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}{
"remappings": [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"@eth-infinitism/account-abstraction/=node_modules/account-abstraction/contracts/",
"account-abstraction/=node_modules/account-abstraction/contracts/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"@alchemy/light-account/=lib/light-account/",
"solady/=node_modules/solady/src/",
"@erc6900/reference-implementation/=node_modules/@erc6900/reference-implementation/src/",
"forge-gas-snapshot/=lib/forge-gas-snapshot/src/",
"forge-std/=lib/forge-std/src/",
"webauthn-sol/=lib/webauthn-sol/",
"FreshCryptoLib/=lib/webauthn-sol/lib/FreshCryptoLib/solidity/src/",
"openzeppelin-contracts/=lib/webauthn-sol/lib/openzeppelin-contracts/"
],
"optimizer": {
"enabled": true,
"runs": 50000
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "paris",
"viaIR": true,
"libraries": {}
}Contract ABI
API[{"inputs":[{"internalType":"contract IEntryPoint","name":"entryPoint","type":"address"},{"internalType":"contract ExecutionInstallDelegate","name":"executionInstallDelegate","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"CreateFailed","type":"error"},{"inputs":[],"name":"DeferredActionSignatureInvalid","type":"error"},{"inputs":[],"name":"DeferredValidationHasValidationHooks","type":"error"},{"inputs":[{"internalType":"HookConfig","name":"hookConfig","type":"bytes25"}],"name":"ExecutionHookAlreadySet","type":"error"},{"inputs":[],"name":"FallbackSignerDisabled","type":"error"},{"inputs":[],"name":"FallbackSignerMismatch","type":"error"},{"inputs":[],"name":"FallbackValidationInstallationNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"InterfaceNotSupported","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidSignatureType","type":"error"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"ModuleInstallCallbackFailed","type":"error"},{"inputs":[],"name":"NonCanonicalEncoding","type":"error"},{"inputs":[],"name":"NotEntryPoint","type":"error"},{"inputs":[],"name":"PreValidationHookDuplicate","type":"error"},{"inputs":[],"name":"RequireUserOperationContext","type":"error"},{"inputs":[],"name":"SegmentOutOfOrder","type":"error"},{"inputs":[],"name":"SelfCallRecursionDepthExceeded","type":"error"},{"inputs":[{"internalType":"ModuleEntity","name":"validationFunction","type":"bytes24"}],"name":"SignatureValidationInvalid","type":"error"},{"inputs":[],"name":"UnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"ModuleEntity","name":"validationFunction","type":"bytes24"},{"internalType":"address","name":"aggregator","type":"address"}],"name":"UnexpectedAggregator","type":"error"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"UnrecognizedFunction","type":"error"},{"inputs":[],"name":"UpgradeFailed","type":"error"},{"inputs":[{"internalType":"ModuleEntity","name":"validationFunction","type":"bytes24"}],"name":"UserOpValidationInvalid","type":"error"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"ModuleEntity","name":"validationFunction","type":"bytes24"}],"name":"ValidationAlreadySet","type":"error"},{"inputs":[],"name":"ValidationAssocHookLimitExceeded","type":"error"},{"inputs":[],"name":"ValidationEntityIdInUse","type":"error"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"ValidationFunctionMissing","type":"error"},{"inputs":[],"name":"ValidationSignatureSegmentMissing","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"},{"components":[{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"bool","name":"skipRuntimeValidation","type":"bool"},{"internalType":"bool","name":"allowGlobalValidation","type":"bool"}],"internalType":"struct ManifestExecutionFunction[]","name":"executionFunctions","type":"tuple[]"},{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"uint32","name":"entityId","type":"uint32"},{"internalType":"bool","name":"isPreHook","type":"bool"},{"internalType":"bool","name":"isPostHook","type":"bool"}],"internalType":"struct ManifestExecutionHook[]","name":"executionHooks","type":"tuple[]"},{"internalType":"bytes4[]","name":"interfaceIds","type":"bytes4[]"}],"indexed":false,"internalType":"struct ExecutionManifest","name":"manifest","type":"tuple"}],"name":"ExecutionInstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"},{"indexed":false,"internalType":"bool","name":"onUninstallSucceeded","type":"bool"},{"components":[{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"bool","name":"skipRuntimeValidation","type":"bool"},{"internalType":"bool","name":"allowGlobalValidation","type":"bool"}],"internalType":"struct ManifestExecutionFunction[]","name":"executionFunctions","type":"tuple[]"},{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"uint32","name":"entityId","type":"uint32"},{"internalType":"bool","name":"isPreHook","type":"bool"},{"internalType":"bool","name":"isPostHook","type":"bool"}],"internalType":"struct ManifestExecutionHook[]","name":"executionHooks","type":"tuple[]"},{"internalType":"bytes4[]","name":"interfaceIds","type":"bytes4[]"}],"indexed":false,"internalType":"struct ExecutionManifest","name":"manifest","type":"tuple"}],"name":"ExecutionUninstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newFallbackSigner","type":"address"},{"indexed":false,"internalType":"bool","name":"isDisabled","type":"bool"}],"name":"FallbackSignerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"},{"indexed":true,"internalType":"uint32","name":"entityId","type":"uint32"}],"name":"ValidationInstalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"},{"indexed":true,"internalType":"uint32","name":"entityId","type":"uint32"},{"indexed":false,"internalType":"bool","name":"onUninstallSucceeded","type":"bool"}],"name":"ValidationUninstalled","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"accountId","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"entryPoint","outputs":[{"internalType":"contract IEntryPoint","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Call[]","name":"calls","type":"tuple[]"}],"name":"executeBatch","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"executeUserOp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"authorization","type":"bytes"}],"name":"executeWithRuntimeValidation","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"getExecutionData","outputs":[{"components":[{"internalType":"address","name":"module","type":"address"},{"internalType":"bool","name":"skipRuntimeValidation","type":"bool"},{"internalType":"bool","name":"allowGlobalValidation","type":"bool"},{"internalType":"HookConfig[]","name":"executionHooks","type":"bytes25[]"}],"internalType":"struct ExecutionDataView","name":"data","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFallbackSignerData","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"ModuleEntity","name":"validationFunction","type":"bytes24"}],"name":"getValidationData","outputs":[{"components":[{"internalType":"ValidationFlags","name":"validationFlags","type":"uint8"},{"internalType":"HookConfig[]","name":"validationHooks","type":"bytes25[]"},{"internalType":"HookConfig[]","name":"executionHooks","type":"bytes25[]"},{"internalType":"bytes4[]","name":"selectors","type":"bytes4[]"}],"internalType":"struct ValidationDataView","name":"data","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"components":[{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"bool","name":"skipRuntimeValidation","type":"bool"},{"internalType":"bool","name":"allowGlobalValidation","type":"bool"}],"internalType":"struct ManifestExecutionFunction[]","name":"executionFunctions","type":"tuple[]"},{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"uint32","name":"entityId","type":"uint32"},{"internalType":"bool","name":"isPreHook","type":"bool"},{"internalType":"bool","name":"isPostHook","type":"bool"}],"internalType":"struct ManifestExecutionHook[]","name":"executionHooks","type":"tuple[]"},{"internalType":"bytes4[]","name":"interfaceIds","type":"bytes4[]"}],"internalType":"struct ExecutionManifest","name":"manifest","type":"tuple"},{"internalType":"bytes","name":"moduleInstallData","type":"bytes"}],"name":"installExecution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"ValidationConfig","name":"validationConfig","type":"bytes25"},{"internalType":"bytes4[]","name":"selectors","type":"bytes4[]"},{"internalType":"bytes","name":"installData","type":"bytes"},{"internalType":"bytes[]","name":"hooks","type":"bytes[]"}],"name":"installValidation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bool","name":"isCreate2","type":"bool"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"performCreate","outputs":[{"internalType":"address","name":"createdAddr","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"components":[{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"bool","name":"skipRuntimeValidation","type":"bool"},{"internalType":"bool","name":"allowGlobalValidation","type":"bool"}],"internalType":"struct ManifestExecutionFunction[]","name":"executionFunctions","type":"tuple[]"},{"components":[{"internalType":"bytes4","name":"executionSelector","type":"bytes4"},{"internalType":"uint32","name":"entityId","type":"uint32"},{"internalType":"bool","name":"isPreHook","type":"bool"},{"internalType":"bool","name":"isPostHook","type":"bool"}],"internalType":"struct ManifestExecutionHook[]","name":"executionHooks","type":"tuple[]"},{"internalType":"bytes4[]","name":"interfaceIds","type":"bytes4[]"}],"internalType":"struct ExecutionManifest","name":"manifest","type":"tuple"},{"internalType":"bytes","name":"moduleUninstallData","type":"bytes"}],"name":"uninstallExecution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"ModuleEntity","name":"validationFunction","type":"bytes24"},{"internalType":"bytes","name":"uninstallData","type":"bytes"},{"internalType":"bytes[]","name":"hookUninstallData","type":"bytes[]"}],"name":"uninstallValidation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fallbackSigner","type":"address"},{"internalType":"bool","name":"isDisabled","type":"bool"}],"name":"updateFallbackSignerData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"missingAccountFunds","type":"uint256"}],"name":"validateUserOp","outputs":[{"internalType":"uint256","name":"validationData","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.