From e93160f6a3fad6a6cfc62ad5bb7753ba5705c755 Mon Sep 17 00:00:00 2001 From: Matt Masurka Date: Fri, 3 Mar 2023 16:20:49 -0700 Subject: [PATCH] Makes comments and formatting consistent --- contracts/core/BaseAccount.sol | 99 +-- contracts/core/BasePaymaster.sol | 96 +-- contracts/core/EntryPoint.sol | 839 +++++++++++++++++-------- contracts/core/Helpers.sol | 130 ++-- contracts/core/SenderCreator.sol | 28 +- contracts/core/StakeManager.sol | 74 ++- contracts/interfaces/IAccount.sol | 39 +- contracts/interfaces/IAggregator.sol | 37 +- contracts/interfaces/IEntryPoint.sol | 254 ++++---- contracts/interfaces/IPaymaster.sol | 72 ++- contracts/interfaces/IStakeManager.sol | 87 +-- contracts/interfaces/UserOperation.sol | 118 ++-- contracts/test/TestHelpers.sol | 20 +- 13 files changed, 1196 insertions(+), 697 deletions(-) diff --git a/contracts/core/BaseAccount.sol b/contracts/core/BaseAccount.sol index 6c06e9184..e124ea069 100644 --- a/contracts/core/BaseAccount.sol +++ b/contracts/core/BaseAccount.sol @@ -11,34 +11,45 @@ import "./Helpers.sol"; /** * Basic account implementation. - * this contract provides the basic logic for implementing the IAccount interface - validateUserOp + * this contract provides the basic logic for implementing the IAccount interface - validateUserOp * specific account implementation should inherit it and provide the account-specific logic */ abstract contract BaseAccount is IAccount { using UserOperationLib for UserOperation; - //return value in case of signature failure, with no time-range. - // equivalent to _packValidationData(true,0,0); - uint256 constant internal SIG_VALIDATION_FAILED = 1; + /** + * Return value in case of signature failure, with no time-range. + * Equivalent to _packValidationData(true,0,0). + */ + uint256 internal constant SIG_VALIDATION_FAILED = 1; /** - * return the account nonce. - * subclass should return a nonce value that is used both by _validateAndUpdateNonce, and by the external provider (to read the current nonce) + * Return the account nonce. + * subclass should return a nonce value that is used both by _validateAndUpdateNonce, + * and by the external provider (to read the current nonce) */ function nonce() public view virtual returns (uint256); /** - * return the entryPoint used by this account. - * subclass should return the current entryPoint used by this account. + * Return the entryPoint used by this account. + * Subclass should return the current entryPoint used by this account. */ function entryPoint() public view virtual returns (IEntryPoint); /** * Validate user's signature and nonce. - * subclass doesn't need to override this method. Instead, it should override the specific internal validation methods. + * Subclass doesn't need to override this method. Instead, + * it should override the specific internal validation methods. + * @param userOp - The user operation to validate. + * @param userOpHash - The hash of the user operation. + * @param missingAccountFunds - The amount of funds missing from the account + * to pay for the user operation. */ - function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) - external override virtual returns (uint256 validationData) { + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external virtual override returns (uint256 validationData) { _requireFromEntryPoint(); validationData = _validateSignature(userOp, userOpHash); if (userOp.initCode.length == 0) { @@ -48,47 +59,59 @@ abstract contract BaseAccount is IAccount { } /** - * ensure the request comes from the known entrypoint. + * Ensure the request comes from the known entrypoint. */ - function _requireFromEntryPoint() internal virtual view { - require(msg.sender == address(entryPoint()), "account: not from EntryPoint"); + function _requireFromEntryPoint() internal view virtual { + require( + msg.sender == address(entryPoint()), + "account: not from EntryPoint" + ); } /** - * validate the signature is valid for this message. - * @param userOp validate the userOp.signature field - * @param userOpHash convenient field: the hash of the request, to check the signature against - * (also hashes the entrypoint and chain id) - * @return validationData signature and time-range of this operation - * <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 the 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. + * Validate the signature is valid for this message. + * @param userOp - Validate the userOp.signature field. + * @param userOpHash - Convenient field: the hash of the request, to check the signature against. + * (also hashes the entrypoint and chain id) + * @return validationData - Signature and time-range of this operation. + * <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 the account doesn't use time-range, it is enough to return + * SIG_VALIDATION_FAILED value (1) for signature failure. + * Note that the validation code cannot use block.timestamp (or block.number) directly. */ - function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash) - internal virtual returns (uint256 validationData); + function _validateSignature( + UserOperation calldata userOp, + bytes32 userOpHash + ) internal virtual returns (uint256 validationData); /** - * validate the current nonce matches the UserOperation nonce. - * then it should update the account's state to prevent replay of this UserOperation. - * called only if initCode is empty (since "nonce" field is used as "salt" on account creation) - * @param userOp the op to validate. + * Validate the current nonce matches the UserOperation nonce, then update the + * account's state to prevent replay of this UserOperation. + * Called only if initCode is empty (since "nonce" field is used as "salt" on account creation) + * @param userOp - The op to validate. */ - function _validateAndUpdateNonce(UserOperation calldata userOp) internal virtual; + function _validateAndUpdateNonce( + UserOperation calldata userOp + ) internal virtual; /** - * sends to the entrypoint (msg.sender) the missing funds for this transaction. - * subclass MAY override this method for better funds management + * Sends to the entrypoint (msg.sender) the missing funds for this transaction. + * sSbclass MAY override this method for better funds management * (e.g. send to the entryPoint more than the minimum required, so that in future transactions - * it will not be required to send again) - * @param missingAccountFunds the minimum value this method should send the entrypoint. - * this value MAY be zero, in case there is enough deposit, or the userOp has a paymaster. + * it will not be required to send again). + * @param missingAccountFunds - The minimum value this method should send the entrypoint. + * This value MAY be zero, in case there is enough deposit, + * or the userOp has a paymaster. */ function _payPrefund(uint256 missingAccountFunds) internal virtual { if (missingAccountFunds != 0) { - (bool success,) = payable(msg.sender).call{value : missingAccountFunds, gas : type(uint256).max}(""); + (bool success, ) = payable(msg.sender).call{ + value: missingAccountFunds, + gas: type(uint256).max + }(""); (success); //ignore failure (its EntryPoint's job to verify, not account.) } diff --git a/contracts/core/BasePaymaster.sol b/contracts/core/BasePaymaster.sol index 1ae3c81c1..b6a4540d8 100644 --- a/contracts/core/BasePaymaster.sol +++ b/contracts/core/BasePaymaster.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.12; - /* solhint-disable reason-string */ import "@openzeppelin/contracts/access/Ownable.sol"; @@ -12,84 +11,107 @@ import "./Helpers.sol"; /** * Helper class for creating a paymaster. * provides helper methods for staking. - * validates that the postOp is called only by the entryPoint + * Validates that the postOp is called only by the entryPoint. */ abstract contract BasePaymaster is IPaymaster, Ownable { - - IEntryPoint immutable public entryPoint; + IEntryPoint public immutable entryPoint; constructor(IEntryPoint _entryPoint) { entryPoint = _entryPoint; } /// @inheritdoc IPaymaster - function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost) - external override returns (bytes memory context, uint256 validationData) { - _requireFromEntryPoint(); + function validatePaymasterUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external override returns (bytes memory context, uint256 validationData) { + _requireFromEntryPoint(); return _validatePaymasterUserOp(userOp, userOpHash, maxCost); } - function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost) - internal virtual returns (bytes memory context, uint256 validationData); + /** + * Validate a user operation. + * @param userOp - The user operation. + * @param userOpHash - The hash of the user operation. + * @param maxCost - The maximum cost of the user operation. + */ + function _validatePaymasterUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) internal virtual returns (bytes memory context, uint256 validationData); /// @inheritdoc IPaymaster - function postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) external override { + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost + ) external override { _requireFromEntryPoint(); _postOp(mode, context, actualGasCost); } /** - * post-operation handler. + * Post-operation handler. * (verified to be called only through the entryPoint) - * @dev if subclass returns a non-empty context from validatePaymasterUserOp, it must also implement this method. - * @param mode enum with the following options: - * opSucceeded - user operation succeeded. - * opReverted - user op reverted. still has to pay for gas. - * postOpReverted - user op succeeded, but caused postOp (in mode=opSucceeded) to revert. - * Now this is the 2nd call, after user's op was deliberately reverted. - * @param context - the context value returned by validatePaymasterUserOp - * @param actualGasCost - actual gas used so far (without this postOp call). + * @dev If subclass returns a non-empty context from validatePaymasterUserOp, + * it must also implement this method. + * @param mode - Enum with the following options: + * opSucceeded - User operation succeeded. + * opReverted - User op reverted. still has to pay for gas. + * postOpReverted - User op succeeded, but caused postOp (in mode=opSucceeded) to revert. + * Now this is the 2nd call, after user's op was deliberately reverted. + * @param context - The context value returned by validatePaymasterUserOp + * @param actualGasCost - Actual gas used so far (without this postOp call). */ - function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal virtual { - - (mode,context,actualGasCost); // unused params + function _postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost + ) internal virtual { + (mode, context, actualGasCost); // unused params // subclass must override this method if validatePaymasterUserOp returns a context revert("must override"); } /** - * add a deposit for this paymaster, used for paying for transaction fees + * Add a deposit for this paymaster, used for paying for transaction fees. */ function deposit() public payable { - entryPoint.depositTo{value : msg.value}(address(this)); + entryPoint.depositTo{value: msg.value}(address(this)); } /** - * withdraw value from the deposit - * @param withdrawAddress target to send to - * @param amount to withdraw + * Withdraw value from the deposit. + * @param withdrawAddress - Target to send to. + * @param amount - Amount to withdraw. */ - function withdrawTo(address payable withdrawAddress, uint256 amount) public onlyOwner { + function withdrawTo( + address payable withdrawAddress, + uint256 amount + ) public onlyOwner { entryPoint.withdrawTo(withdrawAddress, amount); } + /** - * add stake for this paymaster. + * Add stake for this paymaster. * This method can also carry eth value to add to the current stake. - * @param unstakeDelaySec - the unstake delay for this paymaster. Can only be increased. + * @param unstakeDelaySec - The unstake delay for this paymaster. Can only be increased. */ function addStake(uint32 unstakeDelaySec) external payable onlyOwner { - entryPoint.addStake{value : msg.value}(unstakeDelaySec); + entryPoint.addStake{value: msg.value}(unstakeDelaySec); } /** - * return current paymaster's deposit on the entryPoint. + * Return current paymaster's deposit on the entryPoint. */ function getDeposit() public view returns (uint256) { return entryPoint.balanceOf(address(this)); } /** - * unlock the stake, in order to withdraw it. + * Unlock the stake, in order to withdraw it. * The paymaster can't serve requests once unlocked, until it calls addStake again */ function unlockStake() external onlyOwner { @@ -97,15 +119,17 @@ abstract contract BasePaymaster is IPaymaster, Ownable { } /** - * withdraw the entire paymaster's stake. + * Withdraw the entire paymaster's stake. * stake must be unlocked first (and then wait for the unstakeDelay to be over) - * @param withdrawAddress the address to send withdrawn value. + * @param withdrawAddress - The address to send withdrawn value. */ function withdrawStake(address payable withdrawAddress) external onlyOwner { entryPoint.withdrawStake(withdrawAddress); } - /// validate the call is made from a valid entrypoint + /** + * Validate the call is made from a valid entrypoint + */ function _requireFromEntryPoint() internal virtual { require(msg.sender == address(entryPoint), "Sender not EntryPoint"); } diff --git a/contracts/core/EntryPoint.sol b/contracts/core/EntryPoint.sol index a25fa7d94..bed078ed6 100644 --- a/contracts/core/EntryPoint.sol +++ b/contracts/core/EntryPoint.sol @@ -1,7 +1,3 @@ -/** - ** Account-Abstraction (EIP-4337) singleton EntryPoint implementation. - ** Only one instance required on each chain. - **/ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.12; @@ -17,49 +13,58 @@ import "./StakeManager.sol"; import "./SenderCreator.sol"; import "./Helpers.sol"; +/* + * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + * Only one instance required on each chain. + */ contract EntryPoint is IEntryPoint, StakeManager { - using UserOperationLib for UserOperation; SenderCreator private immutable senderCreator = new SenderCreator(); - // internal value used during simulation: need to query aggregator. + // Internal value used during simulation: need to query aggregator. address private constant SIMULATE_FIND_AGGREGATOR = address(1); - // marker for inner call revert on out of gas - bytes32 private constant INNER_OUT_OF_GAS = hex'deaddead'; + // Marker for inner call revert on out of gas + bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead"; uint256 private constant REVERT_REASON_MAX_LEN = 2048; /** - * for simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value - * in case of signature failure, instead of revert. + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * must return this value in case of signature failure, instead of revert. */ uint256 public constant SIG_VALIDATION_FAILED = 1; /** - * 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. + * 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}(""); + (bool success, ) = beneficiary.call{value: amount}(""); require(success, "AA91 failed send to beneficiary"); } /** - * execute a user op - * @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. + * 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, UserOperation calldata userOp, UserOpInfo memory opInfo) private returns (uint256 collected) { + function _executeUserOp( + uint256 opIndex, + UserOperation calldata userOp, + UserOpInfo memory opInfo + ) private returns (uint256 collected) { uint256 preGas = gasleft(); bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset); - try this.innerHandleOp(userOp.callData, opInfo, context) returns (uint256 _actualGasCost) { + try this.innerHandleOp(userOp.callData, opInfo, context) returns ( + uint256 _actualGasCost + ) { collected = _actualGasCost; } catch { bytes32 innerRevertCode; @@ -75,50 +80,65 @@ contract EntryPoint is IEntryPoint, StakeManager { } uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; - collected = _handlePostOp(opIndex, IPaymaster.PostOpMode.postOpReverted, opInfo, context, actualGas); + collected = _handlePostOp( + opIndex, + IPaymaster.PostOpMode.postOpReverted, + opInfo, + context, + actualGas + ); } } /** * Execute a batch of UserOperations. - * no signature aggregator is used. - * if any account requires an aggregator (that is, it returned an aggregator when + * 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 + * @param ops - The operations to execute. + * @param beneficiary - The address to receive the fees. */ - function handleOps(UserOperation[] calldata ops, address payable beneficiary) public { - + function handleOps( + UserOperation[] calldata ops, + address payable beneficiary + ) public { 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)); - } + 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; + uint256 collected = 0; - for (uint256 i = 0; i < opslen; i++) { - collected += _executeUserOp(i, ops[i], opInfos[i]); - } + for (uint256 i = 0; i < opslen; i++) { + collected += _executeUserOp(i, ops[i], opInfos[i]); + } - _compensate(beneficiary, collected); - } //unchecked + _compensate(beneficiary, collected); + } } /** - * 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 + * Execute a batch of UserOperation with Aggregators. + * @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts). + * @param beneficiary - The address to receive the fees. */ function handleAggregatedOps( UserOpsPerAggregator[] calldata opsPerAggregator, address payable beneficiary ) public { - uint256 opasLen = opsPerAggregator.length; uint256 totalOps = 0; for (uint256 i = 0; i < opasLen; i++) { @@ -127,12 +147,14 @@ contract EntryPoint is IEntryPoint, StakeManager { IAggregator aggregator = opa.aggregator; //address(1) is special marker of "signature error" - require(address(aggregator) != address(1), "AA96 invalid aggregator"); + 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 { + try aggregator.validateSignatures(ops, opa.signature) {} catch { revert SignatureValidationFailed(address(aggregator)); } } @@ -151,8 +173,16 @@ contract EntryPoint is IEntryPoint, StakeManager { 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)); + ( + uint256 validationData, + uint256 paymasterValidationData + ) = _validatePrepayment(opIndex, ops[i], opInfo); + _validateAccountAndPaymasterValidationData( + i, + validationData, + paymasterValidationData, + address(aggregator) + ); opIndex++; } } @@ -176,12 +206,21 @@ contract EntryPoint is IEntryPoint, StakeManager { } /// @inheritdoc IEntryPoint - function simulateHandleOp(UserOperation calldata op, address target, bytes calldata targetCallData) external override { - + function simulateHandleOp( + UserOperation calldata op, + address target, + bytes calldata targetCallData + ) external override { UserOpInfo memory opInfo; _simulationOnlyValidations(op); - (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, op, opInfo); - ValidationData memory data = _intersectTimeRange(validationData, paymasterValidationData); + ( + uint256 validationData, + uint256 paymasterValidationData + ) = _validatePrepayment(0, op, opInfo); + ValidationData memory data = _intersectTimeRange( + validationData, + paymasterValidationData + ); numberMarker(); uint256 paid = _executeUserOp(0, op, opInfo); @@ -191,12 +230,20 @@ contract EntryPoint is IEntryPoint, StakeManager { if (target != address(0)) { (targetSuccess, targetResult) = target.call(targetCallData); } - revert ExecutionResult(opInfo.preOpGas, paid, data.validAfter, data.validUntil, targetSuccess, targetResult); + revert ExecutionResult( + opInfo.preOpGas, + paid, + data.validAfter, + data.validUntil, + targetSuccess, + targetResult + ); } - - // A memory copy of UserOp static fields only. - // Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster. + /** + * A memory copy of UserOp static fields only. + * Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster. + */ struct MemoryUserOp { address sender; uint256 nonce; @@ -217,24 +264,33 @@ contract EntryPoint is IEntryPoint, StakeManager { } /** - * inner function to handle a UserOperation. + * 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. */ - function innerHandleOp(bytes memory callData, UserOpInfo memory opInfo, bytes calldata context) external returns (uint256 actualGasCost) { + 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; uint callGasLimit = mUserOp.callGasLimit; - unchecked { - // handleOps was called with gas limit too low. abort entire bundle. - if (gasleft() < callGasLimit + mUserOp.verificationGasLimit + 5000) { - assembly { - mstore(0, INNER_OUT_OF_GAS) - revert(0, 32) + unchecked { + // handleOps was called with gas limit too low. abort entire bundle. + if ( + gasleft() < callGasLimit + mUserOp.verificationGasLimit + 5000 + ) { + assembly { + mstore(0, INNER_OUT_OF_GAS) + revert(0, 32) + } } } - } IPaymaster.PostOpMode mode = IPaymaster.PostOpMode.opSucceeded; if (callData.length > 0) { @@ -242,31 +298,45 @@ contract EntryPoint is IEntryPoint, StakeManager { if (!success) { bytes memory result = Exec.getReturnData(REVERT_REASON_MAX_LEN); if (result.length > 0) { - emit UserOperationRevertReason(opInfo.userOpHash, mUserOp.sender, mUserOp.nonce, result); + emit UserOperationRevertReason( + opInfo.userOpHash, + mUserOp.sender, + mUserOp.nonce, + result + ); } mode = IPaymaster.PostOpMode.opReverted; } } - unchecked { - uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; - //note: opIndex is ignored (relevant only if mode==postOpReverted, which is only possible outside of innerHandleOp) - return _handlePostOp(0, mode, opInfo, context, actualGas); - } + unchecked { + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + // Note: opIndex is ignored (relevant only if mode==postOpReverted, which is only possible outside of innerHandleOp) + return _handlePostOp(0, mode, opInfo, context, actualGas); + } } /** - * 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. + * 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. */ - function getUserOpHash(UserOperation calldata userOp) public view returns (bytes32) { - return keccak256(abi.encode(userOp.hash(), address(this), block.chainid)); + function getUserOpHash( + UserOperation 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. + * Copy general fields from userOp into the memory opInfo structure. + * @param userOp - The user operation. + * @param mUserOp - The memory user operation. */ - function _copyUserOpToMemory(UserOperation calldata userOp, MemoryUserOp memory mUserOp) internal pure { + function _copyUserOpToMemory( + UserOperation calldata userOp, + MemoryUserOp memory mUserOp + ) internal pure { mUserOp.sender = userOp.sender; mUserOp.nonce = userOp.nonce; mUserOp.callGasLimit = userOp.callGasLimit; @@ -276,8 +346,11 @@ contract EntryPoint is IEntryPoint, StakeManager { mUserOp.maxPriorityFeePerGas = userOp.maxPriorityFeePerGas; bytes calldata paymasterAndData = userOp.paymasterAndData; if (paymasterAndData.length > 0) { - require(paymasterAndData.length >= 20, "AA93 invalid paymasterAndData"); - mUserOp.paymaster = address(bytes20(paymasterAndData[: 20])); + require( + paymasterAndData.length >= 20, + "AA93 invalid paymasterAndData" + ); + mUserOp.paymaster = address(bytes20(paymasterAndData[:20])); } else { mUserOp.paymaster = address(0); } @@ -285,77 +358,143 @@ contract EntryPoint is IEntryPoint, StakeManager { /** * Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp. - * @dev this method always revert. Successful result is ValidationResult error. other errors are failures. - * @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage outside the account's data. - * @param userOp the user operation to validate. + * @dev This method always reverts. Successful result is ValidationResult error. other errors are failures. + * @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage + * outside the account's data. + * @param userOp - The user operation to validate. */ function simulateValidation(UserOperation calldata userOp) external { UserOpInfo memory outOpInfo; _simulationOnlyValidations(userOp); - (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, userOp, outOpInfo); - StakeInfo memory paymasterInfo = _getStakeInfo(outOpInfo.mUserOp.paymaster); + ( + uint256 validationData, + uint256 paymasterValidationData + ) = _validatePrepayment(0, userOp, outOpInfo); + StakeInfo memory paymasterInfo = _getStakeInfo( + outOpInfo.mUserOp.paymaster + ); StakeInfo memory senderInfo = _getStakeInfo(outOpInfo.mUserOp.sender); StakeInfo memory factoryInfo; { bytes calldata initCode = userOp.initCode; - address factory = initCode.length >= 20 ? address(bytes20(initCode[0 : 20])) : address(0); + address factory = initCode.length >= 20 + ? address(bytes20(initCode[0:20])) + : address(0); factoryInfo = _getStakeInfo(factory); } - ValidationData memory data = _intersectTimeRange(validationData, paymasterValidationData); + ValidationData memory data = _intersectTimeRange( + validationData, + paymasterValidationData + ); address aggregator = data.aggregator; bool sigFailed = aggregator == address(1); - ReturnInfo memory returnInfo = ReturnInfo(outOpInfo.preOpGas, outOpInfo.prefund, - sigFailed, data.validAfter, data.validUntil, getMemoryBytesFromOffset(outOpInfo.contextOffset)); + ReturnInfo memory returnInfo = ReturnInfo( + outOpInfo.preOpGas, + outOpInfo.prefund, + sigFailed, + data.validAfter, + data.validUntil, + getMemoryBytesFromOffset(outOpInfo.contextOffset) + ); if (aggregator != address(0) && aggregator != address(1)) { - AggregatorStakeInfo memory aggregatorInfo = AggregatorStakeInfo(aggregator, _getStakeInfo(aggregator)); - revert ValidationResultWithAggregation(returnInfo, senderInfo, factoryInfo, paymasterInfo, aggregatorInfo); + AggregatorStakeInfo memory aggregatorInfo = AggregatorStakeInfo( + aggregator, + _getStakeInfo(aggregator) + ); + revert ValidationResultWithAggregation( + returnInfo, + senderInfo, + factoryInfo, + paymasterInfo, + aggregatorInfo + ); } - revert ValidationResult(returnInfo, senderInfo, factoryInfo, paymasterInfo); - + revert ValidationResult( + returnInfo, + senderInfo, + factoryInfo, + paymasterInfo + ); } - function _getRequiredPrefund(MemoryUserOp memory mUserOp) internal pure returns (uint256 requiredPrefund) { - unchecked { - //when using a Paymaster, the verificationGasLimit is used also to as a limit for the postOp call. - // our security model might call postOp eventually twice - uint256 mul = mUserOp.paymaster != address(0) ? 3 : 1; - uint256 requiredGas = mUserOp.callGasLimit + mUserOp.verificationGasLimit * mul + mUserOp.preVerificationGas; - - requiredPrefund = requiredGas * mUserOp.maxFeePerGas; - } + /** + * 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 { + // When using a Paymaster, the verificationGasLimit is used also to as a limit for the postOp call. + // Our security model might call postOp eventually twice. + uint256 mul = mUserOp.paymaster != address(0) ? 3 : 1; + uint256 requiredGas = mUserOp.callGasLimit + + mUserOp.verificationGasLimit * + mul + + mUserOp.preVerificationGas; + + requiredPrefund = requiredGas * mUserOp.maxFeePerGas; + } } - // create the sender's contract if needed. - function _createSenderIfNeeded(uint256 opIndex, UserOpInfo memory opInfo, bytes calldata initCode) internal { + /** + * 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); + 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 + ); } } /** * 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. + * Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation. + * This method always revert, and returns the address in SenderAddressResult error. + * @param initCode - The constructor code to be passed into the UserOperation. */ function getSenderAddress(bytes calldata initCode) public { revert SenderAddressResult(senderCreator.createSender(initCode)); } - function _simulationOnlyValidations(UserOperation calldata userOp) internal view { + function _simulationOnlyValidations( + UserOperation calldata userOp + ) internal view { // solhint-disable-next-line no-empty-blocks - try this._validateSenderAndPaymaster(userOp.initCode, userOp.sender, userOp.paymasterAndData) {} - catch Error(string memory revertReason) { + try + this._validateSenderAndPaymaster( + userOp.initCode, + userOp.sender, + userOp.paymasterAndData + ) + {} catch Error(string memory revertReason) { if (bytes(revertReason).length != 0) { revert FailedOp(0, revertReason); } @@ -363,18 +502,25 @@ contract EntryPoint is IEntryPoint, StakeManager { } /** - * Called only during simulation. - * This function always reverts to prevent warm/cold storage differentiation in simulation vs execution. - */ - function _validateSenderAndPaymaster(bytes calldata initCode, address sender, bytes calldata paymasterAndData) external view { + * Called only during simulation. + * This function always reverts to prevent warm/cold storage differentiation in simulation vs execution. + * @param initCode - The smart account constructor code. + * @param sender - The sender address. + * @param paymasterAndData - The paymaster address followed by the token address to use. + */ + function _validateSenderAndPaymaster( + bytes calldata initCode, + address sender, + bytes calldata paymasterAndData + ) external view { if (initCode.length == 0 && sender.code.length == 0) { // it would revert anyway. but give a meaningful message revert("AA20 account not deployed"); } if (paymasterAndData.length >= 20) { - address paymaster = address(bytes20(paymasterAndData[0 : 20])); + address paymaster = address(bytes20(paymasterAndData[0:20])); if (paymaster.code.length == 0) { - // it would revert anyway. but give a meaningful message + // It would revert anyway. but give a meaningful message. revert("AA30 paymaster not deployed"); } } @@ -383,92 +529,150 @@ contract EntryPoint is IEntryPoint, StakeManager { } /** - * call account.validateUserOp. - * revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund. - * decrement account's deposit if needed + * 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, UserOperation calldata op, UserOpInfo memory opInfo, uint256 requiredPrefund) - internal returns (uint256 gasUsedByValidateAccountPrepayment, uint256 validationData) { - unchecked { - uint256 preGas = gasleft(); - MemoryUserOp memory mUserOp = opInfo.mUserOp; - address sender = mUserOp.sender; - _createSenderIfNeeded(opIndex, opInfo, op.initCode); - address paymaster = mUserOp.paymaster; - numberMarker(); - uint256 missingAccountFunds = 0; - if (paymaster == address(0)) { - uint256 bal = balanceOf(sender); - missingAccountFunds = bal > requiredPrefund ? 0 : requiredPrefund - bal; - } - try IAccount(sender).validateUserOp{gas : mUserOp.verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds) - returns (uint256 _validationData) { - validationData = _validationData; - } catch Error(string memory revertReason) { - revert FailedOp(opIndex, string.concat("AA23 reverted: ", revertReason)); - } catch { - revert FailedOp(opIndex, "AA23 reverted (or OOG)"); - } - if (paymaster == address(0)) { - DepositInfo storage senderInfo = deposits[sender]; - uint256 deposit = senderInfo.deposit; - if (requiredPrefund > deposit) { - revert FailedOp(opIndex, "AA21 didn't pay prefund"); + function _validateAccountPrepayment( + uint256 opIndex, + UserOperation calldata op, + UserOpInfo memory opInfo, + uint256 requiredPrefund + ) + internal + returns ( + uint256 gasUsedByValidateAccountPrepayment, + uint256 validationData + ) + { + unchecked { + uint256 preGas = gasleft(); + MemoryUserOp memory mUserOp = opInfo.mUserOp; + address sender = mUserOp.sender; + _createSenderIfNeeded(opIndex, opInfo, op.initCode); + address paymaster = mUserOp.paymaster; + numberMarker(); + uint256 missingAccountFunds = 0; + if (paymaster == address(0)) { + uint256 bal = balanceOf(sender); + missingAccountFunds = bal > requiredPrefund + ? 0 + : requiredPrefund - bal; + } + try + IAccount(sender).validateUserOp{ + gas: mUserOp.verificationGasLimit + }(op, opInfo.userOpHash, missingAccountFunds) + returns (uint256 _validationData) { + validationData = _validationData; + } catch Error(string memory revertReason) { + revert FailedOp( + opIndex, + string.concat("AA23 reverted: ", revertReason) + ); + } catch { + revert FailedOp(opIndex, "AA23 reverted (or OOG)"); + } + 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 = uint112(deposit - requiredPrefund); } - senderInfo.deposit = uint112(deposit - requiredPrefund); + gasUsedByValidateAccountPrepayment = preGas - gasleft(); } - gasUsedByValidateAccountPrepayment = preGas - gasleft(); - } } /** * 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 + * - 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. + * @param gasUsedByValidateAccountPrepayment - The gas used by _validateAccountPrepayment. */ - function _validatePaymasterPrepayment(uint256 opIndex, UserOperation calldata op, UserOpInfo memory opInfo, uint256 requiredPreFund, uint256 gasUsedByValidateAccountPrepayment) - internal returns (bytes memory context, uint256 validationData) { - unchecked { - MemoryUserOp memory mUserOp = opInfo.mUserOp; - uint256 verificationGasLimit = mUserOp.verificationGasLimit; - require(verificationGasLimit > gasUsedByValidateAccountPrepayment, "AA41 too little verificationGas"); - uint256 gas = verificationGasLimit - gasUsedByValidateAccountPrepayment; - - 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 = uint112(deposit - requiredPreFund); - try IPaymaster(paymaster).validatePaymasterUserOp{gas : gas}(op, opInfo.userOpHash, requiredPreFund) returns (bytes memory _context, uint256 _validationData){ - context = _context; - validationData = _validationData; - } catch Error(string memory revertReason) { - revert FailedOp(opIndex, string.concat("AA33 reverted: ", revertReason)); - } catch { - revert FailedOp(opIndex, "AA33 reverted (or OOG)"); + function _validatePaymasterPrepayment( + uint256 opIndex, + UserOperation calldata op, + UserOpInfo memory opInfo, + uint256 requiredPreFund, + uint256 gasUsedByValidateAccountPrepayment + ) internal returns (bytes memory context, uint256 validationData) { + unchecked { + MemoryUserOp memory mUserOp = opInfo.mUserOp; + uint256 verificationGasLimit = mUserOp.verificationGasLimit; + require( + verificationGasLimit > gasUsedByValidateAccountPrepayment, + "AA41 too little verificationGas" + ); + uint256 gas = verificationGasLimit - + gasUsedByValidateAccountPrepayment; + + 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 = uint112(deposit - requiredPreFund); + try + IPaymaster(paymaster).validatePaymasterUserOp{gas: gas}( + op, + opInfo.userOpHash, + requiredPreFund + ) + returns (bytes memory _context, uint256 _validationData) { + context = _context; + validationData = _validationData; + } catch Error(string memory revertReason) { + revert FailedOp( + opIndex, + string.concat("AA33 reverted: ", revertReason) + ); + } catch { + revert FailedOp(opIndex, "AA33 reverted (or OOG)"); + } } } - } /** - * revert if either account validationData or paymaster validationData is expired + * 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); + 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) + // 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); + (pmAggregator, outOfTimeRange) = _getValidationData( + paymasterValidationData + ); if (pmAggregator != address(0)) { revert FailedOp(opIndex, "AA34 signature error"); } @@ -477,144 +681,221 @@ contract EntryPoint is IEntryPoint, StakeManager { } } - function _getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { + /** + * Parse validationData into its components. + * @param validationData - The packed validation data (sigFailed, validAfter, validUntil). + */ + 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; + outOfTimeRange = + block.timestamp > data.validUntil || + block.timestamp < data.validAfter; aggregator = data.aggregator; } /** - * validate account and paymaster (if defined). - * 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 + * 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, UserOperation calldata userOp, UserOpInfo memory outOpInfo) - private returns (uint256 validationData, uint256 paymasterValidationData) { - + function _validatePrepayment( + uint256 opIndex, + UserOperation calldata userOp, + UserOpInfo memory outOpInfo + ) + private + 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 maxGasValues = mUserOp.preVerificationGas | mUserOp.verificationGasLimit | mUserOp.callGasLimit | - userOp.maxFeePerGas | userOp.maxPriorityFeePerGas; + // Validate all numeric values in userOp are well below 128 bit, so they can safely be added + // and multiplied without causing overflow. + uint256 maxGasValues = mUserOp.preVerificationGas | + mUserOp.verificationGasLimit | + mUserOp.callGasLimit | + userOp.maxFeePerGas | + userOp.maxPriorityFeePerGas; require(maxGasValues <= type(uint120).max, "AA94 gas values overflow"); uint256 gasUsedByValidateAccountPrepayment; - (uint256 requiredPreFund) = _getRequiredPrefund(mUserOp); - (gasUsedByValidateAccountPrepayment, validationData) = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund); - //a "marker" where account opcode validation is done and paymaster opcode validation is about to start - // (used only by off-chain simulateValidation) + uint256 requiredPreFund = _getRequiredPrefund(mUserOp); + ( + gasUsedByValidateAccountPrepayment, + validationData + ) = _validateAccountPrepayment( + opIndex, + userOp, + outOpInfo, + requiredPreFund + ); + // A "marker" where account opcode validation is done and paymaster opcode validation + // is about to start (used only by off-chain simulateValidation). numberMarker(); bytes memory context; if (mUserOp.paymaster != address(0)) { - (context, paymasterValidationData) = _validatePaymasterPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, gasUsedByValidateAccountPrepayment); + (context, paymasterValidationData) = _validatePaymasterPrepayment( + opIndex, + userOp, + outOpInfo, + requiredPreFund, + gasUsedByValidateAccountPrepayment + ); } - unchecked { - uint256 gasUsed = preGas - gasleft(); + unchecked { + uint256 gasUsed = preGas - gasleft(); - if (userOp.verificationGasLimit < gasUsed) { - revert FailedOp(opIndex, "AA40 over verificationGasLimit"); + if (userOp.verificationGasLimit < gasUsed) { + revert FailedOp(opIndex, "AA40 over verificationGasLimit"); + } + outOpInfo.prefund = requiredPreFund; + outOpInfo.contextOffset = getOffsetOfMemoryBytes(context); + outOpInfo.preOpGas = preGas - gasleft() + userOp.preVerificationGas; } - 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 opIndex index in the batch - * @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 + * 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 opIndex - Index in the batch. + * @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 _handlePostOp(uint256 opIndex, IPaymaster.PostOpMode mode, UserOpInfo memory opInfo, bytes memory context, uint256 actualGas) private returns (uint256 actualGasCost) { + function _handlePostOp( + uint256 opIndex, + 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) { - IPaymaster(paymaster).postOp{gas : mUserOp.verificationGasLimit}(mode, context, actualGasCost); - } else { - // solhint-disable-next-line no-empty-blocks - try IPaymaster(paymaster).postOp{gas : mUserOp.verificationGasLimit}(mode, context, actualGasCost) {} - catch Error(string memory reason) { - revert FailedOp(opIndex, string.concat("AA50 postOp reverted: ", reason)); - } - catch { - revert FailedOp(opIndex, "AA50 postOp revert"); + 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) { + IPaymaster(paymaster).postOp{ + gas: mUserOp.verificationGasLimit + }(mode, context, actualGasCost); + } else { + // solhint-disable-next-line no-empty-blocks + try + IPaymaster(paymaster).postOp{ + gas: mUserOp.verificationGasLimit + }(mode, context, actualGasCost) + {} catch Error(string memory reason) { + revert FailedOp( + opIndex, + string.concat("AA50 postOp reverted: ", reason) + ); + } catch { + revert FailedOp(opIndex, "AA50 postOp revert"); + } } } } - } - actualGas += preGas - gasleft(); - actualGasCost = actualGas * gasPrice; - if (opInfo.prefund < actualGasCost) { - revert FailedOp(opIndex, "AA51 prefund below actualGasCost"); - } - uint256 refund = opInfo.prefund - actualGasCost; - _incrementDeposit(refundAddress, refund); - bool success = mode == IPaymaster.PostOpMode.opSucceeded; - emit UserOperationEvent(opInfo.userOpHash, mUserOp.sender, mUserOp.paymaster, mUserOp.nonce, success, actualGasCost, actualGas); - } // unchecked + actualGas += preGas - gasleft(); + actualGasCost = actualGas * gasPrice; + if (opInfo.prefund < actualGasCost) { + revert FailedOp(opIndex, "AA51 prefund below actualGasCost"); + } + uint256 refund = opInfo.prefund - actualGasCost; + _incrementDeposit(refundAddress, refund); + bool success = mode == IPaymaster.PostOpMode.opSucceeded; + emit UserOperationEvent( + opInfo.userOpHash, + mUserOp.sender, + mUserOp.paymaster, + mUserOp.nonce, + 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 + * 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; + 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); } - return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); - } } + /** + * The minimum of two numbers. + * @param a - First number. + * @param b - Second number. + */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } - function getOffsetOfMemoryBytes(bytes memory data) internal pure returns (uint256 offset) { - assembly {offset := data} + /** + * 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 + } } - function getMemoryBytesFromOffset(uint256 offset) internal pure returns (bytes memory data) { - assembly {data := offset} + /** + * 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 { + data := offset + } } - //place the NUMBER opcode in the code. - // this is used as a marker during simulation, as this OP is completely banned from the simulated code of the - // account and paymaster. + /** + * Places the NUMBER opcode in the code. + * This is used as a marker during simulation, as this OP is completely banned from the simulated code of the + * account and paymaster. + */ function numberMarker() internal view { - assembly {mstore(0, number())} + assembly { + mstore(0, number()) + } } } - diff --git a/contracts/core/Helpers.sol b/contracts/core/Helpers.sol index d0bb0c919..11ad38d85 100644 --- a/contracts/core/Helpers.sol +++ b/contracts/core/Helpers.sol @@ -2,64 +2,94 @@ pragma solidity ^0.8.12; /** - * returned data from validateUserOp. - * validateUserOp returns a uint256, with 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. + * Returned data from validateUserOp. + * validateUserOp returns a uint256, with is created by `_packedValidationData` and + * parsed by `_parseValidationData`. + * @param aggregator - address(0) - The account validated the signature by itself. + * address(1) - The account failed to validate the signature. + * otherwise - this is an address of a signature aggregator that must + * be used to validate the signature. + * @param validAfter - This UserOp is valid only after this timestamp. + * @param validaUntil - This UserOp is valid only up to this timestamp. */ - struct ValidationData { - address aggregator; - uint48 validAfter; - uint48 validUntil; - } +struct ValidationData { + address aggregator; + uint48 validAfter; + uint48 validUntil; +} -//extract sigFailed, validAfter, validUntil. -// also convert zero validUntil to type(uint48).max - function _parseValidationData(uint validationData) pure returns (ValidationData memory data) { - address aggregator = address(uint160(validationData)); - uint48 validUntil = uint48(validationData >> 160); - if (validUntil == 0) { - validUntil = type(uint48).max; - } - uint48 validAfter = uint48(validationData >> (48 + 160)); - return ValidationData(aggregator, validAfter, validUntil); +/** + * Extract sigFailed, validAfter, validUntil. + * Also convert zero validUntil to type(uint48).max. + * @param validationData - The packed validation data. + */ +function _parseValidationData( + uint validationData +) pure returns (ValidationData memory data) { + address aggregator = address(uint160(validationData)); + uint48 validUntil = uint48(validationData >> 160); + if (validUntil == 0) { + validUntil = type(uint48).max; } + uint48 validAfter = uint48(validationData >> (48 + 160)); + return ValidationData(aggregator, validAfter, validUntil); +} -// intersect account and paymaster ranges. - function _intersectTimeRange(uint256 validationData, uint256 paymasterValidationData) pure returns (ValidationData memory) { - ValidationData memory accountValidationData = _parseValidationData(validationData); - ValidationData memory pmValidationData = _parseValidationData(paymasterValidationData); - address aggregator = accountValidationData.aggregator; - if (aggregator == address(0)) { - aggregator = pmValidationData.aggregator; - } - uint48 validAfter = accountValidationData.validAfter; - uint48 validUntil = accountValidationData.validUntil; - uint48 pmValidAfter = pmValidationData.validAfter; - uint48 pmValidUntil = pmValidationData.validUntil; - - if (validAfter < pmValidAfter) validAfter = pmValidAfter; - if (validUntil > pmValidUntil) validUntil = pmValidUntil; - return ValidationData(aggregator, validAfter, validUntil); +/** + * Intersect account and paymaster ranges. + * @param validationData - The packed validation data of the account. + * @param paymasterValidationData - The packed validation data of the paymaster. + */ +function _intersectTimeRange( + uint256 validationData, + uint256 paymasterValidationData +) pure returns (ValidationData memory) { + ValidationData memory accountValidationData = _parseValidationData( + validationData + ); + ValidationData memory pmValidationData = _parseValidationData( + paymasterValidationData + ); + address aggregator = accountValidationData.aggregator; + if (aggregator == address(0)) { + aggregator = pmValidationData.aggregator; } + uint48 validAfter = accountValidationData.validAfter; + uint48 validUntil = accountValidationData.validUntil; + uint48 pmValidAfter = pmValidationData.validAfter; + uint48 pmValidUntil = pmValidationData.validUntil; + + if (validAfter < pmValidAfter) validAfter = pmValidAfter; + if (validUntil > pmValidUntil) validUntil = pmValidUntil; + return ValidationData(aggregator, validAfter, validUntil); +} /** - * helper to pack the return value for validateUserOp - * @param data - the ValidationData to pack + * Helper to pack the return value for validateUserOp. + * @param data - The ValidationData to pack. */ - function _packValidationData(ValidationData memory data) pure returns (uint256) { - return uint160(data.aggregator) | (uint256(data.validUntil) << 160) | (uint256(data.validAfter) << (160 + 48)); - } +function _packValidationData( + ValidationData memory data +) pure returns (uint256) { + return + uint160(data.aggregator) | + (uint256(data.validUntil) << 160) | + (uint256(data.validAfter) << (160 + 48)); +} /** - * helper to pack the return value for validateUserOp, when not using an aggregator - * @param sigFailed - true for signature failure, false for success - * @param validUntil last timestamp this UserOperation is valid (or zero for infinite) - * @param validAfter first timestamp this UserOperation is valid + * Helper to pack the return value for validateUserOp, when not using an aggregator. + * @param sigFailed - True for signature failure, false for success. + * @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite). + * @param validAfter - First timestamp this UserOperation is valid. */ - function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) pure returns (uint256) { - return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48)); - } +function _packValidationData( + bool sigFailed, + uint48 validUntil, + uint48 validAfter +) pure returns (uint256) { + return + (sigFailed ? 1 : 0) | + (uint256(validUntil) << 160) | + (uint256(validAfter) << (160 + 48)); +} diff --git a/contracts/core/SenderCreator.sol b/contracts/core/SenderCreator.sol index 36fad7b91..0a49685f9 100644 --- a/contracts/core/SenderCreator.sol +++ b/contracts/core/SenderCreator.sol @@ -2,23 +2,33 @@ pragma solidity ^0.8.12; /** - * helper contract for EntryPoint, to call userOp.initCode from a "neutral" address, + * Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address, * which is explicitly not the entryPoint itself. */ contract SenderCreator { - /** - * call the "initCode" factory to create and return the sender account address - * @param initCode the initCode value from a UserOp. contains 20 bytes of factory address, followed by calldata - * @return sender the returned address of the created account, or zero address on failure. + * Call the "initCode" factory to create and return the sender account address. + * @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address, + * followed by calldata. + * @return sender - The returned address of the created account, or zero address on failure. */ - function createSender(bytes calldata initCode) external returns (address sender) { - address factory = address(bytes20(initCode[0 : 20])); - bytes memory initCallData = initCode[20 :]; + function createSender( + bytes calldata initCode + ) external returns (address sender) { + address factory = address(bytes20(initCode[0:20])); + bytes memory initCallData = initCode[20:]; bool success; /* solhint-disable no-inline-assembly */ assembly { - success := call(gas(), factory, 0, add(initCallData, 0x20), mload(initCallData), 0, 32) + success := call( + gas(), + factory, + 0, + add(initCallData, 0x20), + mload(initCallData), + 0, + 32 + ) sender := mload(0) } if (!success) { diff --git a/contracts/core/StakeManager.sol b/contracts/core/StakeManager.sol index e5ca2b97d..eada9e53d 100644 --- a/contracts/core/StakeManager.sol +++ b/contracts/core/StakeManager.sol @@ -5,29 +5,39 @@ 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. + * 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) { + function getDepositInfo( + address account + ) public view returns (DepositInfo memory info) { return deposits[account]; } - // internal method to return just the stake info - function _getStakeInfo(address addr) internal view returns (StakeInfo memory info) { + /** + * 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; } - /// return the deposit (for gas payment) of the account + /** + * Return the deposit (for gas payment) of the account. + * @param account - The account to query. + */ function balanceOf(address account) public view returns (uint256) { return deposits[account].deposit; } @@ -36,6 +46,11 @@ abstract contract StakeManager is IStakeManager { depositTo(msg.sender); } + /** + * Increments an account's deposit. + * @param account - The account to increment. + * @param amount - The amount to increment by. + */ function _incrementDeposit(address account, uint256 amount) internal { DepositInfo storage info = deposits[account]; uint256 newAmount = info.deposit + amount; @@ -44,7 +59,8 @@ abstract contract StakeManager is IStakeManager { } /** - * add to the deposit of the given account + * Add to the deposit of the given account. + * @param account - The account to add to. */ function depositTo(address account) public payable { _incrementDeposit(account, msg.value); @@ -53,14 +69,17 @@ abstract contract StakeManager is IStakeManager { } /** - * add to the account's stake - amount and delay + * 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. + * @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"); + 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"); @@ -75,8 +94,8 @@ abstract contract StakeManager is IStakeManager { } /** - * attempt to unlock the stake. - * the value can be withdrawn (using withdrawStake) after the unstake delay. + * 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]; @@ -88,37 +107,42 @@ abstract contract StakeManager is IStakeManager { 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. + * 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"); + 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}(""); + (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. + * 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 { + function withdrawTo( + address payable withdrawAddress, + uint256 withdrawAmount + ) external { DepositInfo storage info = deposits[msg.sender]; require(withdrawAmount <= info.deposit, "Withdraw amount too large"); info.deposit = uint112(info.deposit - withdrawAmount); emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount); - (bool success,) = withdrawAddress.call{value : withdrawAmount}(""); + (bool success, ) = withdrawAddress.call{value: withdrawAmount}(""); require(success, "failed to withdraw"); } } diff --git a/contracts/interfaces/IAccount.sol b/contracts/interfaces/IAccount.sol index 1600de3d7..6609e989b 100644 --- a/contracts/interfaces/IAccount.sol +++ b/contracts/interfaces/IAccount.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.12; import "./UserOperation.sol"; interface IAccount { - /** * Validate user's signature and nonce * the entryPoint will make the call to the recipient only if this validation call returns successfully. @@ -14,21 +13,27 @@ interface IAccount { * * @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. + * @param userOp - The operation that is about to be executed. + * @param userOpHash - Hash of the user's request data. can be used as the basis for signature. + * @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. + * This is the minimum amount to transfer to the sender(entryPoint) to be + * able to make the call. The excess is left as a deposit in the entrypoint + * for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". + * In case there is a paymaster in the request (or the current deposit is high + * enough), this value will be zero. + * @return validationData - Packaged ValidationData structure. use `_packValidationData` and + * `_unpackValidationData` to encode and decode. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * otherwise, an address of an "authorizer" contract. + * <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - First timestamp this operation is valid + * If an account doesn't use time-range, it is enough to + * return SIG_VALIDATION_FAILED value (1) for signature failure. + * Note that the validation code cannot use block.timestamp (or block.number) directly. */ - function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) - external returns (uint256 validationData); + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); } diff --git a/contracts/interfaces/IAggregator.sol b/contracts/interfaces/IAggregator.sol index 086c6f322..4226a9c18 100644 --- a/contracts/interfaces/IAggregator.sol +++ b/contracts/interfaces/IAggregator.sol @@ -7,30 +7,37 @@ import "./UserOperation.sol"; * Aggregated Signatures validator. */ interface IAggregator { - /** - * validate aggregated signature. - * revert if the aggregated signature does not match the given list of operations. + * Validate aggregated signature. + * Revert if the aggregated signature does not match the given list of operations. + * @param userOps - Array of UserOperations to validate the signature for. + * @param signature - The aggregated signature. */ - function validateSignatures(UserOperation[] calldata userOps, bytes calldata signature) external view; + function validateSignatures( + UserOperation[] calldata userOps, + bytes calldata signature + ) external view; /** - * validate signature of a single userOp + * Validate signature of a single userOp. * This method is should be called by bundler after EntryPoint.simulateValidation() returns (reverts) with ValidationResultWithAggregation * 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" + * @param userOp - The userOperation received from the user. + * @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps. + * (usually empty, unless account and aggregator support some kind of "multisig". */ - function validateUserOpSignature(UserOperation calldata userOp) - external view returns (bytes memory sigForUserOp); + function validateUserOpSignature( + UserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); /** - * aggregate multiple signatures into a single value. + * 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 + * bundler MAY use optimized custom code perform this aggregation. + * @param userOps - Array of UserOperations to collect the signatures from. + * @return aggregatedSignature - The aggregated signature. */ - function aggregateSignatures(UserOperation[] calldata userOps) external view returns (bytes memory aggregatedSignature); + function aggregateSignatures( + UserOperation[] calldata userOps + ) external view returns (bytes memory aggregatedSignature); } diff --git a/contracts/interfaces/IEntryPoint.sol b/contracts/interfaces/IEntryPoint.sol index c25288b38..ffecf6e88 100644 --- a/contracts/interfaces/IEntryPoint.sol +++ b/contracts/interfaces/IEntryPoint.sol @@ -14,99 +14,129 @@ import "./IStakeManager.sol"; import "./IAggregator.sol"; interface IEntryPoint is IStakeManager { - /*** - * An event emitted after each successful request - * @param userOpHash - unique identifier for the request (hash its entire content, except signature). - * @param sender - the account that generates this request. - * @param paymaster - if non-null, the paymaster that pays for this request. - * @param nonce - the nonce value from the request. - * @param success - true if the sender transaction succeeded, false if reverted. - * @param actualGasCost - actual amount paid (by account or paymaster) for this UserOperation. - * @param actualGasUsed - total gas used by this UserOperation (including preVerification, creation, validation and execution). - */ - event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed); - - /** - * account "sender" was deployed. - * @param userOpHash the userOp that deployed this account. UserOperationEvent will follow. - * @param sender the account that is deployed - * @param factory the factory used to deploy this account (in the initCode) - * @param paymaster the paymaster used by this UserOp - */ - event AccountDeployed(bytes32 indexed userOpHash, address indexed sender, address factory, address paymaster); - - /** - * An event emitted if the UserOperation "callData" reverted with non-zero length - * @param userOpHash the request unique identifier. - * @param sender the sender of this request - * @param nonce the nonce used in the request - * @param revertReason - the return bytes from the (reverted) call to "callData". - */ - event UserOperationRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason); - - /** - * signature aggregator used by the following UserOperationEvents within this bundle. + * An event emitted after each successful request. + * @param userOpHash - Unique identifier for the request (hash its entire content, except signature). + * @param sender - The account that generates this request. + * @param paymaster - If non-null, the paymaster that pays for this request. + * @param nonce - The nonce value from the request. + * @param success - True if the sender transaction succeeded, false if reverted. + * @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation. + * @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation, + * validation and execution). + */ + event UserOperationEvent( + bytes32 indexed userOpHash, + address indexed sender, + address indexed paymaster, + uint256 nonce, + bool success, + uint256 actualGasCost, + uint256 actualGasUsed + ); + + /** + * Account "sender" was deployed. + * @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow. + * @param sender - The account that is deployed + * @param factory - The factory used to deploy this account (in the initCode) + * @param paymaster - The paymaster used by this UserOp + */ + event AccountDeployed( + bytes32 indexed userOpHash, + address indexed sender, + address factory, + address paymaster + ); + + /** + * An event emitted if the UserOperation "callData" reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event UserOperationRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); + + /** + * Signature aggregator used by the following UserOperationEvents within this bundle. + * @param aggregator - The aggregator used for the following UserOperationEvents. */ event SignatureAggregatorChanged(address indexed aggregator); /** - * a custom revert error of handleOps, to identify the offending op. - * 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. - * 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. + * A custom revert error of handleOps, to identify the offending op. + * Should be caught in off-chain handleOps simulation and not happen on-chain. + * Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts. + * NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. The string starts with a unique code "AAmn", + * where "m" is "1" for factory, "2" for account and "3" for paymaster issues, + * so a failure can be attributed to the correct entity. */ error FailedOp(uint256 opIndex, string reason); /** - * error case when a signature aggregator fails to verify the aggregated signature it had created. + * Error case when a signature aggregator fails to verify the aggregated signature it had created. + * @param aggregator The aggregator that failed to verify the signature */ error SignatureValidationFailed(address aggregator); /** * Successful result from simulateValidation. - * @param returnInfo gas and time-range returned values - * @param senderInfo stake information about the sender - * @param factoryInfo stake information about the factory (if any) - * @param paymasterInfo stake information about the paymaster (if any) - */ - error ValidationResult(ReturnInfo returnInfo, - StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo); - - /** - * Successful result from simulateValidation, if the account returns a signature aggregator - * @param returnInfo gas and time-range returned values - * @param senderInfo stake information about the sender - * @param factoryInfo stake information about the factory (if any) - * @param paymasterInfo stake information about the paymaster (if any) - * @param aggregatorInfo signature aggregation info (if the account requires signature aggregator) - * bundler MUST use it to verify the signature, or reject the UserOperation - */ - error ValidationResultWithAggregation(ReturnInfo returnInfo, - StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo, - AggregatorStakeInfo aggregatorInfo); - - /** - * return value of getSenderAddress - */ + * @param returnInfo - Gas and time-range returned values. + * @param senderInfo - Stake information about the sender. + * @param factoryInfo - Stake information about the factory (if any). + * @param paymasterInfo - Stake information about the paymaster (if any). + */ + error ValidationResult( + ReturnInfo returnInfo, + StakeInfo senderInfo, + StakeInfo factoryInfo, + StakeInfo paymasterInfo + ); + + /** + * Successful result from simulateValidation, if the account returns a signature aggregator. + * @param returnInfo Gas and time-range returned values + * @param senderInfo Stake information about the sender + * @param factoryInfo Stake information about the factory (if any) + * @param paymasterInfo Stake information about the paymaster (if any) + * @param aggregatorInfo Signature aggregation info (if the account requires signature aggregator) + * Bundler MUST use it to verify the signature, or reject the UserOperation. + */ + error ValidationResultWithAggregation( + ReturnInfo returnInfo, + StakeInfo senderInfo, + StakeInfo factoryInfo, + StakeInfo paymasterInfo, + AggregatorStakeInfo aggregatorInfo + ); + + // Return value of getSenderAddress. error SenderAddressResult(address sender); - /** - * return value of simulateHandleOp - */ - error ExecutionResult(uint256 preOpGas, uint256 paid, uint48 validAfter, uint48 validUntil, bool targetSuccess, bytes targetResult); - - //UserOps handled, per aggregator + // Return value of simulateHandleOp. + error ExecutionResult( + uint256 preOpGas, + uint256 paid, + uint48 validAfter, + uint48 validUntil, + bool targetSuccess, + bytes targetResult + ); + + // UserOps handled, per aggregator. struct UserOpsPerAggregator { UserOperation[] userOps; - - // aggregator address + // Aggregator address IAggregator aggregator; - // aggregated signature + // Aggregated signature bytes signature; } @@ -115,15 +145,18 @@ interface IEntryPoint is IStakeManager { * 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 + * @param ops - The operations to execute. + * @param beneficiary - The address to receive the fees. */ - function handleOps(UserOperation[] calldata ops, address payable beneficiary) external; + function handleOps( + UserOperation[] calldata ops, + address payable beneficiary + ) external; /** * Execute a batch of UserOperation with Aggregators - * @param opsPerAggregator the operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts) - * @param beneficiary the address to receive the fees + * @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts). + * @param beneficiary - The address to receive the fees. */ function handleAggregatedOps( UserOpsPerAggregator[] calldata opsPerAggregator, @@ -131,27 +164,30 @@ interface IEntryPoint is IStakeManager { ) 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. + * 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. */ - function getUserOpHash(UserOperation calldata userOp) external view returns (bytes32); + function getUserOpHash( + UserOperation calldata userOp + ) external view returns (bytes32); /** * Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp. * @dev this method always revert. Successful result is ValidationResult error. other errors are failures. * @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage outside the account's data. - * @param userOp the user operation to validate. + * @param userOp - The user operation to validate. */ function simulateValidation(UserOperation calldata userOp) external; /** - * gas and return values during simulation - * @param preOpGas the gas used for validation (including preValidationGas) - * @param prefund the required prefund for this operation - * @param sigFailed validateUserOp's (or paymaster's) signature check failed - * @param validAfter - first timestamp this UserOp is valid (merging account and paymaster time-range) - * @param validUntil - last timestamp this UserOp is valid (merging account and paymaster time-range) - * @param paymasterContext returned by validatePaymasterUserOp (to be passed into postOp) + * Gas and return values during simulation. + * @param preOpGas - The gas used for validation (including preValidationGas) + * @param prefund - The required prefund for this operation + * @param sigFailed - ValidateUserOp's (or paymaster's) signature check failed + * @param validAfter - First timestamp this UserOp is valid (merging account and paymaster time-range) + * @param validUntil - Last timestamp this UserOp is valid (merging account and paymaster time-range) + * @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp) */ struct ReturnInfo { uint256 preOpGas; @@ -163,8 +199,8 @@ interface IEntryPoint is IStakeManager { } /** - * returned aggregated signature info. - * the aggregator returned by the account, and its current stake. + * Returned aggregated signature info: + * The aggregator returned by the account, and its current stake. */ struct AggregatorStakeInfo { address aggregator; @@ -173,26 +209,28 @@ interface IEntryPoint is IStakeManager { /** * 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. + * Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation. + * This method always revert, and returns the address in SenderAddressResult error + * @param initCode - The constructor code to be passed into the UserOperation. */ function getSenderAddress(bytes memory initCode) external; - /** - * simulate full execution of a UserOperation (including both validation and target execution) - * this method will always revert with "ExecutionResult". - * it performs full validation of the UserOperation, but ignores signature error. - * an optional target address is called after the userop succeeds, and its value is returned - * (before the entire call is reverted) + * Simulate full execution of a UserOperation (including both validation and target execution) + * This method will always revert with "ExecutionResult". + * It performs full validation of the UserOperation, but ignores signature error. + * An optional target address is called after the userop succeeds, + * and its value is returned (before the entire call is reverted). * Note that in order to collect the the success/failure of the target call, it must be executed * with trace enabled to track the emitted events. - * @param op the UserOperation to simulate - * @param target if nonzero, a target address to call after userop simulation. If called, the targetSuccess and targetResult - * are set to the return from that call. - * @param targetCallData callData to pass to target address - */ - function simulateHandleOp(UserOperation calldata op, address target, bytes calldata targetCallData) external; + * @param op The UserOperation to simulate. + * @param target - If nonzero, a target address to call after userop simulation. If called, + * the targetSuccess and targetResult are set to the return from that call. + * @param targetCallData - CallData to pass to target address. + */ + function simulateHandleOp( + UserOperation calldata op, + address target, + bytes calldata targetCallData + ) external; } - diff --git a/contracts/interfaces/IPaymaster.sol b/contracts/interfaces/IPaymaster.sol index af50367ac..51bff2174 100644 --- a/contracts/interfaces/IPaymaster.sol +++ b/contracts/interfaces/IPaymaster.sol @@ -4,48 +4,58 @@ pragma solidity ^0.8.12; import "./UserOperation.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. + * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. + * A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. */ interface IPaymaster { - enum PostOpMode { - opSucceeded, // user op succeeded - opReverted, // user op reverted. still has to pay for gas. - postOpReverted //user op succeeded, but caused postOp to revert. Now it's a 2nd call, after user's op was deliberately reverted. + // User op succeeded. + opSucceeded, + // User op reverted. Still has to pay for gas. + opReverted, + // User op succeeded, but caused postOp to revert. + // Now it's a 2nd call, after user's op was deliberately reverted. + postOpReverted } /** - * payment validation: check if paymaster agrees to pay. + * 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) + * 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, - * 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 - * Note that the validation code cannot use block.timestamp (or block.number) directly. + * @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, + * 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 + * Note that the validation code cannot use block.timestamp (or block.number) directly. */ - function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost) - external returns (bytes memory context, uint256 validationData); + function validatePaymasterUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); /** - * post-operation handler. - * Must verify sender is the entryPoint - * @param mode enum with the following options: - * opSucceeded - user operation succeeded. - * opReverted - user op reverted. still has to pay for gas. - * postOpReverted - user op succeeded, but caused postOp (in mode=opSucceeded) to revert. - * Now this is the 2nd call, after user's op was deliberately reverted. - * @param context - the context value returned by validatePaymasterUserOp - * @param actualGasCost - actual gas used so far (without this postOp call). + * Post-operation handler. + * Must verify sender is the entryPoint. + * @param mode - Enum with the following options: + * opSucceeded - User operation succeeded. + * opReverted - User op reverted. still has to pay for gas. + * postOpReverted - User op succeeded, but caused postOp (in mode=opSucceeded) to revert. + * Now this is the 2nd call, after user's op was deliberately reverted. + * @param context - The context value returned by validatePaymasterUserOp + * @param actualGasCost - Actual gas used so far (without this postOp call). */ - function postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) external; + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost + ) external; } diff --git a/contracts/interfaces/IStakeManager.sol b/contracts/interfaces/IStakeManager.sol index c19c1bab8..9cb7429fa 100644 --- a/contracts/interfaces/IStakeManager.sol +++ b/contracts/interfaces/IStakeManager.sol @@ -2,16 +2,12 @@ pragma solidity ^0.8.12; /** - * 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. + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by the staked entity. */ interface IStakeManager { - - event Deposited( - address indexed account, - uint256 totalDeposit - ); + event Deposited(address indexed account, uint256 totalDeposit); event Withdrawn( address indexed account, @@ -19,18 +15,15 @@ interface IStakeManager { uint256 amount ); - /// Emitted when stake or unstake delay are modified + // Emitted when stake or unstake delay are modified. event StakeLocked( address indexed account, uint256 totalStaked, uint256 unstakeDelaySec ); - /// Emitted once a stake is scheduled for withdrawal - event StakeUnlocked( - address indexed account, - uint256 withdrawTime - ); + // Emitted once a stake is scheduled for withdrawal. + event StakeUnlocked(address indexed account, uint256 withdrawTime); event StakeWithdrawn( address indexed account, @@ -39,16 +32,16 @@ interface IStakeManager { ); /** - * @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,staked, stake) fit into one cell (used during handleOps) - * and the rest fit into a 2nd cell. - * 112 bit allows for 10^15 eth - * 48 bit for full timestamp - * 32 bit allows 150 years for unstake delay + * @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,staked, stake) fit into one cell (used during handleOps) + * and the rest fit into a 2nd cell. + * - 112 bit allows for 10^15 eth + * - 48 bit for full timestamp + * - 32 bit allows 150 years for unstake delay */ struct DepositInfo { uint112 deposit; @@ -58,47 +51,61 @@ interface IStakeManager { uint48 withdrawTime; } - //API struct used by getStakeInfo and simulateValidation + // API struct used by getStakeInfo and simulateValidation. struct StakeInfo { uint256 stake; uint256 unstakeDelaySec; } - /// @return info - full deposit information of given account - function getDepositInfo(address account) external view returns (DepositInfo memory info); + /** + * Get deposit info. + * @param account - The account to query. + * @return info - Full deposit information of given account. + */ + function getDepositInfo( + address account + ) external view returns (DepositInfo memory info); - /// @return the deposit (for gas payment) of the account + /** + * Get account balance. + * @param account - The account to query. + * @return - The deposit (for gas payment) of the account. + */ function balanceOf(address account) external view returns (uint256); /** - * add to the deposit of the given account + * Add to the deposit of the given account. + * @param account - The account to add to. */ function depositTo(address account) external payable; /** - * add to the account's stake - amount and delay + * 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. + * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. */ function addStake(uint32 _unstakeDelaySec) external payable; /** - * attempt to unlock the stake. - * the value can be withdrawn (using withdrawStake) after the unstake delay. + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. */ function unlockStake() external; /** - * withdraw from the (unlocked) stake. - * must first call unlockStake and wait for the unstakeDelay to pass - * @param withdrawAddress the address to send withdrawn value. + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. */ function withdrawStake(address payable withdrawAddress) external; /** - * withdraw from the deposit. - * @param withdrawAddress the address to send withdrawn value. - * @param withdrawAmount the amount to withdraw. + * 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; + function withdrawTo( + address payable withdrawAddress, + uint256 withdrawAmount + ) external; } diff --git a/contracts/interfaces/UserOperation.sol b/contracts/interfaces/UserOperation.sol index dfff42791..0d5a6c9fd 100644 --- a/contracts/interfaces/UserOperation.sol +++ b/contracts/interfaces/UserOperation.sol @@ -3,62 +3,81 @@ pragma solidity ^0.8.12; /* solhint-disable no-inline-assembly */ - /** - * 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 callGasLimit the gas limit passed to the callData method call. - * @param verificationGasLimit gas used for validateUserOp and validatePaymasterUserOp. - * @param preVerificationGas gas not calculated by the handleOps method, but added to the gas paid. Covers batch overhead. - * @param maxFeePerGas same as EIP-1559 gas parameter. - * @param maxPriorityFeePerGas same as EIP-1559 gas parameter. - * @param paymasterAndData if set, this field holds the paymaster address and paymaster-specific data. the paymaster will pay for the transaction instead of the sender. - * @param signature sender-verified signature over the entire request, the EntryPoint address and the chain ID. - */ - struct UserOperation { - - address sender; - uint256 nonce; - bytes initCode; - bytes callData; - uint256 callGasLimit; - uint256 verificationGasLimit; - uint256 preVerificationGas; - uint256 maxFeePerGas; - uint256 maxPriorityFeePerGas; - bytes paymasterAndData; - bytes signature; - } +/** + * 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 callGasLimit - The gas limit passed to the callData method call. + * @param verificationGasLimit - Gas used for validateUserOp and validatePaymasterUserOp. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param maxFeePerGas - Same as EIP-1559 gas parameter. + * @param maxPriorityFeePerGas - Same as EIP-1559 gas parameter. + * @param paymasterAndData - If set, this field holds the paymaster address and paymaster-specific data. + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ +struct UserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + uint256 callGasLimit; + uint256 verificationGasLimit; + uint256 preVerificationGas; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + bytes paymasterAndData; + bytes signature; +} /** * Utility functions helpful when working with UserOperation structs. */ library UserOperationLib { - - function getSender(UserOperation calldata userOp) internal pure returns (address) { + /** + * Get sender from user operation data. + * @param userOp - The user operation data. + */ + function getSender( + UserOperation calldata userOp + ) internal pure returns (address) { address data; //read sender from userOp, which is first userOp member (saves 800 gas...) - assembly {data := calldataload(userOp)} + 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. - function gasPrice(UserOperation calldata userOp) internal view returns (uint256) { - unchecked { - uint256 maxFeePerGas = userOp.maxFeePerGas; - uint256 maxPriorityFeePerGas = userOp.maxPriorityFeePerGas; - if (maxFeePerGas == maxPriorityFeePerGas) { - //legacy mode (for networks that don't support basefee opcode) - return maxFeePerGas; + /** + * 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( + UserOperation calldata userOp + ) internal view returns (uint256) { + unchecked { + uint256 maxFeePerGas = userOp.maxFeePerGas; + uint256 maxPriorityFeePerGas = userOp.maxPriorityFeePerGas; + if (maxFeePerGas == maxPriorityFeePerGas) { + //legacy mode (for networks that don't support basefee opcode) + return maxFeePerGas; + } + return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); } - return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); - } } - function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) { + /** + * Pack the user operation data into bytes for hashing. + * @param userOp - The user operation data. + */ + function pack( + UserOperation calldata userOp + ) internal pure returns (bytes memory ret) { //lighter signature scheme. must match UserOp.ts#packUserOp bytes calldata sig = userOp.signature; // copy directly the userOp from calldata up to (but not including) the signature. @@ -74,10 +93,21 @@ library UserOperationLib { } } - function hash(UserOperation calldata userOp) internal pure returns (bytes32) { + /** + * Hash the user operation data. + * @param userOp - The user operation data. + */ + function hash( + UserOperation calldata userOp + ) internal pure returns (bytes32) { return keccak256(pack(userOp)); } + /** + * The minimum of two numbers. + * @param a - First number. + * @param b - Second number. + */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } diff --git a/contracts/test/TestHelpers.sol b/contracts/test/TestHelpers.sol index 2ddb83c77..f2a35db60 100644 --- a/contracts/test/TestHelpers.sol +++ b/contracts/test/TestHelpers.sol @@ -4,20 +4,30 @@ pragma solidity ^0.8.12; import "../core/Helpers.sol"; contract TestHelpers { - - function parseValidationData(uint validationData) public pure returns (ValidationData memory) { + function parseValidationData( + uint validationData + ) public pure returns (ValidationData memory) { return _parseValidationData(validationData); } - function intersectTimeRange(uint256 validationData, uint256 paymasterValidationData) public pure returns (ValidationData memory) { + function intersectTimeRange( + uint256 validationData, + uint256 paymasterValidationData + ) public pure returns (ValidationData memory) { return _intersectTimeRange(validationData, paymasterValidationData); } - function packValidationDataStruct(ValidationData memory data) public pure returns (uint256) { + function packValidationDataStruct( + ValidationData memory data + ) public pure returns (uint256) { return _packValidationData(data); } - function packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) public pure returns (uint256) { + function packValidationData( + bool sigFailed, + uint48 validUntil, + uint48 validAfter + ) public pure returns (uint256) { return _packValidationData(sigFailed, validUntil, validAfter); } }