UNPKG

@owlprotocol/contracts-account-abstraction

Version:

ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.

1,220 lines (1,120 loc) 98.1 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var PimlicoEntryPointSimulations_exports = {}; __export(PimlicoEntryPointSimulations_exports, { PimlicoEntryPointSimulations: () => PimlicoEntryPointSimulations }); module.exports = __toCommonJS(PimlicoEntryPointSimulations_exports); const PimlicoEntryPointSimulations = { compiler: { version: "0.8.23+commit.f704f362" }, language: "Solidity", output: { abi: [ { inputs: [ { internalType: "contract EntryPointSimulations", name: "_eps", type: "address" } ], stateMutability: "nonpayable", type: "constructor" }, { inputs: [ { internalType: "address payable", name: "ep", type: "address" }, { internalType: "bytes[]", name: "data", type: "bytes[]" } ], name: "simulateEntryPoint", outputs: [ { internalType: "bytes[]", name: "", type: "bytes[]" } ], stateMutability: "nonpayable", type: "function" } ], devdoc: { kind: "dev", methods: {}, version: 1 }, userdoc: { kind: "user", methods: {}, version: 1 } }, settings: { compilationTarget: { "contracts/PimlicoEntryPointSimulations/PimlicoEntryPointSimulations.sol": "PimlicoEntryPointSimulations" }, evmVersion: "paris", libraries: {}, metadata: { bytecodeHash: "ipfs", useLiteralContent: true }, optimizer: { enabled: true, runs: 1e6 }, remappings: [], viaIR: true }, sources: { "@account-abstraction/contracts/core/EntryPoint.sol": { content: `// 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 "@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 { 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) { bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit); 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"); 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 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; } 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 ) 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); } } `, keccak256: "0x0eb1245b5541ff0fbc0f2a23c746e2b4eb9c46e801a4847f15d7d96d1cecc576", license: "GPL-3.0" }, "@account-abstraction/contracts/core/Helpers.sol": { content: '// SPDX-License-Identifier: GPL-3.0\npragma solidity ^0.8.23;\n\n/* solhint-disable no-inline-assembly */\n\n\n /*\n * For simulation purposes, validateUserOp (and validatePaymasterUserOp)\n * must return this value in case of signature failure, instead of revert.\n */\nuint256 constant SIG_VALIDATION_FAILED = 1;\n\n\n/*\n * For simulation purposes, validateUserOp (and validatePaymasterUserOp)\n * return this value on success.\n */\nuint256 constant SIG_VALIDATION_SUCCESS = 0;\n\n\n/**\n * Returned data from validateUserOp.\n * validateUserOp returns a uint256, which is created by `_packedValidationData` and\n * parsed by `_parseValidationData`.\n * @param aggregator - address(0) - The account validated the signature by itself.\n * address(1) - The account failed to validate the signature.\n * otherwise - This is an address of a signature aggregator that must\n * be used to validate the signature.\n * @param validAfter - This UserOp is valid only after this timestamp.\n * @param validaUntil - This UserOp is valid only up to this timestamp.\n */\nstruct ValidationData {\n address aggregator;\n uint48 validAfter;\n uint48 validUntil;\n}\n\n/**\n * Extract sigFailed, validAfter, validUntil.\n * Also convert zero validUntil to type(uint48).max.\n * @param validationData - The packed validation data.\n */\nfunction _parseValidationData(\n uint256 validationData\n) pure returns (ValidationData memory data) {\n address aggregator = address(uint160(validationData));\n uint48 validUntil = uint48(validationData >> 160);\n if (validUntil == 0) {\n validUntil = type(uint48).max;\n }\n uint48 validAfter = uint48(validationData >> (48 + 160));\n return ValidationData(aggregator, validAfter, validUntil);\n}\n\n/**\n * Helper to pack the return value for validateUserOp.\n * @param data - The ValidationData to pack.\n */\nfunction _packValidationData(\n ValidationData memory data\n) pure returns (uint256) {\n return\n uint160(data.aggregator) |\n (uint256(data.validUntil) << 160) |\n (uint256(data.validAfter) << (160 + 48));\n}\n\n/**\n * Helper to pack the return value for validateUserOp, when not using an aggregator.\n * @param sigFailed - True for signature failure, false for success.\n * @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite).\n * @param validAfter - First timestamp this UserOperation is valid.\n */\nfunction _packValidationData(\n bool sigFailed,\n uint48 validUntil,\n uint48 validAfter\n) pure returns (uint256) {\n return\n (sigFailed ? 1 : 0) |\n (uint256(validUntil) << 160) |\n (uint256(validAfter) << (160 + 48));\n}\n\n/**\n * keccak function over calldata.\n * @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it.\n */\n function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) {\n assembly ("memory-safe") {\n let mem := mload(0x40)\n let len := data.length\n calldatacopy(mem, data.offset, len)\n ret := keccak256(mem, len)\n }\n }\n\n\n/**\n * The minimum of two numbers.\n * @param a - First number.\n * @param b - Second number.\n */\n function min(uint256 a, uint256 b) pure returns (uint256) {\n return a < b ? a : b;\n }\n', keccak256: "0x6247e011a6cb0b263b3aa098822977181674d91b62e5bdfe04c6e66f72da25d6", license: "GPL-3.0" }, "@account-abstraction/contracts/core/NonceManager.sol": { content: `// 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; } } `, keccak256: "0x1f951786ce6f171e7ed0242fee73ee4a205c7523404ee6cffca48b8c64ea5fe9", license: "GPL-3.0" }, "@account-abstraction/contracts/core/SenderCreator.sol": { content: '// SPDX-License-Identifier: GPL-3.0\npragma solidity ^0.8.23;\n\n/**\n * Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address,\n * which is explicitly not the entryPoint itself.\n */\ncontract SenderCreator {\n /**\n * Call the "initCode" factory to create and return the sender account address.\n * @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address,\n * followed by calldata.\n * @return sender - The returned address of the created account, or zero address on failure.\n */\n function createSender(\n bytes calldata initCode\n ) external returns (address sender) {\n address factory = address(bytes20(initCode[0:20]));\n bytes memory initCallData = initCode[20:];\n bool success;\n /* solhint-disable no-inline-assembly */\n assembly ("memory-safe") {\n success := call(\n gas(),\n factory,\n 0,\n add(initCallData, 0x20),\n mload(initCallData),\n 0,\n 32\n )\n sender := mload(0)\n }\n if (!success) {\n sender = address(0);\n }\n }\n}\n', keccak256: "0xeb95afb6e4cf921c1ed105ecb9f549ca46bee57f68acd1d2f4f84607ac0db5c5", license: "GPL-3.0" }, "@account-abstraction/contracts/core/StakeManager.sol": { content: `// 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"); } } `, keccak256: "0x673eb19600058d8642605ca409c9e1d4cab13735564b856270b92c330ffb1b8d", license: "GPL-3.0-only" }, "@account-abstraction/contracts/core/UserOperationLib.sol": { content: `// 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