Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
Latest 1 internal transaction
| Parent Transaction Hash | Block | From | To | Amount | ||
|---|---|---|---|---|---|---|
| 110593160 | 327 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
UniActionPolicy
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 833 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "../../DataTypes.sol";
import { IActionPolicy, IPolicy, VALIDATION_SUCCESS, VALIDATION_FAILED } from "../../interfaces/IPolicy.sol";
import { SubModuleLib } from "../../lib/SubModuleLib.sol";
import { IERC165 } from "forge-std/interfaces/IERC165.sol";
error PolicyNotInitialized(ConfigId id, address mxer, address account);
error ValueLimitExceeded(ConfigId id, uint256 value, uint256 limit);
struct ActionConfig {
uint256 valueLimitPerUse;
ParamRules paramRules;
}
struct ParamRules {
uint256 length;
ParamRule[16] rules;
}
struct ParamRule {
ParamCondition condition;
uint64 offset;
bool isLimited;
bytes32 ref;
LimitUsage usage;
}
struct LimitUsage {
uint256 limit;
uint256 used;
}
enum ParamCondition {
EQUAL,
GREATER_THAN,
LESS_THAN,
GREATER_THAN_OR_EQUAL,
LESS_THAN_OR_EQUAL,
NOT_EQUAL,
IN_RANGE
}
/**
* @title UniActionPolicy: Universal Action Policy
* @dev A policy that allows defining custom rules for actions based on function signatures.
* Rules can be configured for function arguments with conditions.
* So the argument is compared to a reference value against the the condition.
* Also, rules feature usage limits for arguments.
* For example, you can limit not just max amount for a transfer,
* but also limit the total amount to be transferred within a permission.
* Limit is uint256 so you can control any kind of numerable params.
*
* If you need to deal with dynamic-length arguments, such as bytes, please refer to
* https://docs.soliditylang.org/en/v0.8.24/abi-spec.html#function-selector-and-argument-encoding
* to learn more about how dynamic arguments are represented in the calldata
* and which offsets should be used to access them.
*/
contract UniActionPolicy is IActionPolicy {
enum Status {
NA,
Live,
Deprecated
}
using SubModuleLib for bytes;
using UniActionLib for *;
mapping(ConfigId id => mapping(address msgSender => mapping(address userOpSender => ActionConfig))) public
actionConfigs;
/**
* @dev Checks if the action is allowed based on the args rules defined in the policy.
*/
function checkAction(
ConfigId id,
address account,
address,
uint256 value,
bytes calldata data
)
external
returns (uint256)
{
ActionConfig storage config = actionConfigs[id][msg.sender][account];
require(config.paramRules.length > 0, PolicyNotInitialized(id, msg.sender, account));
require(value <= config.valueLimitPerUse, ValueLimitExceeded(id, value, config.valueLimitPerUse));
uint256 length = config.paramRules.length;
for (uint256 i = 0; i < length; i++) {
if (!config.paramRules.rules[i].check(data)) return VALIDATION_FAILED;
}
return VALIDATION_SUCCESS;
}
function _initPolicy(ConfigId id, address mxer, address opSender, bytes calldata _data) internal {
ActionConfig memory config = abi.decode(_data, (ActionConfig));
actionConfigs[id][mxer][opSender].fill(config);
}
/**
* Initializes the policy to be used by given account through multiplexer (msg.sender) such as Smart Sessions.
* Overwrites state.
* @notice ATTENTION: This method is called during permission installation as part of the enabling policies flow.
* A secure policy would minimize external calls from this method (ideally, to 0) to prevent passing control flow to
* external contracts.
*/
function initializeWithMultiplexer(address account, ConfigId configId, bytes calldata initData) external {
_initPolicy(configId, msg.sender, account, initData);
emit IPolicy.PolicySet(configId, msg.sender, account);
}
function supportsInterface(bytes4 interfaceID) external pure override returns (bool) {
return (
interfaceID == type(IERC165).interfaceId || interfaceID == type(IPolicy).interfaceId
|| interfaceID == type(IActionPolicy).interfaceId
);
}
}
library UniActionLib {
/**
* @dev parses the function arg from the calldata based on the offset
* and compares it to the reference value based on the condition.
* Also checks if the limit is reached/exceeded.
*/
function check(ParamRule storage rule, bytes calldata data) internal returns (bool) {
bytes32 param = bytes32(data[4 + rule.offset:4 + rule.offset + 32]);
// CHECK ParamCondition
if (rule.condition == ParamCondition.EQUAL && param != rule.ref) {
return false;
} else if (rule.condition == ParamCondition.GREATER_THAN && param <= rule.ref) {
return false;
} else if (rule.condition == ParamCondition.LESS_THAN && param >= rule.ref) {
return false;
} else if (rule.condition == ParamCondition.GREATER_THAN_OR_EQUAL && param < rule.ref) {
return false;
} else if (rule.condition == ParamCondition.LESS_THAN_OR_EQUAL && param > rule.ref) {
return false;
} else if (rule.condition == ParamCondition.NOT_EQUAL && param == rule.ref) {
return false;
} else if (rule.condition == ParamCondition.IN_RANGE) {
// in this case rule.ref is abi.encodePacked(uint128(min), uint128(max))
if (
param < (rule.ref >> 128)
|| param > (rule.ref & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff)
) {
return false;
}
}
// CHECK PARAM LIMIT
if (rule.isLimited) {
if (rule.usage.used + uint256(param) > rule.usage.limit) {
return false;
}
rule.usage.used += uint256(param);
}
return true;
}
function fill(ActionConfig storage $config, ActionConfig memory config) internal {
$config.valueLimitPerUse = config.valueLimitPerUse;
$config.paramRules.length = config.paramRules.length;
for (uint256 i; i < config.paramRules.length; i++) {
$config.paramRules.rules[i] = config.paramRules.rules[i];
}
}
}
/**
* Further development:
*
* - Add compound value limit.
* struct ActionConfig {
* uint256 valueLimitPerUse;
* uint256 totalValueLimit;
* uint256 valueUsed;
* ParamRules paramRules;
* }
*
* - Add param relations.
*
* Add this to ActionConfig => Relation[] paramRelations;
* struct Relation {
* address verifier;
* bytes4 selector;
* bytes1 argsAmount;
* uint64[4] offsets;
* bytes32 context;
* }
* Add checking for relations.
*/// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.25;
import "./utils/AssociatedArrayLib.sol";
import { IRegistry, ModuleType } from "./interfaces/IRegistry.sol";
import "./interfaces/ISessionValidator.sol";
import { EnumerableSet } from "./utils/EnumerableSet4337.sol";
import { EnumerableMap } from "./utils/EnumerableMap4337.sol";
import { FlatBytesLib } from "flatbytes/BytesLib.sol";
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Parameters */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct EnableSession {
uint8 chainDigestIndex;
ChainDigest[] hashesAndChainIds;
Session sessionToEnable;
// in order to enable a session, the smart account has to sign a digest. The signature for this is stored here.
bytes permissionEnableSig;
}
struct ChainDigest {
uint64 chainId;
bytes32 sessionDigest;
}
/**
*
* Represents a Session structure with various attributes for managing user operations and policies.
*
* Attributes:
* sessionValidator (ISessionValidator): The validator contract for signing user operations.
* Every userOp must be signed by the session key "owner". The signature is validated
* via a stateless external contract (ISessionValidator) that can implement different
* means of validation.
*
* sessionValidatorInitData (bytes): Initialization data for the ISessionValidator contract.
* The ISessionValidator contract can be configured with different parameters that are
* passed in this field.
*
* salt (bytes32): A unique identifier to prevent collision between sessions.
* A session key owner can have multiple sessions with the same parameters. To facilitate
* this, a salt is necessary to avoid collision.
*
* userOpPolicies (PolicyData[]): An array of policy data for user operations.
* When every session can have multiple policies set.
*
* erc7739Policies (ERC7739Data): ERC1271 Policies specific to the ERC7739 standard.
*
* actions (ActionData[]): An array of action data for specifying function-specific policies.
* A common use case of session keys is to scope access to a specific target and function
* selector. SmartSession calls this "Action". With ActionData, we can specify policies
* that are only run if a 7579 execution contains a specific action.
*/
struct Session {
ISessionValidator sessionValidator;
bytes sessionValidatorInitData;
bytes32 salt;
PolicyData[] userOpPolicies;
ERC7739Data erc7739Policies;
ActionData[] actions;
bool permitERC4337Paymaster;
}
struct MultiChainSession {
ChainSession[] sessionsAndChainIds;
}
struct ChainSession {
uint64 chainId;
Session session;
}
// Policy data is a struct that contains the policy address and the initialization data for the policy.
struct PolicyData {
address policy;
bytes initData;
}
// Action data is a struct that contains the actionId and the policies that are associated with this action.
struct ActionData {
bytes4 actionTargetSelector;
address actionTarget;
PolicyData[] actionPolicies;
}
struct ERC7739Context {
// we can not use a detailed EIP712Domain struct here.
// EIP712 specifies: Protocol designers only need to include the fields that make sense for their signing domain.
// Unused fields are left out of the struct type.
bytes32 appDomainSeparator;
string[] contentNames;
}
struct EIP712Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}
struct ERC7739Data {
ERC7739Context[] allowedERC7739Content;
PolicyData[] erc1271Policies;
}
enum SmartSessionMode {
USE,
ENABLE,
UNSAFE_ENABLE
}
struct ERC7739ContextHashes {
bytes32 appDomainSeparator;
bytes32[] contentNameHashes;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Storage */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
struct SignerConf {
ISessionValidator sessionValidator;
FlatBytesLib.Bytes config; // using FlatBytes to get around storage slot limitations
}
struct Policy {
mapping(PermissionId => EnumerableSet.AddressSet) policyList;
}
struct EnumerableActionPolicy {
mapping(ActionId => Policy) actionPolicies;
mapping(PermissionId => EnumerableSet.Bytes32Set) enabledActionIds;
}
struct EnumerableERC7739Config {
mapping(PermissionId => mapping(bytes32 appDomainSeparator => EnumerableSet.Bytes32Set)) enabledContentNames;
mapping(PermissionId => EnumerableSet.Bytes32Set) enabledDomainSeparators;
}
// struct EnumerableERC7739Config {
// mapping(PermissionId => EnumerableMap.Bytes32ToBytes32Map) erc1271Policies;
// }
// mapping(PermissionId => EnumerableSet.Bytes32Set) enabledDomainSeparators;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Custom Types & Constants */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
type PermissionId is bytes32;
type ActionId is bytes32;
type ActionPolicyId is bytes32;
type UserOpPolicyId is bytes32;
type Erc1271PolicyId is bytes32;
type ConfigId is bytes32;
type ValidationData is uint256;
ActionId constant EMPTY_ACTIONID = ActionId.wrap(bytes32(0));
PermissionId constant EMPTY_PERMISSIONID = PermissionId.wrap(bytes32(0));
UserOpPolicyId constant EMPTY_USEROPPOLICYID = UserOpPolicyId.wrap(bytes32(0));
ActionPolicyId constant EMPTY_ACTIONPOLICYID = ActionPolicyId.wrap(bytes32(0));
Erc1271PolicyId constant EMPTY_ERC1271POLICYID = Erc1271PolicyId.wrap(bytes32(0));
ConfigId constant EMPTY_CONFIGID = ConfigId.wrap(bytes32(0));
ValidationData constant ERC4337_VALIDATION_SUCCESS = ValidationData.wrap(0);
ValidationData constant ERC4337_VALIDATION_FAILED = ValidationData.wrap(1);
bytes4 constant EIP1271_SUCCESS = 0x1626ba7e;
bytes4 constant EIP1271_FAILED = 0xFFFFFFFF;
uint256 constant ERC7579_MODULE_TYPE_VALIDATOR = 1;
uint256 constant ERC7579_MODULE_TYPE_EXECUTOR = 2;
uint256 constant ERC7579_MODULE_TYPE_FALLBACK = 3;
uint256 constant ERC7579_MODULE_TYPE_HOOK = 4;
uint256 constant ERC7579_MODULE_TYPE_STATELESS_VALIDATOR = 7;
enum PolicyType {
NA,
USER_OP,
ACTION,
ERC1271
}
IRegistry constant registry = IRegistry(0x000000000069E2a187AEFFb852bF3cCdC95151B2);
ModuleType constant VALIDATOR_MODULE_TYPE = ModuleType.wrap(ERC7579_MODULE_TYPE_VALIDATOR);
// ActionId for a fallback action policy. This id will be used if both action
// target and selector are set to 1. During validation if the current target and
// selector does not have a set action policy, then the fallback will be used if
// enabled.
address constant FALLBACK_TARGET_FLAG = address(1);
bytes4 constant FALLBACK_TARGET_SELECTOR_FLAG = 0x00000001;
bytes4 constant FALLBACK_TARGET_SELECTOR_FLAG_PERMITTED_TO_CALL_SMARTSESSION = 0x00000002;
// keccak256(abi.encodePacked(FALLBACK_TARGET_FLAG, FALLBACK_TARGET_SELECTOR_FLAG))
ActionId constant FALLBACK_ACTIONID = ActionId.wrap(0xd884b6afa19f8ace90a388daca691e4e28f20cdac5aeefd46ad8bd1c074d28cf);
// keccak256(abi.encodePacked(FALLBACK_TARGET_FLAG, FALLBACK_TARGET_SELECTOR_FLAG_PERMITTED_TO_CALL_SMARTSESSION))
ActionId constant FALLBACK_ACTIONID_SMARTSESSION_CALL =
ActionId.wrap(0x986126569d6396d837d7adeb3ca726199afaf83546f38726e6f331bb92d8e9d6);
// A unique ValidationData value to retry a policy check with the FALLBACK_ACTIONID.
ValidationData constant RETRY_WITH_FALLBACK = ValidationData.wrap(uint256(0x50FFBAAD));
using { validationDataEq as == } for ValidationData global;
using { validationDataNeq as != } for ValidationData global;
function validationDataEq(ValidationData uid1, ValidationData uid2) pure returns (bool) {
return ValidationData.unwrap(uid1) == ValidationData.unwrap(uid2);
}
function validationDataNeq(ValidationData uid1, ValidationData uid2) pure returns (bool) {
return ValidationData.unwrap(uid1) != ValidationData.unwrap(uid2);
}
using { permissionIdEq as == } for PermissionId global;
using { permissionIdNeq as != } for PermissionId global;
function permissionIdEq(PermissionId uid1, PermissionId uid2) pure returns (bool) {
return PermissionId.unwrap(uid1) == PermissionId.unwrap(uid2);
}
function permissionIdNeq(PermissionId uid1, PermissionId uid2) pure returns (bool) {
return PermissionId.unwrap(uid1) != PermissionId.unwrap(uid2);
}
// ActionId
using { actionIdEq as == } for ActionId global;
using { actionIdNeq as != } for ActionId global;
function actionIdEq(ActionId id1, ActionId id2) pure returns (bool) {
return ActionId.unwrap(id1) == ActionId.unwrap(id2);
}
function actionIdNeq(ActionId id1, ActionId id2) pure returns (bool) {
return ActionId.unwrap(id1) != ActionId.unwrap(id2);
}
// UserOpPolicyId
using { userOpPolicyIdEq as == } for UserOpPolicyId global;
using { userOpPolicyIdNeq as != } for UserOpPolicyId global;
function userOpPolicyIdEq(UserOpPolicyId id1, UserOpPolicyId id2) pure returns (bool) {
return UserOpPolicyId.unwrap(id1) == UserOpPolicyId.unwrap(id2);
}
function userOpPolicyIdNeq(UserOpPolicyId id1, UserOpPolicyId id2) pure returns (bool) {
return UserOpPolicyId.unwrap(id1) != UserOpPolicyId.unwrap(id2);
}
// ActionPolicyId
using { actionPolicyIdEq as == } for ActionPolicyId global;
using { actionPolicyIdNeq as != } for ActionPolicyId global;
function actionPolicyIdEq(ActionPolicyId id1, ActionPolicyId id2) pure returns (bool) {
return ActionPolicyId.unwrap(id1) == ActionPolicyId.unwrap(id2);
}
function actionPolicyIdNeq(ActionPolicyId id1, ActionPolicyId id2) pure returns (bool) {
return ActionPolicyId.unwrap(id1) != ActionPolicyId.unwrap(id2);
}
// Erc1271PolicyId
using { erc1271PolicyIdEq as == } for Erc1271PolicyId global;
using { erc1271PolicyIdNeq as != } for Erc1271PolicyId global;
function erc1271PolicyIdEq(Erc1271PolicyId id1, Erc1271PolicyId id2) pure returns (bool) {
return Erc1271PolicyId.unwrap(id1) == Erc1271PolicyId.unwrap(id2);
}
function erc1271PolicyIdNeq(Erc1271PolicyId id1, Erc1271PolicyId id2) pure returns (bool) {
return Erc1271PolicyId.unwrap(id1) != Erc1271PolicyId.unwrap(id2);
}
// ConfigId
using { configIdEq as == } for ConfigId global;
using { configIdNeq as != } for ConfigId global;
function configIdEq(ConfigId id1, ConfigId id2) pure returns (bool) {
return ConfigId.unwrap(id1) == ConfigId.unwrap(id2);
}
function configIdNeq(ConfigId id1, ConfigId id2) pure returns (bool) {
return ConfigId.unwrap(id1) != ConfigId.unwrap(id2);
}// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;
import { PackedUserOperation, _packValidationData } from "modulekit/external/ERC4337.sol";
import { IModule as IERC7579Module, VALIDATION_SUCCESS, VALIDATION_FAILED } from "erc7579/interfaces/IERC7579Module.sol";
import "../DataTypes.sol";
import "forge-std/interfaces/IERC165.sol";
/**
* IPolicy are external contracts that enforce policies / permission on 4337/7579 executions
* Since it's not the account calling into this contract, and check functions are called during the ERC4337 validation
* phase, IPolicy implementations MUST follow ERC4337 storage and opcode restructions
* A recommend storage layout to store policy related data:
* mapping(id => msg.sender => userOp.sender(account) => state)
* ^ smartSession ^ smart account (associated storage)
*/
interface IPolicy is IERC165 {
event PolicySet(ConfigId id, address multiplexer, address account);
/**
* This function may be called by the multiplexer (SmartSessions) without deinitializing first.
* Policies MUST overwrite the current state when this happens
* @notice ATTENTION: This method is called during permission installation as part of the enabling policies flow.
* A secure policy would minimize external calls from this method (ideally, to 0) to prevent passing control flow to
* external contracts.
*/
function initializeWithMultiplexer(address account, ConfigId configId, bytes calldata initData) external;
}
/**
* IUserOpPolicy is a policy that enforces restrictions on user operations. It is called during the validation phase
* of the ERC4337 execution.
* Use this policy to enforce restrictions on user operations (userOp.gas, Time based restrictions).
* The checkUserOpPolicy function should return a uint256 value that represents the policy's decision.
* The policy's decision should be one of the following:
* - VALIDATION_SUCCESS: The user operation is allowed.
* - VALIDATION_FAILED: The user operation is not allowed.
* - While it is possible to return values that pack validUntil and validAfter timestamps,
* SmartSession Policies can not utilize aggregator addresses. (PolicyLib.isFailed() will prevent this)
*/
interface IUserOpPolicy is IPolicy {
function checkUserOpPolicy(ConfigId id, PackedUserOperation calldata userOp) external returns (uint256);
}
/**
* IActionPolicy is a policy that enforces restrictions on actions. It is called during the validation phase
* of the ERC4337 execution.
* ERC7579 accounts natively support batched executions. So in one userOp, multiple actions can be executed.
* SmartSession will destruct the execution batch, and call the policy for each action, if the policy is installed for
* the actionId for the account.
* Use this policy to enforce restrictions on individual actions (i.e. transfers, approvals, etc).
* The checkAction function should return a uint256 value that represents the policy's decision.
* The policy's decision should be one of the following:
* - VALIDATION_SUCCESS: The action is allowed.
* - VALIDATION_FAILED: The action is not allowed.
*/
interface IActionPolicy is IPolicy {
function checkAction(
ConfigId id,
address account,
address target,
uint256 value,
bytes calldata data
)
external
returns (uint256);
}
/**
* I1271Policy is a policy that enforces restrictions on 1271 signed actions. It is called during an ERC1271 signature
* validation
*/
interface I1271Policy is IPolicy {
// request sender is probably protocol, so can introduce policies based on it.
function check1271SignedAction(
ConfigId id,
address requestSender,
address account,
bytes32 hash,
bytes calldata signature
)
external
view
returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
type ModuleType is uint256;
interface IRegistry {
event NewTrustedAttesters(address indexed smartAccount);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Check with Registry internal attesters */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function check(address module) external view;
function checkForAccount(address smartAccount, address module) external view;
function check(address module, ModuleType moduleType) external view;
function checkForAccount(address smartAccount, address module, ModuleType moduleType) external view;
/**
* Allows Smart Accounts - the end users of the registry - to appoint
* one or many attesters as trusted.
* @dev this function reverts, if address(0), or duplicates are provided in attesters[]
*
* @param threshold The minimum number of attestations required for a module
* to be considered secure.
* @param attesters The addresses of the attesters to be trusted.
*/
function trustAttesters(uint8 threshold, address[] calldata attesters) external;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* Check with external attester(s) */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function check(address module, address[] calldata attesters, uint256 threshold) external view;
function check(
address module,
ModuleType moduleType,
address[] calldata attesters,
uint256 threshold
)
external
view;
}// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;
import { IModule } from "erc7579/interfaces/IERC7579Module.sol";
/**
* ISessionValidator is a contract that validates signatures for a given session.
* this interface expects to validate the signature in a stateless way.
* all parameters required to validate the signature are passed in the function call.
* Only one ISessionValidator is responsible to validate a userOp.
* if you want to use multiple validators, you can create a ISessionValidator that aggregates multiple signatures that
* are packed into userOp.signature
* It is used to validate the signature of a session.
* hash The userOp hash
* sig The signature of userOp
* data the config data that is used to validate the signature
*/
interface ISessionValidator is IModule {
function validateSignatureWithData(
bytes32 hash,
bytes calldata sig,
bytes calldata data
)
external
view
returns (bool validSig);
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.25;
import "../DataTypes.sol";
library SubModuleLib {
error DataTooShort(uint256 length);
function parseInstallData(bytes calldata data) internal pure returns (ConfigId, bytes calldata) {
if (data.length < 32) revert DataTooShort(data.length);
return (ConfigId.wrap(bytes32(data[0:32])), data[52:]);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* ERC-4337 / ERC-7562 Compatible array lib.
* This array can be used as mapping value in mappings such as (address account => Bytes32Array array)
* Array size should not exceed 128.
*/
library AssociatedArrayLib {
using AssociatedArrayLib for *;
error AssociatedArray_OutOfBounds(uint256 index);
struct Array {
uint256 _spacer;
}
function _slot(Array storage s, address account) private pure returns (bytes32 __slot) {
assembly {
mstore(0x00, account)
mstore(0x20, s.slot)
__slot := keccak256(0x00, 0x40)
}
}
function _length(Array storage s, address account) private view returns (uint256 __length) {
bytes32 slot = _slot(s, account);
assembly {
__length := sload(slot)
}
}
function _get(Array storage s, address account, uint256 index) private view returns (bytes32 value) {
return _get(_slot(s, account), index);
}
function _get(bytes32 slot, uint256 index) private view returns (bytes32 value) {
assembly {
//if (index >= _length(s, account)) revert AssociatedArray_OutOfBounds(index);
if iszero(lt(index, sload(slot))) {
mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)`
mstore(0x20, index)
revert(0x1c, 0x24)
}
value := sload(add(slot, add(index, 1)))
}
}
function _getAll(Array storage s, address account) private view returns (bytes32[] memory values) {
bytes32 slot = _slot(s, account);
uint256 __length;
assembly {
__length := sload(slot)
}
values = new bytes32[](__length);
for (uint256 i; i < __length; i++) {
values[i] = _get(slot, i);
}
}
// inefficient. complexity = O(n)
// use with caution
// in case of large arrays, consider using EnumerableSet4337 instead
function _contains(Array storage s, address account, bytes32 value) private view returns (bool) {
bytes32 slot = _slot(s, account);
uint256 __length;
assembly {
__length := sload(slot)
}
for (uint256 i; i < __length; i++) {
if (_get(slot, i) == value) {
return true;
}
}
return false;
}
function _set(Array storage s, address account, uint256 index, bytes32 value) private {
_set(_slot(s, account), index, value);
}
function _set(bytes32 slot, uint256 index, bytes32 value) private {
assembly {
//if (index >= _length(s, account)) revert AssociatedArray_OutOfBounds(index);
if iszero(lt(index, sload(slot))) {
mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)`
mstore(0x20, index)
revert(0x1c, 0x24)
}
sstore(add(slot, add(index, 1)), value)
}
}
function _push(Array storage s, address account, bytes32 value) private {
bytes32 slot = _slot(s, account);
assembly {
// load length (stored @ slot) => this would be the index of a new element
let index := sload(slot)
if gt(index, 127) {
mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)`
mstore(0x20, index)
revert(0x1c, 0x24)
}
sstore(add(slot, add(index, 1)), value) // store at (slot+index+1) => 0th element is stored at slot+1
sstore(slot, add(index, 1)) // increment length by 1
}
}
function _pop(Array storage s, address account) private {
bytes32 slot = _slot(s, account);
uint256 __length;
assembly {
__length := sload(slot)
}
if (__length == 0) return;
_set(slot, __length - 1, 0);
assembly {
sstore(slot, sub(__length, 1))
}
}
function _remove(Array storage s, address account, uint256 index) private {
bytes32 slot = _slot(s, account);
uint256 __length;
assembly {
__length := sload(slot)
if iszero(lt(index, __length)) {
mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)`
mstore(0x20, index)
revert(0x1c, 0x24)
}
}
_set(slot, index, _get(s, account, __length - 1));
assembly {
// clear the last slot
// this is the 'unchecked' version of _set(slot, __length - 1, 0)
// as we use length-1 as index, so the check is excessive.
// also removes extra -1 and +1 operations
sstore(add(slot, __length), 0)
// store new length
sstore(slot, sub(__length, 1))
}
}
struct Bytes32Array {
Array _inner;
}
function length(Bytes32Array storage s, address account) internal view returns (uint256) {
return _length(s._inner, account);
}
function get(Bytes32Array storage s, address account, uint256 index) internal view returns (bytes32) {
return _get(s._inner, account, index);
}
function getAll(Bytes32Array storage s, address account) internal view returns (bytes32[] memory) {
return _getAll(s._inner, account);
}
function contains(Bytes32Array storage s, address account, bytes32 value) internal view returns (bool) {
return _contains(s._inner, account, value);
}
function add(Bytes32Array storage s, address account, bytes32 value) internal {
if (!_contains(s._inner, account, value)) {
_push(s._inner, account, value);
}
}
function set(Bytes32Array storage s, address account, uint256 index, bytes32 value) internal {
_set(s._inner, account, index, value);
}
function push(Bytes32Array storage s, address account, bytes32 value) internal {
_push(s._inner, account, value);
}
function pop(Bytes32Array storage s, address account) internal {
_pop(s._inner, account);
}
function remove(Bytes32Array storage s, address account, uint256 index) internal {
_remove(s._inner, account, index);
}
struct AddressArray {
Array _inner;
}
function length(AddressArray storage s, address account) internal view returns (uint256) {
return _length(s._inner, account);
}
function get(AddressArray storage s, address account, uint256 index) internal view returns (address) {
return address(uint160(uint256(_get(s._inner, account, index))));
}
function getAll(AddressArray storage s, address account) internal view returns (address[] memory) {
bytes32[] memory bytes32Array = _getAll(s._inner, account);
address[] memory addressArray;
/// @solidity memory-safe-assembly
assembly {
addressArray := bytes32Array
}
return addressArray;
}
function contains(AddressArray storage s, address account, address value) internal view returns (bool) {
return _contains(s._inner, account, bytes32(uint256(uint160(value))));
}
function add(AddressArray storage s, address account, address value) internal {
if (!_contains(s._inner, account, bytes32(uint256(uint160(value))))) {
_push(s._inner, account, bytes32(uint256(uint160(value))));
}
}
function set(AddressArray storage s, address account, uint256 index, address value) internal {
_set(s._inner, account, index, bytes32(uint256(uint160(value))));
}
function push(AddressArray storage s, address account, address value) internal {
_push(s._inner, account, bytes32(uint256(uint160(value))));
}
function pop(AddressArray storage s, address account) internal {
_pop(s._inner, account);
}
function remove(AddressArray storage s, address account, uint256 index) internal {
_remove(s._inner, account, index);
}
struct UintArray {
Array _inner;
}
function length(UintArray storage s, address account) internal view returns (uint256) {
return _length(s._inner, account);
}
function get(UintArray storage s, address account, uint256 index) internal view returns (uint256) {
return uint256(_get(s._inner, account, index));
}
function getAll(UintArray storage s, address account) internal view returns (uint256[] memory) {
bytes32[] memory bytes32Array = _getAll(s._inner, account);
uint256[] memory uintArray;
/// @solidity memory-safe-assembly
assembly {
uintArray := bytes32Array
}
return uintArray;
}
function contains(UintArray storage s, address account, uint256 value) internal view returns (bool) {
return _contains(s._inner, account, bytes32(value));
}
function add(UintArray storage s, address account, uint256 value) internal {
if (!_contains(s._inner, account, bytes32(value))) {
_push(s._inner, account, bytes32(value));
}
}
function set(UintArray storage s, address account, uint256 index, uint256 value) internal {
_set(s._inner, account, index, bytes32(value));
}
function push(UintArray storage s, address account, uint256 value) internal {
_push(s._inner, account, bytes32(value));
}
function pop(UintArray storage s, address account) internal {
_pop(s._inner, account);
}
function remove(UintArray storage s, address account, uint256 index) internal {
_remove(s._inner, account, index);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.
pragma solidity ^0.8.20;
import { EnumerableSet } from "./EnumerableSet4337.sol";
/**
* Fork of OZ's EnumerableSet that makes all storage access ERC-4337 compliant via associated storage
* @author zeroknots.eth (rhinestone)
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => mapping(address account => bytes32)) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(
Bytes32ToBytes32Map storage map,
address account,
bytes32 key,
bytes32 value
)
internal
returns (bool)
{
map._values[key][account] = value;
return map._keys.add(account, key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, address account, bytes32 key) internal returns (bool) {
delete map._values[key][account];
return map._keys.remove(account, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, address account, bytes32 key) internal view returns (bool) {
return map._keys.contains(account, key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map, address account) internal view returns (uint256) {
return map._keys.length(account);
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(
Bytes32ToBytes32Map storage map,
address account,
uint256 index
)
internal
view
returns (bytes32, bytes32)
{
bytes32 key = map._keys.at(account, index);
return (key, map._values[key][account]);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(
Bytes32ToBytes32Map storage map,
address account,
bytes32 key
)
internal
view
returns (bool, bytes32)
{
bytes32 value = map._values[key][account];
if (value == bytes32(0)) {
return (contains(map, account, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, address account, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key][account];
if (value == 0 && !contains(map, account, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map, address account) internal view returns (bytes32[] memory) {
return map._keys.values(account);
}
// UintToUintMap
struct UintToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToUintMap storage map, address account, uint256 key, uint256 value) internal returns (bool) {
return set(map._inner, account, bytes32(key), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToUintMap storage map, address account, uint256 key) internal returns (bool) {
return remove(map._inner, account, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToUintMap storage map, address account, uint256 key) internal view returns (bool) {
return contains(map._inner, account, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToUintMap storage map, address account) internal view returns (uint256) {
return length(map._inner, account);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToUintMap storage map, address account, uint256 index) internal view returns (uint256, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, account, index);
return (uint256(key), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToUintMap storage map, address account, uint256 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, account, bytes32(key));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToUintMap storage map, address account, uint256 key) internal view returns (uint256) {
return uint256(get(map._inner, account, bytes32(key)));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToUintMap storage map, address account) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner, account);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToAddressMap
struct UintToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, address account, uint256 key, address value) internal returns (bool) {
return set(map._inner, account, bytes32(key), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, address account, uint256 key) internal returns (bool) {
return remove(map._inner, account, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, address account, uint256 key) internal view returns (bool) {
return contains(map._inner, account, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map, address account) internal view returns (uint256) {
return length(map._inner, account);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(
UintToAddressMap storage map,
address account,
uint256 index
)
internal
view
returns (uint256, address)
{
(bytes32 key, bytes32 value) = at(map._inner, account, index);
return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, address account, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, account, bytes32(key));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, address account, uint256 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, account, bytes32(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToAddressMap storage map, address account) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner, account);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToUintMap
struct AddressToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToUintMap storage map, address account, address key, uint256 value) internal returns (bool) {
return set(map._inner, account, bytes32(uint256(uint160(key))), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToUintMap storage map, address account, address key) internal returns (bool) {
return remove(map._inner, account, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToUintMap storage map, address account, address key) internal view returns (bool) {
return contains(map._inner, account, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToUintMap storage map, address account) internal view returns (uint256) {
return length(map._inner, account);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(
AddressToUintMap storage map,
address account,
uint256 index
)
internal
view
returns (address, uint256)
{
(bytes32 key, bytes32 value) = at(map._inner, account, index);
return (address(uint160(uint256(key))), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToUintMap storage map, address account, address key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, account, bytes32(uint256(uint160(key))));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToUintMap storage map, address account, address key) internal view returns (uint256) {
return uint256(get(map._inner, account, bytes32(uint256(uint160(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToUintMap storage map, address account) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner, account);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToUintMap
struct Bytes32ToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToUintMap storage map, address account, bytes32 key, uint256 value) internal returns (bool) {
return set(map._inner, account, key, bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToUintMap storage map, address account, bytes32 key) internal returns (bool) {
return remove(map._inner, account, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, address account, bytes32 key) internal view returns (bool) {
return contains(map._inner, account, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap storage map, address account) internal view returns (uint256) {
return length(map._inner, account);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(
Bytes32ToUintMap storage map,
address account,
uint256 index
)
internal
view
returns (bytes32, uint256)
{
(bytes32 key, bytes32 value) = at(map._inner, account, index);
return (key, uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToUintMap storage map, address account, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, account, key);
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, address account, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, account, key));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToUintMap storage map, address account) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner, account);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./AssociatedArrayLib.sol";
/**
* Fork of OZ's EnumerableSet that makes all storage access ERC-4337 compliant via associated storage
* @author zeroknots.eth (rhinestone)
*/
library EnumerableSet {
using AssociatedArrayLib for AssociatedArrayLib.Bytes32Array;
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
AssociatedArrayLib.Bytes32Array _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => mapping(address account => uint256)) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, address account, bytes32 value) private returns (bool) {
if (!_contains(set, account, value)) {
set._values.push(account, value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value][account] = set._values.length(account);
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, address account, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value][account];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length(account) - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values.get(account, lastIndex);
// Move the lastValue to the index where the value to delete is
set._values.set(account, valueIndex, lastValue);
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue][account] = position;
}
// Delete the slot where the moved value was stored
set._values.pop(account);
// Delete the tracked position for the deleted slot
delete set._positions[value][account];
return true;
} else {
return false;
}
}
function _removeAll(Set storage set, address account) internal {
// get length of the array
uint256 len = _length(set, account);
for (uint256 i = 1; i <= len; i++) {
// get last value
bytes32 value = _at(set, account, len - i);
_remove(set, account, value);
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, address account, bytes32 value) private view returns (bool) {
return set._positions[value][account] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set, address account) private view returns (uint256) {
return set._values.length(account);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, address account, uint256 index) private view returns (bytes32) {
return set._values.get(account, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set, address account) private view returns (bytes32[] memory) {
return set._values.getAll(account);
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, address account, bytes32 value) internal returns (bool) {
return _add(set._inner, account, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, address account, bytes32 value) internal returns (bool) {
return _remove(set._inner, account, value);
}
function removeAll(Bytes32Set storage set, address account) internal {
return _removeAll(set._inner, account);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, address account, bytes32 value) internal view returns (bool) {
return _contains(set._inner, account, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set, address account) internal view returns (uint256) {
return _length(set._inner, account);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, address account, uint256 index) internal view returns (bytes32) {
return _at(set._inner, account, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set, address account) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner, account);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address account, address value) internal returns (bool) {
return _add(set._inner, account, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address account, address value) internal returns (bool) {
return _remove(set._inner, account, bytes32(uint256(uint160(value))));
}
function removeAll(AddressSet storage set, address account) internal {
return _removeAll(set._inner, account);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address account, address value) internal view returns (bool) {
return _contains(set._inner, account, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set, address account) internal view returns (uint256) {
return _length(set._inner, account);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, address account, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, account, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set, address account) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner, account);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, address account, uint256 value) internal returns (bool) {
return _add(set._inner, account, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, address account, uint256 value) internal returns (bool) {
return _remove(set._inner, account, bytes32(value));
}
function removeAll(UintSet storage set, address account) internal {
return _removeAll(set._inner, account);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, address account, uint256 value) internal view returns (bool) {
return _contains(set._inner, account, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set, address account) internal view returns (uint256) {
return _length(set._inner, account);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, address account, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, account, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set, address account) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner, account);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
import "../interfaces/IAccount.sol";
import "../interfaces/IAccountExecute.sol";
import "../interfaces/IPaymaster.sol";
import "../interfaces/IEntryPoint.sol";
import "../utils/Exec.sol";
import "./StakeManager.sol";
import "./SenderCreator.sol";
import "./Helpers.sol";
import "./NonceManager.sol";
import "./UserOperationLib.sol";
import "./GasDebug.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/*
* Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
* Only one instance required on each chain.
*/
/// @custom:security-contact https://bounty.ethereum.org
contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ERC165, GasDebug {
using UserOperationLib for PackedUserOperation;
SenderCreator private immutable _senderCreator = new SenderCreator();
function senderCreator() internal view virtual returns (SenderCreator) {
return _senderCreator;
}
//compensate for innerHandleOps' emit message and deposit refund.
// allow some slack for future gas price changes.
uint256 private constant INNER_GAS_OVERHEAD = 10000;
// Marker for inner call revert on out of gas
bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead";
bytes32 private constant INNER_REVERT_LOW_PREFUND = hex"deadaa51";
uint256 private constant REVERT_REASON_MAX_LEN = 2048;
uint256 private constant PENALTY_PERCENT = 10;
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
// note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything
return interfaceId
== (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId)
|| interfaceId == type(IEntryPoint).interfaceId || interfaceId == type(IStakeManager).interfaceId
|| interfaceId == type(INonceManager).interfaceId || super.supportsInterface(interfaceId);
}
/**
* Compensate the caller's beneficiary address with the collected fees of all UserOperations.
* @param beneficiary - The address to receive the fees.
* @param amount - Amount to transfer.
*/
function _compensate(address payable beneficiary, uint256 amount) internal {
require(beneficiary != address(0), "AA90 invalid beneficiary");
(bool success,) = beneficiary.call{value: amount}("");
require(success, "AA91 failed send to beneficiary");
}
/**
* Execute a user operation.
* @param opIndex - Index into the opInfo array.
* @param userOp - The userOp to execute.
* @param opInfo - The opInfo filled by validatePrepayment for this userOp.
* @return collected - The total amount this userOp paid.
*/
function _executeUserOp(uint256 opIndex, PackedUserOperation calldata userOp, UserOpInfo memory opInfo)
internal
returns (uint256 collected)
{
uint256 preGas = gasleft();
bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset);
bool success;
{
uint256 saveFreePtr;
assembly ("memory-safe") {
saveFreePtr := mload(0x40)
}
bytes calldata callData = userOp.callData;
bytes memory innerCall;
bytes4 methodSig;
assembly {
let len := callData.length
if gt(len, 3) { methodSig := calldataload(callData.offset) }
}
if (methodSig == IAccountExecute.executeUserOp.selector) {
bytes memory executeUserOp = abi.encodeCall(IAccountExecute.executeUserOp, (userOp, opInfo.userOpHash));
innerCall = abi.encodeCall(this.innerHandleOp, (executeUserOp, opInfo, context));
} else {
innerCall = abi.encodeCall(this.innerHandleOp, (callData, opInfo, context));
}
assembly ("memory-safe") {
success := call(gas(), address(), 0, add(innerCall, 0x20), mload(innerCall), 0, 32)
collected := mload(0)
mstore(0x40, saveFreePtr)
}
}
if (!success) {
bytes32 innerRevertCode;
assembly ("memory-safe") {
let len := returndatasize()
if eq(32, len) {
returndatacopy(0, 0, 32)
innerRevertCode := mload(0)
}
}
if (innerRevertCode == INNER_OUT_OF_GAS) {
// handleOps was called with gas limit too low. abort entire bundle.
//can only be caused by bundler (leaving not enough gas for inner call)
revert FailedOp(opIndex, "AA95 out of gas");
} else if (innerRevertCode == INNER_REVERT_LOW_PREFUND) {
// innerCall reverted on prefund too low. treat entire prefund as "gas cost"
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
uint256 actualGasCost = opInfo.prefund;
emitPrefundTooLow(opInfo);
emitUserOperationEvent(opInfo, false, actualGasCost, actualGas);
collected = actualGasCost;
} else {
emit PostOpRevertReason(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.nonce,
Exec.getReturnData(REVERT_REASON_MAX_LEN)
);
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
collected = _postExecution(IPaymaster.PostOpMode.postOpReverted, opInfo, context, actualGas);
}
}
}
function emitUserOperationEvent(UserOpInfo memory opInfo, bool success, uint256 actualGasCost, uint256 actualGas)
internal
virtual
{
emit UserOperationEvent(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.paymaster,
opInfo.mUserOp.nonce,
success,
actualGasCost,
actualGas
);
}
function emitPrefundTooLow(UserOpInfo memory opInfo) internal virtual {
emit UserOperationPrefundTooLow(opInfo.userOpHash, opInfo.mUserOp.sender, opInfo.mUserOp.nonce);
}
/// @inheritdoc IEntryPoint
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) public nonReentrant {
uint256 opslen = ops.length;
UserOpInfo[] memory opInfos = new UserOpInfo[](opslen);
unchecked {
for (uint256 i = 0; i < opslen; i++) {
UserOpInfo memory opInfo = opInfos[i];
(uint256 validationData, uint256 pmValidationData) = _validatePrepayment(i, ops[i], opInfo);
_validateAccountAndPaymasterValidationData(i, validationData, pmValidationData, address(0));
}
uint256 collected = 0;
emit BeforeExecution();
for (uint256 i = 0; i < opslen; i++) {
collected += _executeUserOp(i, ops[i], opInfos[i]);
}
_compensate(beneficiary, collected);
}
}
/// @inheritdoc IEntryPoint
function handleAggregatedOps(UserOpsPerAggregator[] calldata opsPerAggregator, address payable beneficiary)
public
nonReentrant
{
uint256 opasLen = opsPerAggregator.length;
uint256 totalOps = 0;
for (uint256 i = 0; i < opasLen; i++) {
UserOpsPerAggregator calldata opa = opsPerAggregator[i];
PackedUserOperation[] calldata ops = opa.userOps;
IAggregator aggregator = opa.aggregator;
//address(1) is special marker of "signature error"
require(address(aggregator) != address(1), "AA96 invalid aggregator");
if (address(aggregator) != address(0)) {
// solhint-disable-next-line no-empty-blocks
try aggregator.validateSignatures(ops, opa.signature) {}
catch {
revert SignatureValidationFailed(address(aggregator));
}
}
totalOps += ops.length;
}
UserOpInfo[] memory opInfos = new UserOpInfo[](totalOps);
uint256 opIndex = 0;
for (uint256 a = 0; a < opasLen; a++) {
UserOpsPerAggregator calldata opa = opsPerAggregator[a];
PackedUserOperation[] calldata ops = opa.userOps;
IAggregator aggregator = opa.aggregator;
uint256 opslen = ops.length;
for (uint256 i = 0; i < opslen; i++) {
UserOpInfo memory opInfo = opInfos[opIndex];
(uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(opIndex, ops[i], opInfo);
_validateAccountAndPaymasterValidationData(
i, validationData, paymasterValidationData, address(aggregator)
);
opIndex++;
}
}
emit BeforeExecution();
uint256 collected = 0;
opIndex = 0;
for (uint256 a = 0; a < opasLen; a++) {
UserOpsPerAggregator calldata opa = opsPerAggregator[a];
emit SignatureAggregatorChanged(address(opa.aggregator));
PackedUserOperation[] calldata ops = opa.userOps;
uint256 opslen = ops.length;
for (uint256 i = 0; i < opslen; i++) {
collected += _executeUserOp(opIndex, ops[i], opInfos[opIndex]);
opIndex++;
}
}
emit SignatureAggregatorChanged(address(0));
_compensate(beneficiary, collected);
}
/**
* A memory copy of UserOp static fields only.
* Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster.
*/
struct MemoryUserOp {
address sender;
uint256 nonce;
uint256 verificationGasLimit;
uint256 callGasLimit;
uint256 paymasterVerificationGasLimit;
uint256 paymasterPostOpGasLimit;
uint256 preVerificationGas;
address paymaster;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
}
struct UserOpInfo {
MemoryUserOp mUserOp;
bytes32 userOpHash;
uint256 prefund;
uint256 contextOffset;
uint256 preOpGas;
}
/**
* Inner function to handle a UserOperation.
* Must be declared "external" to open a call context, but it can only be called by handleOps.
* @param callData - The callData to execute.
* @param opInfo - The UserOpInfo struct.
* @param context - The context bytes.
* @return actualGasCost - the actual cost in eth this UserOperation paid for gas
*/
function innerHandleOp(bytes memory callData, UserOpInfo memory opInfo, bytes calldata context)
external
returns (uint256 actualGasCost)
{
uint256 preGas = gasleft();
require(msg.sender == address(this), "AA92 internal call only");
MemoryUserOp memory mUserOp = opInfo.mUserOp;
uint256 callGasLimit = mUserOp.callGasLimit;
unchecked {
// handleOps was called with gas limit too low. abort entire bundle.
if (gasleft() * 63 / 64 < callGasLimit + mUserOp.paymasterPostOpGasLimit + INNER_GAS_OVERHEAD) {
assembly ("memory-safe") {
mstore(0, INNER_OUT_OF_GAS)
revert(0, 32)
}
}
}
IPaymaster.PostOpMode mode = IPaymaster.PostOpMode.opSucceeded;
if (callData.length > 0) {
uint256 _execGas = gasleft();
bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit);
setGasConsumed(mUserOp.sender, 2, _execGas - gasleft());
if (!success) {
bytes memory result = Exec.getReturnData(REVERT_REASON_MAX_LEN);
if (result.length > 0) {
emit UserOperationRevertReason(opInfo.userOpHash, mUserOp.sender, mUserOp.nonce, result);
}
mode = IPaymaster.PostOpMode.opReverted;
}
}
unchecked {
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
return _postExecution(mode, opInfo, context, actualGas);
}
}
/// @inheritdoc IEntryPoint
function getUserOpHash(PackedUserOperation calldata userOp) public view returns (bytes32) {
return keccak256(abi.encode(userOp.hash(), address(this), block.chainid));
}
/**
* Copy general fields from userOp into the memory opInfo structure.
* @param userOp - The user operation.
* @param mUserOp - The memory user operation.
*/
function _copyUserOpToMemory(PackedUserOperation calldata userOp, MemoryUserOp memory mUserOp) internal pure {
mUserOp.sender = userOp.sender;
mUserOp.nonce = userOp.nonce;
(mUserOp.verificationGasLimit, mUserOp.callGasLimit) = UserOperationLib.unpackUints(userOp.accountGasLimits);
mUserOp.preVerificationGas = userOp.preVerificationGas;
(mUserOp.maxPriorityFeePerGas, mUserOp.maxFeePerGas) = UserOperationLib.unpackUints(userOp.gasFees);
bytes calldata paymasterAndData = userOp.paymasterAndData;
if (paymasterAndData.length > 0) {
require(paymasterAndData.length >= UserOperationLib.PAYMASTER_DATA_OFFSET, "AA93 invalid paymasterAndData");
(mUserOp.paymaster, mUserOp.paymasterVerificationGasLimit, mUserOp.paymasterPostOpGasLimit) =
UserOperationLib.unpackPaymasterStaticFields(paymasterAndData);
} else {
mUserOp.paymaster = address(0);
mUserOp.paymasterVerificationGasLimit = 0;
mUserOp.paymasterPostOpGasLimit = 0;
}
}
/**
* Get the required prefunded gas fee amount for an operation.
* @param mUserOp - The user operation in memory.
*/
function _getRequiredPrefund(MemoryUserOp memory mUserOp) internal pure returns (uint256 requiredPrefund) {
unchecked {
uint256 requiredGas = mUserOp.verificationGasLimit + mUserOp.callGasLimit
+ mUserOp.paymasterVerificationGasLimit + mUserOp.paymasterPostOpGasLimit + mUserOp.preVerificationGas;
requiredPrefund = requiredGas * mUserOp.maxFeePerGas;
}
}
/**
* Create sender smart contract account if init code is provided.
* @param opIndex - The operation index.
* @param opInfo - The operation info.
* @param initCode - The init code for the smart contract account.
*/
function _createSenderIfNeeded(uint256 opIndex, UserOpInfo memory opInfo, bytes calldata initCode) internal {
if (initCode.length != 0) {
address sender = opInfo.mUserOp.sender;
if (sender.code.length != 0) {
revert FailedOp(opIndex, "AA10 sender already constructed");
}
uint256 _creationGas = gasleft();
address sender1 = senderCreator().createSender{gas: opInfo.mUserOp.verificationGasLimit}(initCode);
setGasConsumed(sender, 0, _creationGas - gasleft());
if (sender1 == address(0)) {
revert FailedOp(opIndex, "AA13 initCode failed or OOG");
}
if (sender1 != sender) {
revert FailedOp(opIndex, "AA14 initCode must return sender");
}
if (sender1.code.length == 0) {
revert FailedOp(opIndex, "AA15 initCode must create sender");
}
address factory = address(bytes20(initCode[0:20]));
emit AccountDeployed(opInfo.userOpHash, sender, factory, opInfo.mUserOp.paymaster);
}
}
/// @inheritdoc IEntryPoint
function getSenderAddress(bytes calldata initCode) public {
address sender = senderCreator().createSender(initCode);
revert SenderAddressResult(sender);
}
/**
* Call account.validateUserOp.
* Revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund.
* Decrement account's deposit if needed.
* @param opIndex - The operation index.
* @param op - The user operation.
* @param opInfo - The operation info.
* @param requiredPrefund - The required prefund amount.
*/
function _validateAccountPrepayment(
uint256 opIndex,
PackedUserOperation calldata op,
UserOpInfo memory opInfo,
uint256 requiredPrefund,
uint256 verificationGasLimit
) internal returns (uint256 validationData) {
unchecked {
MemoryUserOp memory mUserOp = opInfo.mUserOp;
address sender = mUserOp.sender;
_createSenderIfNeeded(opIndex, opInfo, op.initCode);
address paymaster = mUserOp.paymaster;
uint256 missingAccountFunds = 0;
if (paymaster == address(0)) {
uint256 bal = balanceOf(sender);
missingAccountFunds = bal > requiredPrefund ? 0 : requiredPrefund - bal;
}
uint256 _verificationGas = gasleft();
try IAccount(sender).validateUserOp{gas: verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds)
returns (uint256 _validationData) {
validationData = _validationData;
setGasConsumed(sender, 1, _verificationGas - gasleft());
} catch {
revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN));
}
if (paymaster == address(0)) {
DepositInfo storage senderInfo = deposits[sender];
uint256 deposit = senderInfo.deposit;
if (requiredPrefund > deposit) {
revert FailedOp(opIndex, "AA21 didn't pay prefund");
}
senderInfo.deposit = deposit - requiredPrefund;
}
}
}
/**
* In case the request has a paymaster:
* - Validate paymaster has enough deposit.
* - Call paymaster.validatePaymasterUserOp.
* - Revert with proper FailedOp in case paymaster reverts.
* - Decrement paymaster's deposit.
* @param opIndex - The operation index.
* @param op - The user operation.
* @param opInfo - The operation info.
* @param requiredPreFund - The required prefund amount.
*/
function _validatePaymasterPrepayment(
uint256 opIndex,
PackedUserOperation calldata op,
UserOpInfo memory opInfo,
uint256 requiredPreFund
) internal returns (bytes memory context, uint256 validationData) {
unchecked {
uint256 preGas = gasleft();
MemoryUserOp memory mUserOp = opInfo.mUserOp;
address paymaster = mUserOp.paymaster;
DepositInfo storage paymasterInfo = deposits[paymaster];
uint256 deposit = paymasterInfo.deposit;
if (deposit < requiredPreFund) {
revert FailedOp(opIndex, "AA31 paymaster deposit too low");
}
paymasterInfo.deposit = deposit - requiredPreFund;
uint256 pmVerificationGasLimit = mUserOp.paymasterVerificationGasLimit;
try IPaymaster(paymaster).validatePaymasterUserOp{gas: pmVerificationGasLimit}(
op, opInfo.userOpHash, requiredPreFund
) returns (bytes memory _context, uint256 _validationData) {
context = _context;
validationData = _validationData;
} catch {
revert FailedOpWithRevert(opIndex, "AA33 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN));
}
if (preGas - gasleft() > pmVerificationGasLimit) {
revert FailedOp(opIndex, "AA36 over paymasterVerificationGasLimit");
}
}
}
/**
* Revert if either account validationData or paymaster validationData is expired.
* @param opIndex - The operation index.
* @param validationData - The account validationData.
* @param paymasterValidationData - The paymaster validationData.
* @param expectedAggregator - The expected aggregator.
*/
function _validateAccountAndPaymasterValidationData(
uint256 opIndex,
uint256 validationData,
uint256 paymasterValidationData,
address expectedAggregator
) internal view {
(address aggregator, bool outOfTimeRange) = _getValidationData(validationData);
if (expectedAggregator != aggregator) {
revert FailedOp(opIndex, "AA24 signature error");
}
if (outOfTimeRange) {
revert FailedOp(opIndex, "AA22 expired or not due");
}
// pmAggregator is not a real signature aggregator: we don't have logic to handle it as address.
// Non-zero address means that the paymaster fails due to some signature check (which is ok only during estimation).
address pmAggregator;
(pmAggregator, outOfTimeRange) = _getValidationData(paymasterValidationData);
if (pmAggregator != address(0)) {
revert FailedOp(opIndex, "AA34 signature error");
}
if (outOfTimeRange) {
revert FailedOp(opIndex, "AA32 paymaster expired or not due");
}
}
/**
* Parse validationData into its components.
* @param validationData - The packed validation data (sigFailed, validAfter, validUntil).
* @return aggregator the aggregator of the validationData
* @return outOfTimeRange true if current time is outside the time range of this validationData.
*/
function _getValidationData(uint256 validationData)
internal
view
returns (address aggregator, bool outOfTimeRange)
{
if (validationData == 0) {
return (address(0), false);
}
ValidationData memory data = _parseValidationData(validationData);
// solhint-disable-next-line not-rely-on-time
outOfTimeRange = block.timestamp > data.validUntil || block.timestamp < data.validAfter;
aggregator = data.aggregator;
}
/**
* Validate account and paymaster (if defined) and
* also make sure total validation doesn't exceed verificationGasLimit.
* This method is called off-chain (simulateValidation()) and on-chain (from handleOps)
* @param opIndex - The index of this userOp into the "opInfos" array.
* @param userOp - The userOp to validate.
*/
function _validatePrepayment(uint256 opIndex, PackedUserOperation calldata userOp, UserOpInfo memory outOpInfo)
internal
returns (uint256 validationData, uint256 paymasterValidationData)
{
uint256 preGas = gasleft();
MemoryUserOp memory mUserOp = outOpInfo.mUserOp;
_copyUserOpToMemory(userOp, mUserOp);
outOpInfo.userOpHash = getUserOpHash(userOp);
// Validate all numeric values in userOp are well below 128 bit, so they can safely be added
// and multiplied without causing overflow.
uint256 verificationGasLimit = mUserOp.verificationGasLimit;
uint256 maxGasValues = mUserOp.preVerificationGas | verificationGasLimit | mUserOp.callGasLimit
| mUserOp.paymasterVerificationGasLimit | mUserOp.paymasterPostOpGasLimit | mUserOp.maxFeePerGas
| mUserOp.maxPriorityFeePerGas;
require(maxGasValues <= type(uint120).max, "AA94 gas values overflow");
uint256 requiredPreFund = _getRequiredPrefund(mUserOp);
validationData = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, verificationGasLimit);
if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) {
revert FailedOp(opIndex, "AA25 invalid account nonce");
}
unchecked {
if (preGas - gasleft() > verificationGasLimit) {
revert FailedOp(opIndex, "AA26 over verificationGasLimit");
}
}
bytes memory context;
if (mUserOp.paymaster != address(0)) {
(context, paymasterValidationData) =
_validatePaymasterPrepayment(opIndex, userOp, outOpInfo, requiredPreFund);
}
unchecked {
outOpInfo.prefund = requiredPreFund;
outOpInfo.contextOffset = getOffsetOfMemoryBytes(context);
outOpInfo.preOpGas = preGas - gasleft() + userOp.preVerificationGas;
}
}
/**
* Process post-operation, called just after the callData is executed.
* If a paymaster is defined and its validation returned a non-empty context, its postOp is called.
* The excess amount is refunded to the account (or paymaster - if it was used in the request).
* @param mode - Whether is called from innerHandleOp, or outside (postOpReverted).
* @param opInfo - UserOp fields and info collected during validation.
* @param context - The context returned in validatePaymasterUserOp.
* @param actualGas - The gas used so far by this user operation.
*/
function _postExecution(
IPaymaster.PostOpMode mode,
UserOpInfo memory opInfo,
bytes memory context,
uint256 actualGas
) private returns (uint256 actualGasCost) {
uint256 preGas = gasleft();
unchecked {
address refundAddress;
MemoryUserOp memory mUserOp = opInfo.mUserOp;
uint256 gasPrice = getUserOpGasPrice(mUserOp);
address paymaster = mUserOp.paymaster;
if (paymaster == address(0)) {
refundAddress = mUserOp.sender;
} else {
refundAddress = paymaster;
if (context.length > 0) {
actualGasCost = actualGas * gasPrice;
if (mode != IPaymaster.PostOpMode.postOpReverted) {
try IPaymaster(paymaster).postOp{gas: mUserOp.paymasterPostOpGasLimit}(
mode, context, actualGasCost, gasPrice
) {
// solhint-disable-next-line no-empty-blocks
} catch {
bytes memory reason = Exec.getReturnData(REVERT_REASON_MAX_LEN);
revert PostOpReverted(reason);
}
}
}
}
actualGas += preGas - gasleft();
// Calculating a penalty for unused execution gas
{
uint256 executionGasLimit = mUserOp.callGasLimit + mUserOp.paymasterPostOpGasLimit;
uint256 executionGasUsed = actualGas - opInfo.preOpGas;
// this check is required for the gas used within EntryPoint and not covered by explicit gas limits
if (executionGasLimit > executionGasUsed) {
uint256 unusedGas = executionGasLimit - executionGasUsed;
uint256 unusedGasPenalty = (unusedGas * PENALTY_PERCENT) / 100;
actualGas += unusedGasPenalty;
}
}
actualGasCost = actualGas * gasPrice;
uint256 prefund = opInfo.prefund;
if (prefund < actualGasCost) {
if (mode == IPaymaster.PostOpMode.postOpReverted) {
actualGasCost = prefund;
emitPrefundTooLow(opInfo);
emitUserOperationEvent(opInfo, false, actualGasCost, actualGas);
} else {
assembly ("memory-safe") {
mstore(0, INNER_REVERT_LOW_PREFUND)
revert(0, 32)
}
}
} else {
uint256 refund = prefund - actualGasCost;
_incrementDeposit(refundAddress, refund);
bool success = mode == IPaymaster.PostOpMode.opSucceeded;
emitUserOperationEvent(opInfo, success, actualGasCost, actualGas);
}
} // unchecked
}
/**
* The gas price this UserOp agrees to pay.
* Relayer/block builder might submit the TX with higher priorityFee, but the user should not.
* @param mUserOp - The userOp to get the gas price from.
*/
function getUserOpGasPrice(MemoryUserOp memory mUserOp) internal view returns (uint256) {
unchecked {
uint256 maxFeePerGas = mUserOp.maxFeePerGas;
uint256 maxPriorityFeePerGas = mUserOp.maxPriorityFeePerGas;
if (maxFeePerGas == maxPriorityFeePerGas) {
//legacy mode (for networks that don't support basefee opcode)
return maxFeePerGas;
}
return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
}
}
/**
* The offset of the given bytes in memory.
* @param data - The bytes to get the offset of.
*/
function getOffsetOfMemoryBytes(bytes memory data) internal pure returns (uint256 offset) {
assembly {
offset := data
}
}
/**
* The bytes in memory at the given offset.
* @param offset - The offset to get the bytes from.
*/
function getMemoryBytesFromOffset(uint256 offset) internal pure returns (bytes memory data) {
assembly ("memory-safe") {
data := offset
}
}
/// @inheritdoc IEntryPoint
function delegateAndRevert(address target, bytes calldata data) external {
(bool success, bytes memory ret) = target.delegatecall(data);
revert DelegateAndRevert(success, ret);
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
import "./EntryPoint.sol";
import "../interfaces/IEntryPointSimulations.sol";
/*
* This contract inherits the EntryPoint and extends it with the view-only methods that are executed by
* the bundler in order to check UserOperation validity and estimate its gas consumption.
* This contract should never be deployed on-chain and is only used as a parameter for the "eth_call" request.
*/
contract EntryPointSimulations is EntryPoint, IEntryPointSimulations {
// solhint-disable-next-line var-name-mixedcase
AggregatorStakeInfo private NOT_AGGREGATED = AggregatorStakeInfo(address(0), StakeInfo(0, 0));
SenderCreator private _senderCreator;
function initSenderCreator() internal virtual {
//this is the address of the first contract created with CREATE by this address.
address createdObj = address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", address(this), hex"01")))));
_senderCreator = SenderCreator(createdObj);
}
function senderCreator() internal view virtual override returns (SenderCreator) {
// return the same senderCreator as real EntryPoint.
// this call is slightly (100) more expensive than EntryPoint's access to immutable member
return _senderCreator;
}
/**
* simulation contract should not be deployed, and specifically, accounts should not trust
* it as entrypoint, since the simulation functions don't check the signatures
*/
constructor() {
// THIS CONTRACT SHOULD NOT BE DEPLOYED
// however, the line of code below is commented to allow this entryPoint to be used in fork tests
// require(block.number < 100, "should not be deployed");
}
/// @inheritdoc IEntryPointSimulations
function simulateValidation(
PackedUserOperation calldata userOp
)
external
returns (
ValidationResult memory
){
UserOpInfo memory outOpInfo;
_simulationOnlyValidations(userOp);
(
uint256 validationData,
uint256 paymasterValidationData
) = _validatePrepayment(0, userOp, outOpInfo);
StakeInfo memory paymasterInfo = _getStakeInfo(
outOpInfo.mUserOp.paymaster
);
StakeInfo memory senderInfo = _getStakeInfo(outOpInfo.mUserOp.sender);
StakeInfo memory factoryInfo;
{
bytes calldata initCode = userOp.initCode;
address factory = initCode.length >= 20
? address(bytes20(initCode[0 : 20]))
: address(0);
factoryInfo = _getStakeInfo(factory);
}
address aggregator = address(uint160(validationData));
ReturnInfo memory returnInfo = ReturnInfo(
outOpInfo.preOpGas,
outOpInfo.prefund,
validationData,
paymasterValidationData,
getMemoryBytesFromOffset(outOpInfo.contextOffset)
);
AggregatorStakeInfo memory aggregatorInfo = NOT_AGGREGATED;
if (uint160(aggregator) != SIG_VALIDATION_SUCCESS && uint160(aggregator) != SIG_VALIDATION_FAILED) {
aggregatorInfo = AggregatorStakeInfo(
aggregator,
_getStakeInfo(aggregator)
);
}
return ValidationResult(
returnInfo,
senderInfo,
factoryInfo,
paymasterInfo,
aggregatorInfo
);
}
/// @inheritdoc IEntryPointSimulations
function simulateHandleOp(
PackedUserOperation calldata op,
address target,
bytes calldata targetCallData
)
external nonReentrant
returns (
ExecutionResult memory
){
UserOpInfo memory opInfo;
_simulationOnlyValidations(op);
(
uint256 validationData,
uint256 paymasterValidationData
) = _validatePrepayment(0, op, opInfo);
uint256 paid = _executeUserOp(0, op, opInfo);
bool targetSuccess;
bytes memory targetResult;
if (target != address(0)) {
(targetSuccess, targetResult) = target.call(targetCallData);
}
return ExecutionResult(
opInfo.preOpGas,
paid,
validationData,
paymasterValidationData,
targetSuccess,
targetResult
);
}
function _simulationOnlyValidations(
PackedUserOperation calldata userOp
)
internal
{
//initialize senderCreator(). we can't rely on constructor
initSenderCreator();
try
this._validateSenderAndPaymaster(
userOp.initCode,
userOp.sender,
userOp.paymasterAndData
)
// solhint-disable-next-line no-empty-blocks
{} catch Error(string memory revertReason) {
if (bytes(revertReason).length != 0) {
revert FailedOp(0, revertReason);
}
}
}
/**
* Called only during simulation.
* This function always reverts to prevent warm/cold storage differentiation in simulation vs execution.
* @param initCode - The smart account constructor code.
* @param sender - The sender address.
* @param paymasterAndData - The paymaster address (followed by other params, ignored by this method)
*/
function _validateSenderAndPaymaster(
bytes calldata initCode,
address sender,
bytes calldata paymasterAndData
) external view {
if (initCode.length == 0 && sender.code.length == 0) {
// it would revert anyway. but give a meaningful message
revert("AA20 account not deployed");
}
if (paymasterAndData.length >= 20) {
address paymaster = address(bytes20(paymasterAndData[0 : 20]));
if (paymaster.code.length == 0) {
// It would revert anyway. but give a meaningful message.
revert("AA30 paymaster not deployed");
}
}
// always revert
revert("");
}
//make sure depositTo cost is more than normal EntryPoint's cost,
// to mitigate DoS vector on the bundler
// empiric test showed that without this wrapper, simulation depositTo costs less..
function depositTo(address account) public override(IStakeManager, StakeManager) payable {
unchecked{
// silly code, to waste some gas to make sure depositTo is always little more
// expensive than on-chain call
uint256 x = 1;
while (x < 5) {
x++;
}
StakeManager.depositTo(account);
}
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
contract GasDebug {
// Phase 0: account creation
// Phase 1: validation
// Phase 2: execution
mapping(address account => mapping(uint256 phase => uint256 gas)) gasConsumed;
function setGasConsumed(address account, uint256 phase, uint256 gas) internal {
gasConsumed[account][phase] = gas;
}
function getGasConsumed(address account, uint256 phase) public view returns (uint256) {
return gasConsumed[account][phase];
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable no-inline-assembly */
/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* must return this value in case of signature failure, instead of revert.
*/
uint256 constant SIG_VALIDATION_FAILED = 1;
/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* return this value on success.
*/
uint256 constant SIG_VALIDATION_SUCCESS = 0;
/**
* Returned data from validateUserOp.
* validateUserOp returns a uint256, which is created by `_packedValidationData` and
* parsed by `_parseValidationData`.
* @param aggregator - address(0) - The account validated the signature by itself.
* address(1) - The account failed to validate the signature.
* otherwise - This is an address of a signature aggregator that must
* be used to validate the signature.
* @param validAfter - This UserOp is valid only after this timestamp.
* @param validaUntil - This UserOp is valid only up to this timestamp.
*/
struct ValidationData {
address aggregator;
uint48 validAfter;
uint48 validUntil;
}
/**
* Extract sigFailed, validAfter, validUntil.
* Also convert zero validUntil to type(uint48).max.
* @param validationData - The packed validation data.
*/
function _parseValidationData(
uint256 validationData
) pure returns (ValidationData memory data) {
address aggregator = address(uint160(validationData));
uint48 validUntil = uint48(validationData >> 160);
if (validUntil == 0) {
validUntil = type(uint48).max;
}
uint48 validAfter = uint48(validationData >> (48 + 160));
return ValidationData(aggregator, validAfter, validUntil);
}
/**
* Helper to pack the return value for validateUserOp.
* @param data - The ValidationData to pack.
*/
function _packValidationData(
ValidationData memory data
) pure returns (uint256) {
return
uint160(data.aggregator) |
(uint256(data.validUntil) << 160) |
(uint256(data.validAfter) << (160 + 48));
}
/**
* Helper to pack the return value for validateUserOp, when not using an aggregator.
* @param sigFailed - True for signature failure, false for success.
* @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite).
* @param validAfter - First timestamp this UserOperation is valid.
*/
function _packValidationData(
bool sigFailed,
uint48 validUntil,
uint48 validAfter
) pure returns (uint256) {
return
(sigFailed ? 1 : 0) |
(uint256(validUntil) << 160) |
(uint256(validAfter) << (160 + 48));
}
/**
* keccak function over calldata.
* @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it.
*/
function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) {
assembly ("memory-safe") {
let mem := mload(0x40)
let len := data.length
calldatacopy(mem, data.offset, len)
ret := keccak256(mem, len)
}
}
/**
* The minimum of two numbers.
* @param a - First number.
* @param b - Second number.
*/
function min(uint256 a, uint256 b) pure returns (uint256) {
return a < b ? a : b;
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
import "../interfaces/INonceManager.sol";
/**
* nonce management functionality
*/
abstract contract NonceManager is INonceManager {
/**
* The next valid sequence number for a given nonce key.
*/
mapping(address => mapping(uint192 => uint256)) public nonceSequenceNumber;
/// @inheritdoc INonceManager
function getNonce(address sender, uint192 key)
public view override returns (uint256 nonce) {
return nonceSequenceNumber[sender][key] | (uint256(key) << 64);
}
// allow an account to manually increment its own nonce.
// (mainly so that during construction nonce can be made non-zero,
// to "absorb" the gas cost of first nonce increment to 1st transaction (construction),
// not to 2nd transaction)
function incrementNonce(uint192 key) public override {
nonceSequenceNumber[msg.sender][key]++;
}
/**
* validate nonce uniqueness for this account.
* called just after validateUserOp()
* @return true if the nonce was incremented successfully.
* false if the current nonce doesn't match the given one.
*/
function _validateAndUpdateNonce(address sender, uint256 nonce) internal returns (bool) {
uint192 key = uint192(nonce >> 64);
uint64 seq = uint64(nonce);
return nonceSequenceNumber[sender][key]++ == seq;
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/**
* Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address,
* which is explicitly not the entryPoint itself.
*/
contract SenderCreator {
/**
* Call the "initCode" factory to create and return the sender account address.
* @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address,
* followed by calldata.
* @return sender - The returned address of the created account, or zero address on failure.
*/
function createSender(
bytes calldata initCode
) external returns (address sender) {
address factory = address(bytes20(initCode[0:20]));
bytes memory initCallData = initCode[20:];
bool success;
/* solhint-disable no-inline-assembly */
assembly ("memory-safe") {
success := call(
gas(),
factory,
0,
add(initCallData, 0x20),
mload(initCallData),
0,
32
)
sender := mload(0)
}
if (!success) {
sender = address(0);
}
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.23;
import "../interfaces/IStakeManager.sol";
/* solhint-disable avoid-low-level-calls */
/* solhint-disable not-rely-on-time */
/**
* 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 a paymaster.
*/
abstract contract StakeManager is IStakeManager {
/// maps paymaster to their deposits and stakes
mapping(address => DepositInfo) public deposits;
/// @inheritdoc IStakeManager
function getDepositInfo(
address account
) public view returns (DepositInfo memory info) {
return deposits[account];
}
/**
* Internal method to return just the stake info.
* @param addr - The account to query.
*/
function _getStakeInfo(
address addr
) internal view returns (StakeInfo memory info) {
DepositInfo storage depositInfo = deposits[addr];
info.stake = depositInfo.stake;
info.unstakeDelaySec = depositInfo.unstakeDelaySec;
}
/// @inheritdoc IStakeManager
function balanceOf(address account) public view returns (uint256) {
return deposits[account].deposit;
}
receive() external payable {
depositTo(msg.sender);
}
/**
* Increments an account's deposit.
* @param account - The account to increment.
* @param amount - The amount to increment by.
* @return the updated deposit of this account
*/
function _incrementDeposit(address account, uint256 amount) internal returns (uint256) {
DepositInfo storage info = deposits[account];
uint256 newAmount = info.deposit + amount;
info.deposit = newAmount;
return newAmount;
}
/**
* Add to the deposit of the given account.
* @param account - The account to add to.
*/
function depositTo(address account) public virtual payable {
uint256 newDeposit = _incrementDeposit(account, msg.value);
emit Deposited(account, newDeposit);
}
/**
* 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) public payable {
DepositInfo storage info = deposits[msg.sender];
require(unstakeDelaySec > 0, "must specify unstake delay");
require(
unstakeDelaySec >= info.unstakeDelaySec,
"cannot decrease unstake time"
);
uint256 stake = info.stake + msg.value;
require(stake > 0, "no stake specified");
require(stake <= type(uint112).max, "stake overflow");
deposits[msg.sender] = DepositInfo(
info.deposit,
true,
uint112(stake),
unstakeDelaySec,
0
);
emit StakeLocked(msg.sender, stake, unstakeDelaySec);
}
/**
* Attempt to unlock the stake.
* The value can be withdrawn (using withdrawStake) after the unstake delay.
*/
function unlockStake() external {
DepositInfo storage info = deposits[msg.sender];
require(info.unstakeDelaySec != 0, "not staked");
require(info.staked, "already unstaking");
uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec;
info.withdrawTime = withdrawTime;
info.staked = false;
emit StakeUnlocked(msg.sender, withdrawTime);
}
/**
* 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 {
DepositInfo storage info = deposits[msg.sender];
uint256 stake = info.stake;
require(stake > 0, "No stake to withdraw");
require(info.withdrawTime > 0, "must call unlockStake() first");
require(
info.withdrawTime <= block.timestamp,
"Stake withdrawal is not due"
);
info.unstakeDelaySec = 0;
info.withdrawTime = 0;
info.stake = 0;
emit StakeWithdrawn(msg.sender, withdrawAddress, stake);
(bool success,) = withdrawAddress.call{value: stake}("");
require(success, "failed to withdraw stake");
}
/**
* 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 {
DepositInfo storage info = deposits[msg.sender];
require(withdrawAmount <= info.deposit, "Withdraw amount too large");
info.deposit = info.deposit - withdrawAmount;
emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount);
(bool success,) = withdrawAddress.call{value: withdrawAmount}("");
require(success, "failed to withdraw");
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable no-inline-assembly */
import "../interfaces/PackedUserOperation.sol";
import {calldataKeccak, min} from "./Helpers.sol";
/**
* Utility functions helpful when working with UserOperation structs.
*/
library UserOperationLib {
uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20;
uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36;
uint256 public constant PAYMASTER_DATA_OFFSET = 52;
/**
* Get sender from user operation data.
* @param userOp - The user operation data.
*/
function getSender(
PackedUserOperation calldata userOp
) internal pure returns (address) {
address data;
//read sender from userOp, which is first userOp member (saves 800 gas...)
assembly {
data := calldataload(userOp)
}
return address(uint160(data));
}
/**
* Relayer/block builder might submit the TX with higher priorityFee,
* but the user should not pay above what he signed for.
* @param userOp - The user operation data.
*/
function gasPrice(
PackedUserOperation calldata userOp
) internal view returns (uint256) {
unchecked {
(uint256 maxPriorityFeePerGas, uint256 maxFeePerGas) = unpackUints(userOp.gasFees);
if (maxFeePerGas == maxPriorityFeePerGas) {
//legacy mode (for networks that don't support basefee opcode)
return maxFeePerGas;
}
return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
}
}
/**
* Pack the user operation data into bytes for hashing.
* @param userOp - The user operation data.
*/
function encode(
PackedUserOperation calldata userOp
) internal pure returns (bytes memory ret) {
address sender = getSender(userOp);
uint256 nonce = userOp.nonce;
bytes32 hashInitCode = calldataKeccak(userOp.initCode);
bytes32 hashCallData = calldataKeccak(userOp.callData);
bytes32 accountGasLimits = userOp.accountGasLimits;
uint256 preVerificationGas = userOp.preVerificationGas;
bytes32 gasFees = userOp.gasFees;
bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData);
return abi.encode(
sender, nonce,
hashInitCode, hashCallData,
accountGasLimits, preVerificationGas, gasFees,
hashPaymasterAndData
);
}
function unpackUints(
bytes32 packed
) internal pure returns (uint256 high128, uint256 low128) {
return (uint128(bytes16(packed)), uint128(uint256(packed)));
}
//unpack just the high 128-bits from a packed value
function unpackHigh128(bytes32 packed) internal pure returns (uint256) {
return uint256(packed) >> 128;
}
// unpack just the low 128-bits from a packed value
function unpackLow128(bytes32 packed) internal pure returns (uint256) {
return uint128(uint256(packed));
}
function unpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackHigh128(userOp.gasFees);
}
function unpackMaxFeePerGas(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackLow128(userOp.gasFees);
}
function unpackVerificationGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackHigh128(userOp.accountGasLimits);
}
function unpackCallGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackLow128(userOp.accountGasLimits);
}
function unpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET]));
}
function unpackPostOpGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET]));
}
function unpackPaymasterStaticFields(
bytes calldata paymasterAndData
) internal pure returns (address paymaster, uint256 validationGasLimit, uint256 postOpGasLimit) {
return (
address(bytes20(paymasterAndData[: PAYMASTER_VALIDATION_GAS_OFFSET])),
uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET])),
uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET]))
);
}
/**
* Hash the user operation data.
* @param userOp - The user operation data.
*/
function hash(
PackedUserOperation calldata userOp
) internal pure returns (bytes32) {
return keccak256(encode(userOp));
}
}// 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: 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: 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);
}/**
** 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: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
import "./IEntryPoint.sol";
interface IEntryPointSimulations is IEntryPoint {
// Return value of simulateHandleOp.
struct ExecutionResult {
uint256 preOpGas;
uint256 paid;
uint256 accountValidationData;
uint256 paymasterValidationData;
bool targetSuccess;
bytes targetResult;
}
/**
* Successful result from simulateValidation.
* If the account returns a signature aggregator the "aggregatorInfo" struct is filled in as well.
* @param returnInfo Gas and time-range returned values
* @param senderInfo Stake information about the sender
* @param factoryInfo Stake information about the factory (if any)
* @param paymasterInfo Stake information about the paymaster (if any)
* @param aggregatorInfo Signature aggregation info (if the account requires signature aggregator)
* Bundler MUST use it to verify the signature, or reject the UserOperation.
*/
struct ValidationResult {
ReturnInfo returnInfo;
StakeInfo senderInfo;
StakeInfo factoryInfo;
StakeInfo paymasterInfo;
AggregatorStakeInfo aggregatorInfo;
}
/**
* Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp.
* @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage
* outside the account's data.
* @param userOp - The user operation to validate.
* @return the validation result structure
*/
function simulateValidation(
PackedUserOperation calldata userOp
)
external
returns (
ValidationResult memory
);
/**
* Simulate full execution of a UserOperation (including both validation and target execution)
* It performs full validation of the UserOperation, but ignores signature error.
* An optional target address is called after the userop succeeds,
* and its value is returned (before the entire call is reverted).
* Note that in order to collect the the success/failure of the target call, it must be executed
* with trace enabled to track the emitted events.
* @param op The UserOperation to simulate.
* @param target - If nonzero, a target address to call after userop simulation. If called,
* the targetSuccess and targetResult are set to the return from that call.
* @param targetCallData - CallData to pass to target address.
* @return the execution result structure
*/
function simulateHandleOp(
PackedUserOperation calldata op,
address target,
bytes calldata targetCallData
)
external
returns (
ExecutionResult memory
);
}// 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: 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: 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;
/**
* 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: LGPL-3.0-only
pragma solidity ^0.8.23;
// solhint-disable no-inline-assembly
/**
* Utility functions helpful when making different kinds of contract calls in Solidity.
*/
library Exec {
function call(
address to,
uint256 value,
bytes memory data,
uint256 txGas
) internal returns (bool success) {
assembly ("memory-safe") {
success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
}
}
function staticcall(
address to,
bytes memory data,
uint256 txGas
) internal view returns (bool success) {
assembly ("memory-safe") {
success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0)
}
}
function delegateCall(
address to,
bytes memory data,
uint256 txGas
) internal returns (bool success) {
assembly ("memory-safe") {
success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
}
}
// get returned data from last call or calldelegate
function getReturnData(uint256 maxLen) internal pure returns (bytes memory returnData) {
assembly ("memory-safe") {
let len := returndatasize()
if gt(len, maxLen) {
len := maxLen
}
let ptr := mload(0x40)
mstore(0x40, add(ptr, add(len, 0x20)))
mstore(ptr, len)
returndatacopy(add(ptr, 0x20), 0, len)
returnData := ptr
}
}
// revert with explicit byte array (probably reverted info from call)
function revertWithData(bytes memory returnData) internal pure {
assembly ("memory-safe") {
revert(add(returnData, 32), mload(returnData))
}
}
function callAndRevert(address to, bytes memory data, uint256 maxLen) internal {
bool success = call(to,0,data,gasleft());
if (!success) {
revertWithData(getReturnData(maxLen));
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// 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.25;
/**
* @title FlatBytesLib
* @dev Library for storing bytes data in consecutive storage slots
* @dev This is useful in the context of the ERC-4337 validation rules
* @dev Be careful that this does not override existing data in the next slots and ideally use this
* data as the value of a struct
* @author Rhinestone
*/
library FlatBytesLib {
using FlatBytesLib for *;
/*//////////////////////////////////////////////////////////////////////////
DATA STRUCTURES
//////////////////////////////////////////////////////////////////////////*/
// Data structure to store bytes in consecutive slots using an array
struct Data {
bytes32[10] slot1;
}
// Store the length of the data and the data itself in consecutive slots
struct Bytes {
uint256 totalLength;
Data data;
}
/*//////////////////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/**
* Store the data in storage
*
* @param self The storage to store the data in
* @param data The data to store
*/
function store(Bytes storage self, bytes memory data) internal {
if (data.length > 32 * 10) revert();
bytes32[] memory entries;
(self.totalLength, entries) = data.toArray();
uint256 length = entries.length;
Data storage _data = self.data;
for (uint256 i; i < length; i++) {
bytes32 value = entries[i];
assembly {
sstore(add(_data.slot, i), value)
}
}
}
/**
* Clear the data in storage
*
* @param self The storage to clear the data in
*/
function clear(Bytes storage self) internal {
self.totalLength = 0;
Data storage _data = self.data;
for (uint256 i; i < 10; i++) {
assembly {
sstore(add(_data.slot, i), 0)
}
}
}
/**
* Load the data from storage
*
* @param self The storage to load the data from
*
* @return data The data loaded from storage
*/
function load(Bytes storage self) internal view returns (bytes memory data) {
return self.toBytes();
}
/*//////////////////////////////////////////////////////////////////////////
INTERNAL
//////////////////////////////////////////////////////////////////////////*/
/**
* Convert bytes to an array of bytes32
*
* @param data The data to convert
* @return totalLength The total length of the data
*
* @return dataList The data as an array of bytes32
*/
function toArray(bytes memory data)
internal
pure
returns (uint256 totalLength, bytes32[] memory dataList)
{
// Find 32 bytes segments nb
totalLength = data.length;
if (totalLength > 32 * 10) revert();
uint256 dataNb = totalLength / 32 + 1;
// Create an array of dataNb elements
dataList = new bytes32[](dataNb);
// Loop all 32 bytes segments
for (uint256 i = 0; i < dataNb; i++) {
bytes32 temp;
// Get 32 bytes from data
assembly {
temp := mload(add(data, mul(add(i, 1), 32)))
}
// Add extracted 32 bytes to list
dataList[i] = temp;
}
}
/**
* Convert an array of bytes32 to bytes
*
* @param self The array of bytes32 to convert
*
* @return data The data as bytes
*/
function toBytes(Bytes storage self) internal view returns (bytes memory data) {
uint256 totalLength = self.totalLength;
uint256 slotsCnt = totalLength / 32 + 1;
Data storage _data = self.data;
bytes32[] memory entries = new bytes32[](slotsCnt);
for (uint256 i; i < slotsCnt; i++) {
bytes32 tmp;
assembly {
tmp := sload(add(_data.slot, i))
}
entries[i] = tmp;
}
data = abi.encodePacked(entries);
assembly {
mstore(data, totalLength)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
/* solhint-disable no-unused-import */
import { PackedUserOperation } from
"@ERC4337/account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import { UserOperationLib } from "@ERC4337/account-abstraction/contracts/core/UserOperationLib.sol";
import { IEntryPoint } from "@ERC4337/account-abstraction/contracts/interfaces/IEntryPoint.sol";
import { EntryPointSimulations } from
"@ERC4337/account-abstraction/contracts/core/EntryPointSimulations.sol";
import { IEntryPointSimulations } from
"@ERC4337/account-abstraction/contracts/interfaces/IEntryPointSimulations.sol";
import {
ValidationData,
_packValidationData
} from "@ERC4337/account-abstraction/contracts/core/Helpers.sol";
import { IStakeManager } from "@ERC4337/account-abstraction/contracts/interfaces/IStakeManager.sol";
import { IAccount as IERC4337 } from
"@ERC4337/account-abstraction/contracts/interfaces/IAccount.sol";
import { IAccountExecute } from
"@ERC4337/account-abstraction/contracts/interfaces/IAccountExecute.sol";
/* solhint-enable no-unused-import */// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol";
uint256 constant VALIDATION_SUCCESS = 0;
uint256 constant VALIDATION_FAILED = 1;
uint256 constant MODULE_TYPE_VALIDATOR = 1;
uint256 constant MODULE_TYPE_EXECUTOR = 2;
uint256 constant MODULE_TYPE_FALLBACK = 3;
uint256 constant MODULE_TYPE_HOOK = 4;
interface IModule {
error AlreadyInitialized(address smartAccount);
error NotInitialized(address smartAccount);
/**
* @dev This function is called by the smart account during installation of the module
* @param data arbitrary data that may be required on the module during `onInstall`
* initialization
*
* MUST revert on error (i.e. if module is already enabled)
*/
function onInstall(bytes calldata data) external;
/**
* @dev This function is called by the smart account during uninstallation of the module
* @param data arbitrary data that may be required on the module during `onUninstall`
* de-initialization
*
* MUST revert on error
*/
function onUninstall(bytes calldata data) external;
/**
* @dev Returns boolean value if module is a certain type
* @param moduleTypeId the module type ID according the ERC-7579 spec
*
* MUST return true if the module is of the given type and false otherwise
*/
function isModuleType(uint256 moduleTypeId) external view returns (bool);
/**
* @dev Returns if the module was already initialized for a provided smartaccount
*/
function isInitialized(address smartAccount) external view returns (bool);
}
interface IValidator is IModule {
error InvalidTargetAddress(address target);
/**
* @dev Validates a transaction on behalf of the account.
* This function is intended to be called by the MSA during the ERC-4337 validaton phase
* Note: solely relying on bytes32 hash and signature is not suffcient for some
* validation implementations (i.e. SessionKeys often need access to userOp.calldata)
* @param userOp The user operation to be validated. The userOp MUST NOT contain any metadata.
* The MSA MUST clean up the userOp before sending it to the validator.
* @param userOpHash The hash of the user operation to be validated
* @return return value according to ERC-4337
*/
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
)
external
returns (uint256);
/**
* Validator can be used for ERC-1271 validation
*/
function isValidSignatureWithSender(
address sender,
bytes32 hash,
bytes calldata data
)
external
view
returns (bytes4);
}
interface IExecutor is IModule { }
interface IHook is IModule {
function preCheck(
address msgSender,
uint256 msgValue,
bytes calldata msgData
)
external
returns (bytes memory hookData);
function postCheck(bytes calldata hookData) external;
}
interface IFallback is IModule { }// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
interface IERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}{
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"appendCBOR": true,
"bytecodeHash": "none",
"useLiteralContent": false
},
"optimizer": {
"enabled": true,
"runs": 833
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": [
"@rhinestone/=node_modules/@rhinestone/",
"sentinellist/=node_modules/@rhinestone/sentinellist/src/",
"erc4337-validation/=node_modules/@rhinestone/erc4337-validation/src/",
"safe7579/=node_modules/@rhinestone/safe7579/src/",
"modulekit/=node_modules/@rhinestone/modulekit/src/",
"module-bases/=node_modules/@rhinestone/module-bases/src/",
"solmate/=node_modules/solmate/src/",
"@ERC4337/=node_modules/@ERC4337/",
"account-abstraction/=node_modules/@ERC4337/account-abstraction/contracts/",
"account-abstraction-v0.6/=node_modules/@ERC4337/account-abstraction-v0.6/contracts/",
"@openzeppelin/=node_modules/@openzeppelin/contracts/",
"@safe-global/=node_modules/@safe-global/",
"ds-test/=node_modules/ds-test/src/",
"erc7579/=node_modules/erc7579/src/",
"forge-std/=node_modules/forge-std/src/",
"solady/=node_modules/solady/src/",
"solarray/=node_modules/solarray/src/",
"@prb/math/=node_modules/@prb/math/src/",
"freshcryptolib/=node_modules/freshcryptolib/solidity/src/",
"kernel/=node_modules/@zerodev/kernel/src/",
"ExcessivelySafeCall/=node_modules/excessively-safe-call/src/",
"excessively-safe-call/=node_modules/excessively-safe-call/src/",
"flatbytes/=node_modules/@rhinestone/flatbytes/src/",
"@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
"stringutils/=node_modules/stringutils/src/",
"@gnosis.pm/=node_modules/@gnosis.pm/",
"@zerodev/=node_modules/@zerodev/",
"hardhat-deploy/=node_modules/hardhat-deploy/",
"hardhat/=node_modules/hardhat/"
],
"viaIR": false
}Contract ABI
API[{"inputs":[{"internalType":"ConfigId","name":"id","type":"bytes32"},{"internalType":"address","name":"mxer","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"PolicyNotInitialized","type":"error"},{"inputs":[{"internalType":"ConfigId","name":"id","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"limit","type":"uint256"}],"name":"ValueLimitExceeded","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"ConfigId","name":"id","type":"bytes32"},{"indexed":false,"internalType":"address","name":"multiplexer","type":"address"},{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"PolicySet","type":"event"},{"inputs":[{"internalType":"ConfigId","name":"id","type":"bytes32"},{"internalType":"address","name":"msgSender","type":"address"},{"internalType":"address","name":"userOpSender","type":"address"}],"name":"actionConfigs","outputs":[{"internalType":"uint256","name":"valueLimitPerUse","type":"uint256"},{"components":[{"internalType":"uint256","name":"length","type":"uint256"},{"components":[{"internalType":"enum ParamCondition","name":"condition","type":"uint8"},{"internalType":"uint64","name":"offset","type":"uint64"},{"internalType":"bool","name":"isLimited","type":"bool"},{"internalType":"bytes32","name":"ref","type":"bytes32"},{"components":[{"internalType":"uint256","name":"limit","type":"uint256"},{"internalType":"uint256","name":"used","type":"uint256"}],"internalType":"struct LimitUsage","name":"usage","type":"tuple"}],"internalType":"struct ParamRule[16]","name":"rules","type":"tuple[16]"}],"internalType":"struct ParamRules","name":"paramRules","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"ConfigId","name":"id","type":"bytes32"},{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"checkAction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"ConfigId","name":"configId","type":"bytes32"},{"internalType":"bytes","name":"initData","type":"bytes"}],"name":"initializeWithMultiplexer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}]Contract Creation Code
6080604052348015600e575f5ffd5b50610c0c8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c806301ffc9a71461004e57806305c0089514610076578063989c9e4614610097578063cc39a985146100ac575b5f5ffd5b61006161005c36600461074f565b6100cd565b60405190151581526020015b60405180910390f35b6100896100843660046107d6565b61011e565b60405190815260200161006d565b6100aa6100a5366004610849565b610229565b005b6100bf6100ba36600461089f565b610283565b60405161006d9291906108ec565b5f6001600160e01b031982166301ffc9a760e01b14806100fd57506001600160e01b03198216634c4e4f2360e11b145b8061011857506001600160e01b031982166305c0089560e01b145b92915050565b5f86815260208181526040808320338085529083528184206001600160a01b038a1685529092528220600181015490918991899061018e5760405163369d19e760e11b815260048101939093526001600160a01b0391821660248401521660448201526064015b60405180910390fd5b505081548991508690808211156101c957604051638a8a722d60e01b8152600481019390935260248301919091526044820152606401610185565b50505060018101545f5b818110156102185761020086866002860184601081106101f5576101f5610993565b600402019190610393565b610210576001935050505061021f565b6001016101d3565b505f925050505b9695505050505050565b6102368333868585610604565b604080518481523360208201526001600160a01b0386168183015290517f5d14f8bf6f75758495bb0b0768b81cdebc7869d1f19edacc2f483ca0c89a17159181900360600190a150505050565b5f602081815293815260408082208552928152828120845290815281812080548351808501855260018301805482528551610200810190965291959294909391928401916002860190601090835b82821015610386576040805160a081019091526004830285018054829060ff166006811115610302576103026108d8565b6006811115610313576103136108d8565b81528154610100810467ffffffffffffffff16602080840191909152690100000000000000000090910460ff1615156040808401919091526001808501546060850152815180830190925260028501548252600390940154818301526080909201919091529183529290920191016102d1565b5050505081525050905082565b82545f908190849084906103b790610100900467ffffffffffffffff1660046109bb565b875467ffffffffffffffff91821692916103d89161010090041660046109bb565b6103e39060206109bb565b67ffffffffffffffff16926103fa939291906109db565b61040391610a02565b90505f855460ff16600681111561041c5761041c6108d8565b14801561042d575084600101548114155b1561043b575f9150506105fd565b6001855460ff166006811115610453576104536108d8565b148015610464575084600101548111155b15610472575f9150506105fd565b6002855460ff16600681111561048a5761048a6108d8565b14801561049b575084600101548110155b156104a9575f9150506105fd565b6003855460ff1660068111156104c1576104c16108d8565b1480156104d15750846001015481105b156104df575f9150506105fd565b6004855460ff1660068111156104f7576104f76108d8565b1480156105075750846001015481115b15610515575f9150506105fd565b6005855460ff16600681111561052d5761052d6108d8565b14801561053d5750846001015481145b1561054b575f9150506105fd565b6006855460ff166006811115610563576105636108d8565b036105a057600185015460801c811080610592575060018501546fffffffffffffffffffffffffffffffff1681115b156105a0575f9150506105fd565b84546901000000000000000000900460ff16156105f757600285015460038601546105cc908390610a1f565b11156105db575f9150506105fd565b6003850180548291905f906105f1908490610a1f565b90915550505b60019150505b9392505050565b5f61061182840184610ae5565b5f878152602081815260408083206001600160a01b03808b168552908352818420908916845290915290209091506106499082610651565b505050505050565b8051825560208101515160018301555f5b60208201515181101561074a57816020015160200151816010811061068957610689610993565b60200201516002840182601081106106a3576106a3610993565b600402015f820151815f015f6101000a81548160ff021916908360068111156106ce576106ce6108d8565b021790555060208281015182546040850151151569010000000000000000000269ff0000000000000000001967ffffffffffffffff909316610100029290921669ffffffffffffffffff001990911617178255606083015160018084019190915560809093015180516002840155015160039091015501610662565b505050565b5f6020828403121561075f575f5ffd5b81356001600160e01b0319811681146105fd575f5ffd5b80356001600160a01b038116811461078c575f5ffd5b919050565b5f5f83601f8401126107a1575f5ffd5b50813567ffffffffffffffff8111156107b8575f5ffd5b6020830191508360208285010111156107cf575f5ffd5b9250929050565b5f5f5f5f5f5f60a087890312156107eb575f5ffd5b863595506107fb60208801610776565b945061080960408801610776565b935060608701359250608087013567ffffffffffffffff81111561082b575f5ffd5b61083789828a01610791565b979a9699509497509295939492505050565b5f5f5f5f6060858703121561085c575f5ffd5b61086585610776565b935060208501359250604085013567ffffffffffffffff811115610887575f5ffd5b61089387828801610791565b95989497509550505050565b5f5f5f606084860312156108b1575f5ffd5b833592506108c160208501610776565b91506108cf60408501610776565b90509250925092565b634e487b7160e01b5f52602160045260245ffd5b5f610c4082019050838252825160208301526020830151604083015f5b601081101561098957825180516007811061093257634e487b7160e01b5f52602160045260245ffd5b835260208181015167ffffffffffffffff1681850152604080830151151590850152606080830151908501526080918201518051928501929092529081015160a0840152929092019160c090910190600101610909565b5050509392505050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b67ffffffffffffffff8181168382160190811115610118576101186109a7565b5f5f858511156109e9575f5ffd5b838611156109f5575f5ffd5b5050820193919092039150565b80356020831015610118575f19602084900360031b1b1692915050565b80820180821115610118576101186109a7565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff81118282101715610a6957610a69610a32565b60405290565b604051610200810167ffffffffffffffff81118282101715610a6957610a69610a32565b60405160a0810167ffffffffffffffff81118282101715610a6957610a69610a32565b5f60408284031215610ac6575f5ffd5b610ace610a46565b823581526020928301359281019290925250919050565b5f818303610c4081128015610af8575f5ffd5b50610b01610a46565b83358152610c20601f1983011215610b17575f5ffd5b610b1f610a46565b602085013581529150605f84018513610b36575f5ffd5b610b3e610a6f565b80610c40860187811115610b50575f5ffd5b604087015b81811015610be65760c0818a031215610b6c575f5ffd5b610b74610a93565b813560078110610b82575f5ffd5b8152602082013567ffffffffffffffff81168114610b9e575f5ffd5b602082015260408201358015158114610bb5575f5ffd5b604082015260608281013590820152610bd18a60808401610ab6565b6080820152845260209093019260c001610b55565b505060208481019190915282019290925294935050505056fea164736f6c634300081c000a
Deployed Bytecode
0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c806301ffc9a71461004e57806305c0089514610076578063989c9e4614610097578063cc39a985146100ac575b5f5ffd5b61006161005c36600461074f565b6100cd565b60405190151581526020015b60405180910390f35b6100896100843660046107d6565b61011e565b60405190815260200161006d565b6100aa6100a5366004610849565b610229565b005b6100bf6100ba36600461089f565b610283565b60405161006d9291906108ec565b5f6001600160e01b031982166301ffc9a760e01b14806100fd57506001600160e01b03198216634c4e4f2360e11b145b8061011857506001600160e01b031982166305c0089560e01b145b92915050565b5f86815260208181526040808320338085529083528184206001600160a01b038a1685529092528220600181015490918991899061018e5760405163369d19e760e11b815260048101939093526001600160a01b0391821660248401521660448201526064015b60405180910390fd5b505081548991508690808211156101c957604051638a8a722d60e01b8152600481019390935260248301919091526044820152606401610185565b50505060018101545f5b818110156102185761020086866002860184601081106101f5576101f5610993565b600402019190610393565b610210576001935050505061021f565b6001016101d3565b505f925050505b9695505050505050565b6102368333868585610604565b604080518481523360208201526001600160a01b0386168183015290517f5d14f8bf6f75758495bb0b0768b81cdebc7869d1f19edacc2f483ca0c89a17159181900360600190a150505050565b5f602081815293815260408082208552928152828120845290815281812080548351808501855260018301805482528551610200810190965291959294909391928401916002860190601090835b82821015610386576040805160a081019091526004830285018054829060ff166006811115610302576103026108d8565b6006811115610313576103136108d8565b81528154610100810467ffffffffffffffff16602080840191909152690100000000000000000090910460ff1615156040808401919091526001808501546060850152815180830190925260028501548252600390940154818301526080909201919091529183529290920191016102d1565b5050505081525050905082565b82545f908190849084906103b790610100900467ffffffffffffffff1660046109bb565b875467ffffffffffffffff91821692916103d89161010090041660046109bb565b6103e39060206109bb565b67ffffffffffffffff16926103fa939291906109db565b61040391610a02565b90505f855460ff16600681111561041c5761041c6108d8565b14801561042d575084600101548114155b1561043b575f9150506105fd565b6001855460ff166006811115610453576104536108d8565b148015610464575084600101548111155b15610472575f9150506105fd565b6002855460ff16600681111561048a5761048a6108d8565b14801561049b575084600101548110155b156104a9575f9150506105fd565b6003855460ff1660068111156104c1576104c16108d8565b1480156104d15750846001015481105b156104df575f9150506105fd565b6004855460ff1660068111156104f7576104f76108d8565b1480156105075750846001015481115b15610515575f9150506105fd565b6005855460ff16600681111561052d5761052d6108d8565b14801561053d5750846001015481145b1561054b575f9150506105fd565b6006855460ff166006811115610563576105636108d8565b036105a057600185015460801c811080610592575060018501546fffffffffffffffffffffffffffffffff1681115b156105a0575f9150506105fd565b84546901000000000000000000900460ff16156105f757600285015460038601546105cc908390610a1f565b11156105db575f9150506105fd565b6003850180548291905f906105f1908490610a1f565b90915550505b60019150505b9392505050565b5f61061182840184610ae5565b5f878152602081815260408083206001600160a01b03808b168552908352818420908916845290915290209091506106499082610651565b505050505050565b8051825560208101515160018301555f5b60208201515181101561074a57816020015160200151816010811061068957610689610993565b60200201516002840182601081106106a3576106a3610993565b600402015f820151815f015f6101000a81548160ff021916908360068111156106ce576106ce6108d8565b021790555060208281015182546040850151151569010000000000000000000269ff0000000000000000001967ffffffffffffffff909316610100029290921669ffffffffffffffffff001990911617178255606083015160018084019190915560809093015180516002840155015160039091015501610662565b505050565b5f6020828403121561075f575f5ffd5b81356001600160e01b0319811681146105fd575f5ffd5b80356001600160a01b038116811461078c575f5ffd5b919050565b5f5f83601f8401126107a1575f5ffd5b50813567ffffffffffffffff8111156107b8575f5ffd5b6020830191508360208285010111156107cf575f5ffd5b9250929050565b5f5f5f5f5f5f60a087890312156107eb575f5ffd5b863595506107fb60208801610776565b945061080960408801610776565b935060608701359250608087013567ffffffffffffffff81111561082b575f5ffd5b61083789828a01610791565b979a9699509497509295939492505050565b5f5f5f5f6060858703121561085c575f5ffd5b61086585610776565b935060208501359250604085013567ffffffffffffffff811115610887575f5ffd5b61089387828801610791565b95989497509550505050565b5f5f5f606084860312156108b1575f5ffd5b833592506108c160208501610776565b91506108cf60408501610776565b90509250925092565b634e487b7160e01b5f52602160045260245ffd5b5f610c4082019050838252825160208301526020830151604083015f5b601081101561098957825180516007811061093257634e487b7160e01b5f52602160045260245ffd5b835260208181015167ffffffffffffffff1681850152604080830151151590850152606080830151908501526080918201518051928501929092529081015160a0840152929092019160c090910190600101610909565b5050509392505050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b67ffffffffffffffff8181168382160190811115610118576101186109a7565b5f5f858511156109e9575f5ffd5b838611156109f5575f5ffd5b5050820193919092039150565b80356020831015610118575f19602084900360031b1b1692915050565b80820180821115610118576101186109a7565b634e487b7160e01b5f52604160045260245ffd5b6040805190810167ffffffffffffffff81118282101715610a6957610a69610a32565b60405290565b604051610200810167ffffffffffffffff81118282101715610a6957610a69610a32565b60405160a0810167ffffffffffffffff81118282101715610a6957610a69610a32565b5f60408284031215610ac6575f5ffd5b610ace610a46565b823581526020928301359281019290925250919050565b5f818303610c4081128015610af8575f5ffd5b50610b01610a46565b83358152610c20601f1983011215610b17575f5ffd5b610b1f610a46565b602085013581529150605f84018513610b36575f5ffd5b610b3e610a6f565b80610c40860187811115610b50575f5ffd5b604087015b81811015610be65760c0818a031215610b6c575f5ffd5b610b74610a93565b813560078110610b82575f5ffd5b8152602082013567ffffffffffffffff81168114610b9e575f5ffd5b602082015260408201358015158114610bb5575f5ffd5b604082015260608281013590820152610bd18a60808401610ab6565b6080820152845260209093019260c001610b55565b505060208481019190915282019290925294935050505056fea164736f6c634300081c000a
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ 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.