// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)pragmasolidity ^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);
* }
* ```
*/abstractcontractERC165isIERC165{
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool) {
return interfaceId ==type(IERC165).interfaceId;
}
}
Contract Source Code
File 2 of 18: EntryPoint.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity ^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"./Exec.sol";
import"./StakeManager.sol";
import"./SenderCreator.sol";
import"./Helpers.sol";
import"./NonceManager.sol";
import"./UserOperationLib.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.orgcontractEntryPointisIEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ERC165{
usingUserOperationLibforPackedUserOperation;
SenderCreator privateimmutable _senderCreator =new SenderCreator();
functionsenderCreator() internalviewvirtualreturns (SenderCreator) {
return _senderCreator;
}
//compensate for innerHandleOps' emit message and deposit refund.// allow some slack for future gas price changes.uint256privateconstant INNER_GAS_OVERHEAD =10000;
// Marker for inner call revert on out of gasbytes32privateconstant INNER_OUT_OF_GAS =hex"deaddead";
bytes32privateconstant INNER_REVERT_LOW_PREFUND =hex"deadaa51";
uint256privateconstant REVERT_REASON_MAX_LEN =2048;
uint256privateconstant PENALTY_PERCENT =10;
/// @inheritdoc IERC165functionsupportsInterface(bytes4 interfaceId) publicviewvirtualoverridereturns (bool) {
// note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everythingreturn 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(addresspayable 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
)
internalreturns
(uint256 collected) {
uint256 preGas =gasleft();
bytesmemory context = getMemoryBytesFromOffset(opInfo.contextOffset);
bool success;
{
uint256 saveFreePtr;
assembly ("memory-safe") {
saveFreePtr :=mload(0x40)
}
bytescalldata callData = userOp.callData;
bytesmemory innerCall;
bytes4 methodSig;
assembly {
let len := callData.lengthifgt(len, 3) {
methodSig :=calldataload(callData.offset)
}
}
if (methodSig == IAccountExecute.executeUserOp.selector) {
bytesmemory 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()
ifeq(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");
} elseif (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
);
}
}
}
functionemitUserOperationEvent(UserOpInfo memory opInfo, bool success, uint256 actualGasCost, uint256 actualGas) internalvirtual{
emit UserOperationEvent(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.paymaster,
opInfo.mUserOp.nonce,
success,
actualGasCost,
actualGas
);
}
functionemitPrefundTooLow(UserOpInfo memory opInfo) internalvirtual{
emit UserOperationPrefundTooLow(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.nonce
);
}
/// @inheritdoc IEntryPointfunctionhandleOps(
PackedUserOperation[] calldata ops,
addresspayable beneficiary
) publicnonReentrant{
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 IEntryPointfunctionhandleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
addresspayable beneficiary
) publicnonReentrant{
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-blockstry 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.
*/structMemoryUserOp {
address sender;
uint256 nonce;
uint256 verificationGasLimit;
uint256 callGasLimit;
uint256 paymasterVerificationGasLimit;
uint256 paymasterPostOpGasLimit;
uint256 preVerificationGas;
address paymaster;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
}
structUserOpInfo {
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
*/functioninnerHandleOp(bytesmemory callData,
UserOpInfo memory opInfo,
bytescalldata context
) externalreturns (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) {
bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit);
if (!success) {
bytesmemory 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 IEntryPointfunctiongetUserOpHash(
PackedUserOperation calldata userOp
) publicviewreturns (bytes32) {
returnkeccak256(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
) internalpure{
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);
bytescalldata 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
) internalpurereturns (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,
bytescalldata initCode
) internal{
if (initCode.length!=0) {
address sender = opInfo.mUserOp.sender;
if (sender.code.length!=0)
revert FailedOp(opIndex, "AA10 sender already constructed");
address sender1 = senderCreator().createSender{
gas: opInfo.mUserOp.verificationGasLimit
}(initCode);
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 IEntryPointfunctiongetSenderAddress(bytescalldata 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
)
internalreturns (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;
}
try
IAccount(sender).validateUserOp{
gas: verificationGasLimit
}(op, opInfo.userOpHash, missingAccountFunds)
returns (uint256 _validationData) {
validationData = _validationData;
} 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
) internalreturns (bytesmemory 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 (bytesmemory _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
) internalview{
(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
) internalviewreturns (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
)
internalreturns (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");
}
}
bytesmemory 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,
bytesmemory context,
uint256 actualGas
) privatereturns (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 {
bytesmemory 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 limitsif (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.
*/functiongetUserOpGasPrice(
MemoryUserOp memory mUserOp
) internalviewreturns (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.
*/functiongetOffsetOfMemoryBytes(bytesmemory data
) internalpurereturns (uint256 offset) {
assembly {
offset := data
}
}
/**
* The bytes in memory at the given offset.
* @param offset - The offset to get the bytes from.
*/functiongetMemoryBytesFromOffset(uint256 offset
) internalpurereturns (bytesmemory data) {
assembly ("memory-safe") {
data := offset
}
}
/// @inheritdoc IEntryPointfunctiondelegateAndRevert(address target, bytescalldata data) external{
(bool success, bytesmemory ret) = target.delegatecall(data);
revert DelegateAndRevert(success, ret);
}
}
Contract Source Code
File 3 of 18: Exec.sol
// SPDX-License-Identifier: LGPL-3.0-onlypragmasolidity ^0.8.23;// solhint-disable no-inline-assembly/**
* Utility functions helpful when making different kinds of contract calls in Solidity.
*/libraryExec{
functioncall(address to,
uint256 value,
bytesmemory data,
uint256 txGas
) internalreturns (bool success) {
assembly ("memory-safe") {
success :=call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
}
}
functionstaticcall(address to,
bytesmemory data,
uint256 txGas
) internalviewreturns (bool success) {
assembly ("memory-safe") {
success :=staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0)
}
}
functiondelegateCall(address to,
bytesmemory data,
uint256 txGas
) internalreturns (bool success) {
assembly ("memory-safe") {
success :=delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
}
}
// get returned data from last call or calldelegatefunctiongetReturnData(uint256 maxLen) internalpurereturns (bytesmemory returnData) {
assembly ("memory-safe") {
let len :=returndatasize()
ifgt(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)functionrevertWithData(bytesmemory returnData) internalpure{
assembly ("memory-safe") {
revert(add(returnData, 32), mload(returnData))
}
}
functioncallAndRevert(address to, bytesmemory data, uint256 maxLen) internal{
bool success = call(to,0,data,gasleft());
if (!success) {
revertWithData(getReturnData(maxLen));
}
}
}
Contract Source Code
File 4 of 18: Helpers.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity ^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.
*/uint256constant SIG_VALIDATION_FAILED =1;
/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* return this value on success.
*/uint256constant 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.
*/structValidationData {
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
) purereturns (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
) purereturns (uint256) {
returnuint160(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
) purereturns (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.
*/functioncalldataKeccak(bytescalldata data) purereturns (bytes32 ret) {
assembly ("memory-safe") {
let mem :=mload(0x40)
let len := data.lengthcalldatacopy(mem, data.offset, len)
ret :=keccak256(mem, len)
}
}
/**
* The minimum of two numbers.
* @param a - First number.
* @param b - Second number.
*/functionmin(uint256 a, uint256 b) purereturns (uint256) {
return a < b ? a : b;
}
Contract Source Code
File 5 of 18: IAccount.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity >=0.7.5;import"./PackedUserOperation.sol";
interfaceIAccount{
structPermit {
address authorizedAddress;
address verifyingContract;
uint256 nonce;
uint256 until;
}
structCall {
address dest;
address signer;
uint256 nonce;
uint256 value;
bytes data;
}
/**
* 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.
*/functionvalidateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) externalreturns (uint256 validationData);
functioncall(Call calldata _callData, bytescalldata signature_) external;
}
Contract Source Code
File 6 of 18: IAccountExecute.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity >=0.7.5;import"./PackedUserOperation.sol";
interfaceIAccountExecute{
/**
* 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.
*/functionexecuteUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external;
}
Contract Source Code
File 7 of 18: IAggregator.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity >=0.7.5;import"./PackedUserOperation.sol";
/**
* Aggregated Signatures validator.
*/interfaceIAggregator{
/**
* 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.
*/functionvalidateSignatures(
PackedUserOperation[] calldata userOps,
bytescalldata signature
) externalview;
/**
* 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".
*/functionvalidateUserOpSignature(
PackedUserOperation calldata userOp
) externalviewreturns (bytesmemory 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.
*/functionaggregateSignatures(
PackedUserOperation[] calldata userOps
) externalviewreturns (bytesmemory aggregatedSignature);
}
Contract Source Code
File 8 of 18: IERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)pragmasolidity ^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}.
*/interfaceIERC165{
/**
* @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.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
Contract Source Code
File 9 of 18: IEntryPoint.sol
/**
** Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
** Only one instance required on each chain.
**/// SPDX-License-Identifier: GPL-3.0pragmasolidity >=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";
interfaceIEntryPointisIStakeManager, 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).
*/eventUserOperationEvent(bytes32indexed userOpHash,
addressindexed sender,
addressindexed 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
*/eventAccountDeployed(bytes32indexed userOpHash,
addressindexed 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".
*/eventUserOperationRevertReason(bytes32indexed userOpHash,
addressindexed 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".
*/eventPostOpRevertReason(bytes32indexed userOpHash,
addressindexed 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.
*/eventUserOperationPrefundTooLow(bytes32indexed userOpHash,
addressindexed sender,
uint256 nonce
);
/**
* An event emitted by handleOps(), before starting the execution loop.
* Any event emitted before this event, is part of the validation.
*/eventBeforeExecution();
/**
* Signature aggregator used by the following UserOperationEvents within this bundle.
* @param aggregator - The aggregator used for the following UserOperationEvents.
*/eventSignatureAggregatorChanged(addressindexed 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.
*/errorFailedOp(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
*/errorFailedOpWithRevert(uint256 opIndex, string reason, bytes inner);
errorPostOpReverted(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
*/errorSignatureValidationFailed(address aggregator);
// Return value of getSenderAddress.errorSenderAddressResult(address sender);
// UserOps handled, per aggregator.structUserOpsPerAggregator {
PackedUserOperation[] userOps;
// Aggregator address
IAggregator aggregator;
// Aggregated signaturebytes 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.
*/functionhandleOps(
PackedUserOperation[] calldata ops,
addresspayable 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.
*/functionhandleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
addresspayable 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
*/functiongetUserOpHash(
PackedUserOperation calldata userOp
) externalviewreturns (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)
*/structReturnInfo {
uint256 preOpGas;
uint256 prefund;
uint256 accountValidationData;
uint256 paymasterValidationData;
bytes paymasterContext;
}
/**
* Returned aggregated signature info:
* The aggregator returned by the account, and its current stake.
*/structAggregatorStakeInfo {
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.
*/functiongetSenderAddress(bytesmemory initCode) external;
errorDelegateAndRevert(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
*/functiondelegateAndRevert(address target, bytescalldata data) external;
}
Contract Source Code
File 10 of 18: INonceManager.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity >=0.7.5;interfaceINonceManager{
/**
* 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.
*/functiongetNonce(address sender, uint192 key)
externalviewreturns (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.
*/functionincrementNonce(uint192 key) external;
}
Contract Source Code
File 11 of 18: IPaymaster.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity >=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.
*/interfaceIPaymaster{
enumPostOpMode {
// 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.
*/functionvalidatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) externalreturns (bytesmemory 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.
*/functionpostOp(
PostOpMode mode,
bytescalldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
) external;
}
Contract Source Code
File 12 of 18: IStakeManager.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity >=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.
*/interfaceIStakeManager{
eventDeposited(addressindexed account, uint256 totalDeposit);
eventWithdrawn(addressindexed account,
address withdrawAddress,
uint256 amount
);
// Emitted when stake or unstake delay are modified.eventStakeLocked(addressindexed account,
uint256 totalStaked,
uint256 unstakeDelaySec
);
// Emitted once a stake is scheduled for withdrawal.eventStakeUnlocked(addressindexed account, uint256 withdrawTime);
eventStakeWithdrawn(addressindexed 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
*/structDepositInfo {
uint256 deposit;
bool staked;
uint112 stake;
uint32 unstakeDelaySec;
uint48 withdrawTime;
}
// API struct used by getStakeInfo and simulateValidation.structStakeInfo {
uint256 stake;
uint256 unstakeDelaySec;
}
/**
* Get deposit info.
* @param account - The account to query.
* @return info - Full deposit information of given account.
*/functiongetDepositInfo(address account
) externalviewreturns (DepositInfo memory info);
/**
* Get account balance.
* @param account - The account to query.
* @return - The deposit (for gas payment) of the account.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* Add to the deposit of the given account.
* @param account - The account to add to.
*/functiondepositTo(address account) externalpayable;
/**
* 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.
*/functionaddStake(uint32 _unstakeDelaySec) externalpayable;
/**
* Attempt to unlock the stake.
* The value can be withdrawn (using withdrawStake) after the unstake delay.
*/functionunlockStake() 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.
*/functionwithdrawStake(addresspayable withdrawAddress) external;
/**
* Withdraw from the deposit.
* @param withdrawAddress - The address to send withdrawn value.
* @param withdrawAmount - The amount to withdraw.
*/functionwithdrawTo(addresspayable withdrawAddress,
uint256 withdrawAmount
) external;
}
Contract Source Code
File 13 of 18: NonceManager.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity ^0.8.23;import"./interfaces/INonceManager.sol";
/**
* nonce management functionality
*/abstractcontractNonceManagerisINonceManager{
/**
* The next valid sequence number for a given nonce key.
*/mapping(address=>mapping(uint192=>uint256)) public nonceSequenceNumber;
/// @inheritdoc INonceManagerfunctiongetNonce(address sender, uint192 key)
publicviewoverridereturns (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)functionincrementNonce(uint192 key) publicoverride{
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) internalreturns (bool) {
uint192 key =uint192(nonce >>64);
uint64 seq =uint64(nonce);
return nonceSequenceNumber[sender][key]++== seq;
}
}
Contract Source Code
File 14 of 18: PackedUserOperation.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity >=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.
*/structPackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}
Contract Source Code
File 15 of 18: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)pragmasolidity ^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].
*/abstractcontractReentrancyGuard{
// 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.uint256privateconstant NOT_ENTERED =1;
uint256privateconstant ENTERED =2;
uint256private _status;
/**
* @dev Unauthorized reentrant call.
*/errorReentrancyGuardReentrantCall();
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.
*/modifiernonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function_nonReentrantBefore() private{
// On the first call to nonReentrant, _status will be NOT_ENTEREDif (_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() internalviewreturns (bool) {
return _status == ENTERED;
}
}
Contract Source Code
File 16 of 18: SenderCreator.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity ^0.8.23;/**
* Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address,
* which is explicitly not the entryPoint itself.
*/contractSenderCreator{
/**
* 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.
*/functioncreateSender(bytescalldata initCode
) externalreturns (address sender) {
address factory =address(bytes20(initCode[0:20]));
bytesmemory 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);
}
}
}
Contract Source Code
File 17 of 18: StakeManager.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity ^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.
*/abstractcontractStakeManagerisIStakeManager{
/// maps paymaster to their deposits and stakesmapping(address=> DepositInfo) public deposits;
/// @inheritdoc IStakeManagerfunctiongetDepositInfo(address account
) publicviewreturns (DepositInfo memory info) {
return deposits[account];
}
/**
* Internal method to return just the stake info.
* @param addr - The account to query.
*/function_getStakeInfo(address addr
) internalviewreturns (StakeInfo memory info) {
DepositInfo storage depositInfo = deposits[addr];
info.stake = depositInfo.stake;
info.unstakeDelaySec = depositInfo.unstakeDelaySec;
}
/// @inheritdoc IStakeManagerfunctionbalanceOf(address account) publicviewreturns (uint256) {
return deposits[account].deposit;
}
receive() externalpayable{
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) internalreturns (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.
*/functiondepositTo(address account) publicvirtualpayable{
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.
*/functionaddStake(uint32 unstakeDelaySec) publicpayable{
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.
*/functionunlockStake() 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.
*/functionwithdrawStake(addresspayable 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.
*/functionwithdrawTo(addresspayable 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");
}
}
Contract Source Code
File 18 of 18: UserOperationLib.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity ^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.
*/libraryUserOperationLib{
uint256publicconstant PAYMASTER_VALIDATION_GAS_OFFSET =20;
uint256publicconstant PAYMASTER_POSTOP_GAS_OFFSET =36;
uint256publicconstant PAYMASTER_DATA_OFFSET =52;
/**
* Get sender from user operation data.
* @param userOp - The user operation data.
*/functiongetSender(
PackedUserOperation calldata userOp
) internalpurereturns (address) {
address data;
//read sender from userOp, which is first userOp member (saves 800 gas...)assembly {
data :=calldataload(userOp)
}
returnaddress(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.
*/functiongasPrice(
PackedUserOperation calldata userOp
) internalviewreturns (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.
*/functionencode(
PackedUserOperation calldata userOp
) internalpurereturns (bytesmemory ret) {
address sender = getSender(userOp);
uint256 nonce = userOp.nonce;
bytes32 hashInitCode = calldataKeccak(userOp.initCode);
bytes32 hashCallData = calldataKeccak(userOp.callData);
bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData);
returnabi.encode(
sender, nonce,
hashInitCode, hashCallData,
hashPaymasterAndData
);
}
functionunpackUints(bytes32 packed
) internalpurereturns (uint256 high128, uint256 low128) {
return (uint128(bytes16(packed)), uint128(uint256(packed)));
}
//unpack just the high 128-bits from a packed valuefunctionunpackHigh128(bytes32 packed) internalpurereturns (uint256) {
returnuint256(packed) >>128;
}
// unpack just the low 128-bits from a packed valuefunctionunpackLow128(bytes32 packed) internalpurereturns (uint256) {
returnuint128(uint256(packed));
}
functionunpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp)
internalpurereturns (uint256) {
return unpackHigh128(userOp.gasFees);
}
functionunpackMaxFeePerGas(PackedUserOperation calldata userOp)
internalpurereturns (uint256) {
return unpackLow128(userOp.gasFees);
}
functionunpackVerificationGasLimit(PackedUserOperation calldata userOp)
internalpurereturns (uint256) {
return unpackHigh128(userOp.accountGasLimits);
}
functionunpackCallGasLimit(PackedUserOperation calldata userOp)
internalpurereturns (uint256) {
return unpackLow128(userOp.accountGasLimits);
}
functionunpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp)
internalpurereturns (uint256) {
returnuint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET]));
}
functionunpackPostOpGasLimit(PackedUserOperation calldata userOp)
internalpurereturns (uint256) {
returnuint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET]));
}
functionunpackPaymasterStaticFields(bytescalldata paymasterAndData
) internalpurereturns (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.
*/functionhash(
PackedUserOperation calldata userOp
) internalpurereturns (bytes32) {
returnkeccak256(encode(userOp));
}
}