From 6d4cbbfc5fb82746ed8bf30b3965a6f964f53ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:10:25 -0600 Subject: [PATCH 01/41] Add ERC4337 and ERC7579 utilities --- contracts/account/utils/draft-ERC433Utils.sol | 259 +++++++ .../account/utils/draft-ERC7579Utils.sol | 280 +++++++ contracts/interfaces/draft-IERC4337.sol | 268 +++++++ contracts/interfaces/draft-IERC7579.sol | 196 +++++ contracts/utils/Packing.sol | 513 ++++++++++++ .../erc4337-entrypoint/core/EntryPoint.sol | 727 ++++++++++++++++++ .../erc4337-entrypoint/core/Helpers.sol | 88 +++ .../erc4337-entrypoint/core/NonceManager.sol | 39 + .../erc4337-entrypoint/core/SenderCreator.sol | 28 + .../erc4337-entrypoint/core/StakeManager.sol | 126 +++ .../core/UserOperationLib.sol | 127 +++ .../interfaces/IAccount.sol | 39 + .../interfaces/IAccountExecute.sol | 17 + .../interfaces/IAggregator.sol | 41 + .../interfaces/IEntryPoint.sol | 204 +++++ .../interfaces/INonceManager.sol | 25 + .../interfaces/IPaymaster.sol | 63 ++ .../interfaces/IStakeManager.sol | 94 +++ .../interfaces/PackedUserOperation.sol | 28 + .../vendor/erc4337-entrypoint/utils/Exec.sol | 56 ++ scripts/generate/templates/Packing.opts.js | 2 +- scripts/generate/templates/Packing.t.js | 4 +- test/utils/Packing.t.sol | 492 +++++++++--- 23 files changed, 3623 insertions(+), 93 deletions(-) create mode 100644 contracts/account/utils/draft-ERC433Utils.sol create mode 100644 contracts/account/utils/draft-ERC7579Utils.sol create mode 100644 contracts/interfaces/draft-IERC4337.sol create mode 100644 contracts/interfaces/draft-IERC7579.sol create mode 100644 contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol create mode 100644 contracts/vendor/erc4337-entrypoint/core/Helpers.sol create mode 100644 contracts/vendor/erc4337-entrypoint/core/NonceManager.sol create mode 100644 contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol create mode 100644 contracts/vendor/erc4337-entrypoint/core/StakeManager.sol create mode 100644 contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol create mode 100644 contracts/vendor/erc4337-entrypoint/interfaces/PackedUserOperation.sol create mode 100644 contracts/vendor/erc4337-entrypoint/utils/Exec.sol diff --git a/contracts/account/utils/draft-ERC433Utils.sol b/contracts/account/utils/draft-ERC433Utils.sol new file mode 100644 index 00000000000..fddd7789715 --- /dev/null +++ b/contracts/account/utils/draft-ERC433Utils.sol @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Packing} from "../../utils/Packing.sol"; + +/** + * @dev Library with common ERC-4337 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337]. + */ +library ERC4337Utils { + using Packing for *; + + /** + * @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * return this value on success. + */ + uint256 internal constant SIG_VALIDATION_SUCCESS = 0; + + /** + * @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * must return this value in case of signature failure, instead of revert. + */ + uint256 internal constant SIG_VALIDATION_FAILED = 1; + + /** + * @dev Parses the validation data into its components. See {packValidationData}. + */ + function parseValidationData( + uint256 validationData + ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { + validAfter = uint48(bytes32(validationData).extract_32_6(0x00)); + validUntil = uint48(bytes32(validationData).extract_32_6(0x06)); + aggregator = address(bytes32(validationData).extract_32_20(0x0c)); + if (validUntil == 0) validUntil = type(uint48).max; + } + + /** + * @dev Packs the validation data into a single uint256. See {parseValidationData}. + */ + function packValidationData( + address aggregator, + uint48 validAfter, + uint48 validUntil + ) internal pure returns (uint256) { + return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); + } + + /** + * @dev Same as {packValidationData}, but with a boolean signature success flag. + */ + function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { + return + uint256( + bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20( + bytes20(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))) + ) + ); + } + + /** + * @dev Combines two validation data into a single one. + * + * The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while + * the `validAfter` is the maximum and the `validUntil` is the minimum of both. + */ + function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) { + (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1); + (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2); + + bool success = aggregator1 == address(0) && aggregator2 == address(0); + uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); + uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); + return packValidationData(success, validAfter, validUntil); + } + + /** + * @dev Returns the aggregator of the `validationData` and whether it is out of time range. + */ + function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { + if (validationData == 0) { + return (address(0), false); + } else { + (address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData); + return (aggregator_, block.timestamp > validUntil || block.timestamp < validAfter); + } + } + + /** + * @dev Computes the hash of a user operation with the current entrypoint and chainid. + */ + function hash(PackedUserOperation calldata self) internal view returns (bytes32) { + return hash(self, address(this), block.chainid); + } + + /** + * @dev Sames as {hash}, but with a custom entrypoint and chainid. + */ + function hash( + PackedUserOperation calldata self, + address entrypoint, + uint256 chainid + ) internal pure returns (bytes32) { + bytes32 result = keccak256( + abi.encode( + keccak256( + abi.encode( + self.sender, + self.nonce, + keccak256(self.initCode), + keccak256(self.callData), + self.accountGasLimits, + self.preVerificationGas, + self.gasFees, + keccak256(self.paymasterAndData) + ) + ), + entrypoint, + chainid + ) + ); + return result; + } + + /** + * @dev Returns `verificationGasLimit` from the {PackedUserOperation}. + */ + function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x00)); + } + + /** + * @dev Returns `accountGasLimits` from the {PackedUserOperation}. + */ + function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x10)); + } + + /** + * @dev Returns the first section of `gasFees` from the {PackedUserOperation}. + */ + function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x00)); + } + + /** + * @dev Returns the second section of `gasFees` from the {PackedUserOperation}. + */ + function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x10)); + } + + /** + * @dev Returns the total gas price for the {PackedUserOperation} + * (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + */ + function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { + unchecked { + // Following values are "per gas" + uint256 maxPriorityFee = maxPriorityFeePerGas(self); + uint256 maxFee = maxFeePerGas(self); + return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); + } + } + + /** + * @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. + */ + function paymaster(PackedUserOperation calldata self) internal pure returns (address) { + return address(bytes20(self.paymasterAndData[0:20])); + } + + /** + * @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. + */ + function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[20:36])); + } + + /** + * @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. + */ + function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[36:52])); + } + + struct UserOpInfo { + // Static fields + address sender; + uint256 nonce; + uint256 verificationGasLimit; + uint256 callGasLimit; + uint256 paymasterVerificationGasLimit; + uint256 paymasterPostOpGasLimit; + uint256 preVerificationGas; + address paymaster; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + // Extra fields + bytes32 userOpHash; + uint256 prefund; + uint256 preOpGas; + bytes context; + } + + /** + * @dev Loads the {UserOpInfo} from a {PackedUserOperation}. + */ + function load(UserOpInfo memory self, PackedUserOperation calldata source) internal view { + self.sender = source.sender; + self.nonce = source.nonce; + self.verificationGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x00)); + self.callGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x10)); + self.preVerificationGas = source.preVerificationGas; + self.maxPriorityFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x00)); + self.maxFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x10)); + + if (source.paymasterAndData.length > 0) { + require(source.paymasterAndData.length >= 52, "AA93 invalid paymasterAndData"); + self.paymaster = paymaster(source); + self.paymasterVerificationGasLimit = paymasterVerificationGasLimit(source); + self.paymasterPostOpGasLimit = paymasterPostOpGasLimit(source); + } else { + self.paymaster = address(0); + self.paymasterVerificationGasLimit = 0; + self.paymasterPostOpGasLimit = 0; + } + self.userOpHash = hash(source); + self.prefund = 0; + self.preOpGas = 0; + self.context = ""; + } + + /** + * @dev Returns the required prefund for the user operation. + */ + function requiredPrefund(UserOpInfo memory self) internal pure returns (uint256) { + return + (self.verificationGasLimit + + self.callGasLimit + + self.paymasterVerificationGasLimit + + self.paymasterPostOpGasLimit + + self.preVerificationGas) * self.maxFeePerGas; + } + + /** + * @dev Returns the required prefund for the user operation. + */ + function gasPrice(UserOpInfo memory self) internal view returns (uint256) { + unchecked { + uint256 maxFee = self.maxFeePerGas; + uint256 maxPriorityFee = self.maxPriorityFeePerGas; + return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); + } + } +} diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol new file mode 100644 index 00000000000..c2ea6d91b36 --- /dev/null +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Execution} from "../../interfaces/draft-IERC7579.sol"; +import {Packing} from "../../utils/Packing.sol"; +import {Address} from "../../utils/Address.sol"; + +type Mode is bytes32; +type CallType is bytes1; +type ExecType is bytes1; +type ModeSelector is bytes4; +type ModePayload is bytes22; + +/** + * @dev Library with common ERC-7579 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579]. + */ +library ERC7579Utils { + using Packing for *; + + /** + * @dev A single `call` execution. + */ + CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); + + /** + * @dev A batch of `call` executions. + */ + CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); + + /** + * @dev A `delegatecall` execution. + */ + CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + + /** + * @dev Default execution type that reverts on failure. + */ + ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); + + /** + * @dev Execution type that does not revert on failure. + */ + ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + + /** + * @dev Emits when an {EXECTYPE_TRY} execution fails. + */ + event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); + + /** + * @dev The provided {CallType} is not supported. + */ + error ERC7579UnsupportedCallType(CallType callType); + + /** + * @dev The provided {ExecType} is not supported. + */ + error ERC7579UnsupportedExecType(ExecType execType); + + /** + * @dev The provided module doesn't match the provided module type. + */ + error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module); + + /** + * @dev The module is not installed. + */ + error ERC7579UninstalledModule(uint256 moduleTypeId, address module); + + /** + * @dev The module is already installed. + */ + error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module); + + /** + * @dev The module type is not supported. + */ + error ERC7579UnsupportedModuleType(uint256 moduleTypeId); + + /** + * @dev Executes a single call. + */ + function execSingle( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _call(0, execType, target, value, callData); + } + + /** + * @dev Executes a batch of calls. + */ + function execBatch( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + Execution[] calldata executionBatch = decodeBatch(executionCalldata); + returnData = new bytes[](executionBatch.length); + for (uint256 i = 0; i < executionBatch.length; ++i) { + returnData[i] = _call( + i, + execType, + executionBatch[i].target, + executionBatch[i].value, + executionBatch[i].callData + ); + } + } + + /** + * @dev Executes a delegate call. + */ + function execDelegateCall( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + (address target, bytes calldata callData) = decodeDelegate(executionCalldata); + returnData = new bytes[](1); + (bool success, bytes memory returndata) = target.delegatecall(callData); + returnData[0] = returndata; + _validateExecutionMode(0, execType, success, returndata); + } + + /** + * @dev Encodes the mode with the provided parameters. See {decodeMode}. + */ + function encodeMode( + CallType callType, + ExecType execType, + ModeSelector selector, + ModePayload payload + ) internal pure returns (Mode mode) { + return + Mode.wrap( + CallType + .unwrap(callType) + .pack_1_1(ExecType.unwrap(execType)) + .pack_2_4(bytes4(0)) + .pack_6_4(ModeSelector.unwrap(selector)) + .pack_10_22(ModePayload.unwrap(payload)) + ); + } + + /** + * @dev Decodes the mode into its parameters. See {encodeMode}. + */ + function decodeMode( + Mode mode + ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) { + return ( + CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)), + ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)), + ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)), + ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10)) + ); + } + + /** + * @dev Encodes a single call execution. See {decodeSingle}. + */ + function encodeSingle( + address target, + uint256 value, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, value, callData); + } + + /** + * @dev Decodes a single call execution. See {encodeSingle}. + */ + function decodeSingle( + bytes calldata executionCalldata + ) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = abi.decode(executionCalldata[0:20], (address)); + value = abi.decode(executionCalldata[20:52], (uint256)); + callData = executionCalldata[52:]; + } + + /** + * @dev Encodes a delegate call execution. See {decodeDelegate}. + */ + function encodeDelegate( + address target, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, callData); + } + + /** + * @dev Decodes a delegate call execution. See {encodeDelegate}. + */ + function decodeDelegate( + bytes calldata executionCalldata + ) internal pure returns (address target, bytes calldata callData) { + target = abi.decode(executionCalldata[0:20], (address)); + callData = executionCalldata[20:]; + } + + /** + * @dev Encodes a batch of executions. See {decodeBatch}. + */ + function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) { + return abi.encode(executionBatch); + } + + /** + * @dev Decodes a batch of executions. See {encodeBatch}. + */ + function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { + assembly ("memory-safe") { + let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) + // Extract the ERC7579 Executions + executionBatch.offset := add(ptr, 32) + executionBatch.length := calldataload(ptr) + } + } + + /** + * @dev Executes a `call` to the target with the provided {ExecType}. + */ + function _call( + uint256 index, + ExecType execType, + address target, + uint256 value, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /** + * @dev Validates the execution mode and returns the returndata. + */ + function _validateExecutionMode( + uint256 index, + ExecType execType, + bool success, + bytes memory returndata + ) private returns (bytes memory) { + if (execType == ERC7579Utils.EXECTYPE_DEFAULT) return Address.verifyCallResult(success, returndata); + if (execType == ERC7579Utils.EXECTYPE_TRY) { + if (!success) emit ERC7579TryExecuteFail(index, returndata); + return returndata; + } + revert ERC7579UnsupportedExecType(execType); + } +} + +// Operators +using {_eqCallTypeGlobal as ==} for CallType global; +using {_eqExecTypeGlobal as ==} for ExecType global; +using {_eqModeSelectorGlobal as ==} for ModeSelector global; +using {_eqModePayloadGlobal as ==} for ModePayload global; + +/// @dev Compares two `CallType` values for equality. +function _eqCallTypeGlobal(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +/// @dev Compares two `ExecType` values for equality. +function _eqExecTypeGlobal(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +/// @dev Compares two `ModeSelector` values for equality. +function _eqModeSelectorGlobal(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} + +/// @dev Compares two `ModePayload` values for equality. +function _eqModePayloadGlobal(ModePayload a, ModePayload b) pure returns (bool) { + return ModePayload.unwrap(a) == ModePayload.unwrap(b); +} diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol new file mode 100644 index 00000000000..fcc8d95a693 --- /dev/null +++ b/contracts/interfaces/draft-IERC4337.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev An user operation that can be executed by an account on its packed version. + * + * - `sender`: The account making the operation (might not be deployed yet). + * - `nonce`: Anti-replay parameter handled by the entrypoint. + * - `initCode`: Concatenation of the address of the factory and the factory calldata. + * - `callData`: The `data` the sender will be called with. + * - `accountGasLimits`: Concatenation of the verification step gas limit and the main call gas limit. + * - `preVerificationGas`: Extra gas to pay the bundler before the verification step. + * - `gasFees`: Maximum fee and priority fee the sender is willing to pay. + * - `paymasterAndData`: Address of paymaster contract and calldata, (or empty, if account pays for itself). + * - `signature`: Data passed into the account to verify authorization. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; // `abi.encodePacked(factory, factoryData)` + bytes callData; + bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each + uint256 preVerificationGas; + bytes32 gasFees; // `abi.encodePacked(maxPriorityFee, maxFeePerGas)` 16 bytes each + bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` + bytes signature; +} + +/** + * @dev Aggregates and validates multiple signatures for a batch of user operations. + */ +interface IAggregator { + /** + * @dev Validates the signature for a user operation. + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * @dev Returns an aggregated signature for a batch of user operation's signatures. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatesSignature); + + /** + * @dev Validates that the aggregated signature is valid for the user operations. + * + * Requirements: + * + * - The aggregated signature MUST match the given list of operations. + */ + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; +} + +/** + * @dev Handle nonce management for accounts. + */ +interface IEntryPointNonces { + /** + * @dev Returns the nonce for a `sender` account and a `key`. + * + * Nonces for a certain `key` are always increasing. + */ + function getNonce(address sender, uint192 key) external view returns (uint256 nonce); +} + +/** + * @dev Handle stake management for accounts. + */ +interface IEntryPointStake { + /** + * @dev Returns the balance of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Deposits `msg.value` to the account. + */ + function depositTo(address account) external payable; + + /** + * @dev Withdraws `withdrawAmount` from the account to `withdrawAddress`. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; + + /** + * @dev Adds stake to the account with an unstake delay of `unstakeDelaySec`. + */ + function addStake(uint32 unstakeDelaySec) external payable; + + /** + * @dev Unlocks the stake of the account. + */ + function unlockStake() external; + + /** + * @dev Withdraws the stake of the account to `withdrawAddress`. + */ + function withdrawStake(address payable withdrawAddress) external; +} + +/** + * @dev Entry point for user operations. + */ +interface IEntryPoint is IEntryPointNonces, IEntryPointStake { + /** + * @dev A user operation at `opIndex` failed with `reason`. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * @dev A user operation at `opIndex` failed with `reason` and `inner` returned data. + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + /** + * @dev Batch of aggregated user operations per aggregator. + */ + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + IAggregator aggregator; + bytes signature; + } + + /** + * @dev Executes a batch of user operations. + */ + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + + /** + * @dev Executes a batch of aggregated user operations per aggregator. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; +} + +/** + * @dev Handle the simulation of user operations. + */ +interface IEntryPointSimulation { + /** + * @dev Result of executing a user operation. + */ + struct ExecutionResult { + uint256 preOpGas; + uint256 paid; + uint256 accountValidationData; + uint256 paymasterValidationData; + bool targetSuccess; + bytes targetResult; + } + + /** + * @dev Result of validating a user operation. + */ + struct ReturnInfo { + uint256 preOpGas; + uint256 prefund; + uint256 accountValidationData; + uint256 paymasterValidationData; + bytes paymasterContext; + } + + /** + * @dev Information about the stake of an account. + */ + struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; + } + + /** + * @dev Information about the stake of an aggregator. + */ + struct AggregatorStakeInfo { + address aggregator; + StakeInfo stakeInfo; + } + + /** + * @dev Result of simulating the validation of a user operation. + */ + struct ValidationResult { + ReturnInfo returnInfo; + StakeInfo senderInfo; + StakeInfo factoryInfo; + StakeInfo paymasterInfo; + AggregatorStakeInfo aggregatorInfo; + } + + /** + * @dev Simulates the validation of a user operation (i.e. {IAccount-validateUserOp} and {IPaymaster-validatePaymasterUserOp}). + */ + function simulateValidation(PackedUserOperation calldata userOp) external returns (ValidationResult memory); + + /** + * @dev Simulates the full execution of a user operation. + */ + function simulateHandleOp( + PackedUserOperation calldata op, + address target, + bytes calldata targetCallData + ) external returns (ExecutionResult memory); +} + +/** + * @dev Base interface for an account. + */ +interface IAccount { + /** + * @dev Validates a user operation. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} + +/** + * @dev Support for executing user operations by prepending the {executeUserOp} function selector + * to the UserOperation's `callData`. + */ +interface IAccountExecute { + /** + * @dev Executes a user operation. + */ + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +} + +/** + * @dev Interface for a paymaster contract that agrees to pay for the gas costs of a user operation. + * + * NOTE: A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + */ +interface IPaymaster { + enum PostOpMode { + opSucceeded, + opReverted, + postOpReverted + } + + /** + * @dev Validates whether the paymaster is willing to pay for the user operation. + * + * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted. + */ + function validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); + + /** + * @dev Verifies the sender is the entrypoint. + */ + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) external; +} diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol new file mode 100644 index 00000000000..47f1627f682 --- /dev/null +++ b/contracts/interfaces/draft-IERC7579.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "./draft-IERC4337.sol"; + +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; +uint256 constant MODULE_TYPE_VALIDATOR = 1; +uint256 constant MODULE_TYPE_EXECUTOR = 2; +uint256 constant MODULE_TYPE_FALLBACK = 3; +uint256 constant MODULE_TYPE_HOOK = 4; + +interface IERC7579Module { + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` initialization + * + * MUST revert on error (e.g. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579Validator is IERC7579Module { + /** + * @dev Validates a UserOperation + * @param userOp the ERC-4337 PackedUserOperation + * @param userOpHash the hash of the ERC-4337 PackedUserOperation + * + * MUST validate that the signature is a valid signature of the userOpHash + * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /** + * @dev Validates a signature using ERC-1271 + * @param sender the address that sent the ERC-1271 request to the smart account + * @param hash the hash of the ERC-1271 request + * @param signature the signature of the ERC-1271 request + * + * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid + * MUST NOT modify state + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} + +interface IERC7579Hook is IERC7579Module { + /** + * @dev Called by the smart account before execution + * @param msgSender the address that called the smart account + * @param value the value that was sent to the smart account + * @param msgData the data that was sent to the smart account + * + * MAY return arbitrary data in the `hookData` return value + */ + function preCheck( + address msgSender, + uint256 value, + bytes calldata msgData + ) external returns (bytes memory hookData); + + /** + * @dev Called by the smart account after execution + * @param hookData the data that was returned by the `preCheck` function + * + * MAY validate the `hookData` to validate transaction context of the `preCheck` function + */ + function postCheck(bytes calldata hookData) external; +} + +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IERC7579Execution { + /** + * @dev Executes a transaction on behalf of the account. + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function execute(bytes32 mode, bytes calldata executionCalldata) external; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: i.e. onlyExecutorModule + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) external returns (bytes[] memory returnData); +} + +interface IERC7579AccountConfig { + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * + * MUST return a non-empty string + * The accountId SHOULD be structured like so: + * "vendorname.accountname.semver" + * The id SHOULD be unique across all smart accounts + */ + function accountId() external view returns (string memory accountImplementationId); + + /** + * @dev Function to check if the account supports a certain execution mode (see above) + * @param encodedMode the encoded mode + * + * MUST return true if the account supports the mode and false otherwise + */ + function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); + + /** + * @dev Function to check if the account supports a certain module typeId + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * + * MUST return true if the account supports the module type and false otherwise + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579ModuleConfig { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Installs a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onInstall` on the module with the `initData` parameter if provided + * MUST emit ModuleInstalled event + * MUST revert if the module is already installed or the initialization on the module failed + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; + + /** + * @dev Uninstalls a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onUninstall` on the module with the `deInitData` parameter if provided + * MUST emit ModuleUninstalled event + * MUST revert if the module is not installed or the deInitialization on the module failed + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; + + /** + * @dev Returns whether a module is installed on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param additionalContext arbitrary data that may be required to determine if the module is installed + * + * MUST return true if the module is installed and false otherwise + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) external view returns (bool); +} diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 4da311cf3c0..383e5eb78bd 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -67,6 +67,38 @@ library Packing { } } + function pack_2_8(bytes2 left, bytes8 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(192, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_10(bytes2 left, bytes10 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_20(bytes2 left, bytes20 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(96, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_22(bytes2 left, bytes22 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(16, right)) + } + } + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -83,6 +115,14 @@ library Packing { } } + function pack_4_6(bytes4 left, bytes6 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(32, right)) + } + } + function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -139,6 +179,14 @@ library Packing { } } + function pack_6_4(bytes6 left, bytes4 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(224, not(0))) + result := or(left, shr(48, right)) + } + } + function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(208, not(0))) @@ -147,6 +195,38 @@ library Packing { } } + function pack_6_10(bytes6 left, bytes10 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_16(bytes6 left, bytes16 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(128, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_22(bytes6 left, bytes22 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_8_2(bytes8 left, bytes2 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(64, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(192, not(0))) @@ -195,6 +275,46 @@ library Packing { } } + function pack_10_2(bytes10 left, bytes2 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_6(bytes10 left, bytes6 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_10(bytes10 left, bytes10 right) internal pure returns (bytes20 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_12(bytes10 left, bytes12 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(160, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_22(bytes10 left, bytes22 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(80, right)) + } + } + function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -211,6 +331,14 @@ library Packing { } } + function pack_12_10(bytes12 left, bytes10 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(96, right)) + } + } + function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -243,6 +371,14 @@ library Packing { } } + function pack_16_6(bytes16 left, bytes6 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(128, right)) + } + } + function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(128, not(0))) @@ -267,6 +403,14 @@ library Packing { } } + function pack_20_2(bytes20 left, bytes2 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(160, right)) + } + } + function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(96, not(0))) @@ -291,6 +435,30 @@ library Packing { } } + function pack_22_2(bytes22 left, bytes2 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_6(bytes22 left, bytes6 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_10(bytes22 left, bytes10 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(176, right)) + } + } + function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { left := and(left, shl(64, not(0))) @@ -465,6 +633,81 @@ library Packing { } } + function extract_10_1(bytes10 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 9) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_10_1(bytes10 self, bytes1 value, uint8 offset) internal pure returns (bytes10 result) { + bytes1 oldValue = extract_10_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_2(bytes10 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 8) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_10_2(bytes10 self, bytes2 value, uint8 offset) internal pure returns (bytes10 result) { + bytes2 oldValue = extract_10_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_4(bytes10 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_10_4(bytes10 self, bytes4 value, uint8 offset) internal pure returns (bytes10 result) { + bytes4 oldValue = extract_10_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_6(bytes10 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 4) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_10_6(bytes10 self, bytes6 value, uint8 offset) internal pure returns (bytes10 result) { + bytes6 oldValue = extract_10_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_8(bytes10 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_10_8(bytes10 self, bytes8 value, uint8 offset) internal pure returns (bytes10 result) { + bytes8 oldValue = extract_10_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_12_1(bytes12 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 11) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -540,6 +783,21 @@ library Packing { } } + function extract_12_10(bytes12 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_12_10(bytes12 self, bytes10 value, uint8 offset) internal pure returns (bytes12 result) { + bytes10 oldValue = extract_12_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_1(bytes16 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 15) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -615,6 +873,21 @@ library Packing { } } + function extract_16_10(bytes16 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_16_10(bytes16 self, bytes10 value, uint8 offset) internal pure returns (bytes16 result) { + bytes10 oldValue = extract_16_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_12(bytes16 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -705,6 +978,21 @@ library Packing { } } + function extract_20_10(bytes20 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_20_10(bytes20 self, bytes10 value, uint8 offset) internal pure returns (bytes20 result) { + bytes10 oldValue = extract_20_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_20_12(bytes20 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -735,6 +1023,141 @@ library Packing { } } + function extract_22_1(bytes22 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 21) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_22_1(bytes22 self, bytes1 value, uint8 offset) internal pure returns (bytes22 result) { + bytes1 oldValue = extract_22_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_2(bytes22 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 20) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_22_2(bytes22 self, bytes2 value, uint8 offset) internal pure returns (bytes22 result) { + bytes2 oldValue = extract_22_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_4(bytes22 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_22_4(bytes22 self, bytes4 value, uint8 offset) internal pure returns (bytes22 result) { + bytes4 oldValue = extract_22_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_6(bytes22 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 16) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_22_6(bytes22 self, bytes6 value, uint8 offset) internal pure returns (bytes22 result) { + bytes6 oldValue = extract_22_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_8(bytes22 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_22_8(bytes22 self, bytes8 value, uint8 offset) internal pure returns (bytes22 result) { + bytes8 oldValue = extract_22_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_10(bytes22 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 12) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_22_10(bytes22 self, bytes10 value, uint8 offset) internal pure returns (bytes22 result) { + bytes10 oldValue = extract_22_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_12(bytes22 self, uint8 offset) internal pure returns (bytes12 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(160, not(0))) + } + } + + function replace_22_12(bytes22 self, bytes12 value, uint8 offset) internal pure returns (bytes22 result) { + bytes12 oldValue = extract_22_12(self, offset); + assembly ("memory-safe") { + value := and(value, shl(160, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_16(bytes22 self, uint8 offset) internal pure returns (bytes16 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(128, not(0))) + } + } + + function replace_22_16(bytes22 self, bytes16 value, uint8 offset) internal pure returns (bytes22 result) { + bytes16 oldValue = extract_22_16(self, offset); + assembly ("memory-safe") { + value := and(value, shl(128, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_20(bytes22 self, uint8 offset) internal pure returns (bytes20 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(96, not(0))) + } + } + + function replace_22_20(bytes22 self, bytes20 value, uint8 offset) internal pure returns (bytes22 result) { + bytes20 oldValue = extract_22_20(self, offset); + assembly ("memory-safe") { + value := and(value, shl(96, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_1(bytes24 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 23) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -810,6 +1233,21 @@ library Packing { } } + function extract_24_10(bytes24 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_24_10(bytes24 self, bytes10 value, uint8 offset) internal pure returns (bytes24 result) { + bytes10 oldValue = extract_24_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_12(bytes24 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 12) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -855,6 +1293,21 @@ library Packing { } } + function extract_24_22(bytes24 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_24_22(bytes24 self, bytes22 value, uint8 offset) internal pure returns (bytes24 result) { + bytes22 oldValue = extract_24_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_1(bytes28 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 27) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -930,6 +1383,21 @@ library Packing { } } + function extract_28_10(bytes28 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_28_10(bytes28 self, bytes10 value, uint8 offset) internal pure returns (bytes28 result) { + bytes10 oldValue = extract_28_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_12(bytes28 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 16) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -975,6 +1443,21 @@ library Packing { } } + function extract_28_22(bytes28 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_28_22(bytes28 self, bytes22 value, uint8 offset) internal pure returns (bytes28 result) { + bytes22 oldValue = extract_28_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_24(bytes28 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1065,6 +1548,21 @@ library Packing { } } + function extract_32_10(bytes32 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 22) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_32_10(bytes32 self, bytes10 value, uint8 offset) internal pure returns (bytes32 result) { + bytes10 oldValue = extract_32_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_12(bytes32 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 20) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1110,6 +1608,21 @@ library Packing { } } + function extract_32_22(bytes32 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_32_22(bytes32 self, bytes22 value, uint8 offset) internal pure returns (bytes32 result) { + bytes22 oldValue = extract_32_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_24(bytes32 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol new file mode 100644 index 00000000000..175ea2b18d2 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -0,0 +1,727 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ + +import "../interfaces/IAccount.sol"; +import "../interfaces/IAccountExecute.sol"; +import "../interfaces/IPaymaster.sol"; +import "../interfaces/IEntryPoint.sol"; + +import "../utils/Exec.sol"; +import "./StakeManager.sol"; +import "./SenderCreator.sol"; +import "./Helpers.sol"; +import "./NonceManager.sol"; +import "./UserOperationLib.sol"; + +// import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "../../../utils/introspection/ERC165.sol"; // OZ edit +import "../../../utils/ReentrancyGuard.sol"; // OZ edit + +/* + * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + * Only one instance required on each chain. + */ + +/// @custom:security-contact https://bounty.ethereum.org +contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ERC165 { + using UserOperationLib for PackedUserOperation; + + SenderCreator private immutable _senderCreator = new SenderCreator(); + + function senderCreator() internal view virtual returns (SenderCreator) { + return _senderCreator; + } + + //compensate for innerHandleOps' emit message and deposit refund. + // allow some slack for future gas price changes. + uint256 private constant INNER_GAS_OVERHEAD = 10000; + + // Marker for inner call revert on out of gas + bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead"; + bytes32 private constant INNER_REVERT_LOW_PREFUND = hex"deadaa51"; + + uint256 private constant REVERT_REASON_MAX_LEN = 2048; + uint256 private constant PENALTY_PERCENT = 10; + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything + return + interfaceId == + (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) || + interfaceId == type(IEntryPoint).interfaceId || + interfaceId == type(IStakeManager).interfaceId || + interfaceId == type(INonceManager).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * Compensate the caller's beneficiary address with the collected fees of all UserOperations. + * @param beneficiary - The address to receive the fees. + * @param amount - Amount to transfer. + */ + function _compensate(address payable beneficiary, uint256 amount) internal { + require(beneficiary != address(0), "AA90 invalid beneficiary"); + (bool success, ) = beneficiary.call{value: amount}(""); + require(success, "AA91 failed send to beneficiary"); + } + + /** + * Execute a user operation. + * @param opIndex - Index into the opInfo array. + * @param userOp - The userOp to execute. + * @param opInfo - The opInfo filled by validatePrepayment for this userOp. + * @return collected - The total amount this userOp paid. + */ + function _executeUserOp( + uint256 opIndex, + PackedUserOperation calldata userOp, + UserOpInfo memory opInfo + ) internal returns (uint256 collected) { + uint256 preGas = gasleft(); + bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset); + bool success; + { + uint256 saveFreePtr; + assembly ("memory-safe") { + saveFreePtr := mload(0x40) + } + bytes calldata callData = userOp.callData; + bytes memory innerCall; + bytes4 methodSig; + assembly { + let len := callData.length + if gt(len, 3) { + methodSig := calldataload(callData.offset) + } + } + if (methodSig == IAccountExecute.executeUserOp.selector) { + bytes memory executeUserOp = abi.encodeCall(IAccountExecute.executeUserOp, (userOp, opInfo.userOpHash)); + innerCall = abi.encodeCall(this.innerHandleOp, (executeUserOp, opInfo, context)); + } else { + innerCall = abi.encodeCall(this.innerHandleOp, (callData, opInfo, context)); + } + assembly ("memory-safe") { + success := call(gas(), address(), 0, add(innerCall, 0x20), mload(innerCall), 0, 32) + collected := mload(0) + mstore(0x40, saveFreePtr) + } + } + if (!success) { + bytes32 innerRevertCode; + assembly ("memory-safe") { + let len := returndatasize() + if eq(32, len) { + returndatacopy(0, 0, 32) + innerRevertCode := mload(0) + } + } + if (innerRevertCode == INNER_OUT_OF_GAS) { + // handleOps was called with gas limit too low. abort entire bundle. + //can only be caused by bundler (leaving not enough gas for inner call) + revert FailedOp(opIndex, "AA95 out of gas"); + } else if (innerRevertCode == INNER_REVERT_LOW_PREFUND) { + // innerCall reverted on prefund too low. treat entire prefund as "gas cost" + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + uint256 actualGasCost = opInfo.prefund; + emitPrefundTooLow(opInfo); + emitUserOperationEvent(opInfo, false, actualGasCost, actualGas); + collected = actualGasCost; + } else { + emit PostOpRevertReason( + opInfo.userOpHash, + opInfo.mUserOp.sender, + opInfo.mUserOp.nonce, + Exec.getReturnData(REVERT_REASON_MAX_LEN) + ); + + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + collected = _postExecution(IPaymaster.PostOpMode.postOpReverted, opInfo, context, actualGas); + } + } + } + + function emitUserOperationEvent( + UserOpInfo memory opInfo, + bool success, + uint256 actualGasCost, + uint256 actualGas + ) internal virtual { + emit UserOperationEvent( + opInfo.userOpHash, + opInfo.mUserOp.sender, + opInfo.mUserOp.paymaster, + opInfo.mUserOp.nonce, + success, + actualGasCost, + actualGas + ); + } + + function emitPrefundTooLow(UserOpInfo memory opInfo) internal virtual { + emit UserOperationPrefundTooLow(opInfo.userOpHash, opInfo.mUserOp.sender, opInfo.mUserOp.nonce); + } + + /// @inheritdoc IEntryPoint + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) public nonReentrant { + uint256 opslen = ops.length; + UserOpInfo[] memory opInfos = new UserOpInfo[](opslen); + + unchecked { + for (uint256 i = 0; i < opslen; i++) { + UserOpInfo memory opInfo = opInfos[i]; + (uint256 validationData, uint256 pmValidationData) = _validatePrepayment(i, ops[i], opInfo); + _validateAccountAndPaymasterValidationData(i, validationData, pmValidationData, address(0)); + } + + uint256 collected = 0; + emit BeforeExecution(); + + for (uint256 i = 0; i < opslen; i++) { + collected += _executeUserOp(i, ops[i], opInfos[i]); + } + + _compensate(beneficiary, collected); + } + } + + /// @inheritdoc IEntryPoint + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) public nonReentrant { + uint256 opasLen = opsPerAggregator.length; + uint256 totalOps = 0; + for (uint256 i = 0; i < opasLen; i++) { + UserOpsPerAggregator calldata opa = opsPerAggregator[i]; + PackedUserOperation[] calldata ops = opa.userOps; + IAggregator aggregator = opa.aggregator; + + //address(1) is special marker of "signature error" + require(address(aggregator) != address(1), "AA96 invalid aggregator"); + + if (address(aggregator) != address(0)) { + // solhint-disable-next-line no-empty-blocks + try aggregator.validateSignatures(ops, opa.signature) {} catch { + revert SignatureValidationFailed(address(aggregator)); + } + } + + totalOps += ops.length; + } + + UserOpInfo[] memory opInfos = new UserOpInfo[](totalOps); + + uint256 opIndex = 0; + for (uint256 a = 0; a < opasLen; a++) { + UserOpsPerAggregator calldata opa = opsPerAggregator[a]; + PackedUserOperation[] calldata ops = opa.userOps; + IAggregator aggregator = opa.aggregator; + + uint256 opslen = ops.length; + for (uint256 i = 0; i < opslen; i++) { + UserOpInfo memory opInfo = opInfos[opIndex]; + (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment( + opIndex, + ops[i], + opInfo + ); + _validateAccountAndPaymasterValidationData( + i, + validationData, + paymasterValidationData, + address(aggregator) + ); + opIndex++; + } + } + + emit BeforeExecution(); + + uint256 collected = 0; + opIndex = 0; + for (uint256 a = 0; a < opasLen; a++) { + UserOpsPerAggregator calldata opa = opsPerAggregator[a]; + emit SignatureAggregatorChanged(address(opa.aggregator)); + PackedUserOperation[] calldata ops = opa.userOps; + uint256 opslen = ops.length; + + for (uint256 i = 0; i < opslen; i++) { + collected += _executeUserOp(opIndex, ops[i], opInfos[opIndex]); + opIndex++; + } + } + emit SignatureAggregatorChanged(address(0)); + + _compensate(beneficiary, collected); + } + + /** + * A memory copy of UserOp static fields only. + * Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster. + */ + struct MemoryUserOp { + address sender; + uint256 nonce; + uint256 verificationGasLimit; + uint256 callGasLimit; + uint256 paymasterVerificationGasLimit; + uint256 paymasterPostOpGasLimit; + uint256 preVerificationGas; + address paymaster; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + } + + struct UserOpInfo { + MemoryUserOp mUserOp; + bytes32 userOpHash; + uint256 prefund; + uint256 contextOffset; + uint256 preOpGas; + } + + /** + * Inner function to handle a UserOperation. + * Must be declared "external" to open a call context, but it can only be called by handleOps. + * @param callData - The callData to execute. + * @param opInfo - The UserOpInfo struct. + * @param context - The context bytes. + * @return actualGasCost - the actual cost in eth this UserOperation paid for gas + */ + function innerHandleOp( + bytes memory callData, + UserOpInfo memory opInfo, + bytes calldata context + ) external returns (uint256 actualGasCost) { + uint256 preGas = gasleft(); + require(msg.sender == address(this), "AA92 internal call only"); + MemoryUserOp memory mUserOp = opInfo.mUserOp; + + uint256 callGasLimit = mUserOp.callGasLimit; + unchecked { + // handleOps was called with gas limit too low. abort entire bundle. + if ((gasleft() * 63) / 64 < callGasLimit + mUserOp.paymasterPostOpGasLimit + INNER_GAS_OVERHEAD) { + assembly ("memory-safe") { + mstore(0, INNER_OUT_OF_GAS) + revert(0, 32) + } + } + } + + IPaymaster.PostOpMode mode = IPaymaster.PostOpMode.opSucceeded; + if (callData.length > 0) { + bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit); + if (!success) { + bytes memory result = Exec.getReturnData(REVERT_REASON_MAX_LEN); + if (result.length > 0) { + emit UserOperationRevertReason(opInfo.userOpHash, mUserOp.sender, mUserOp.nonce, result); + } + mode = IPaymaster.PostOpMode.opReverted; + } + } + + unchecked { + uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; + return _postExecution(mode, opInfo, context, actualGas); + } + } + + /// @inheritdoc IEntryPoint + function getUserOpHash(PackedUserOperation calldata userOp) public view returns (bytes32) { + return keccak256(abi.encode(userOp.hash(), address(this), block.chainid)); + } + + /** + * Copy general fields from userOp into the memory opInfo structure. + * @param userOp - The user operation. + * @param mUserOp - The memory user operation. + */ + function _copyUserOpToMemory(PackedUserOperation calldata userOp, MemoryUserOp memory mUserOp) internal pure { + mUserOp.sender = userOp.sender; + mUserOp.nonce = userOp.nonce; + (mUserOp.verificationGasLimit, mUserOp.callGasLimit) = UserOperationLib.unpackUints(userOp.accountGasLimits); + mUserOp.preVerificationGas = userOp.preVerificationGas; + (mUserOp.maxPriorityFeePerGas, mUserOp.maxFeePerGas) = UserOperationLib.unpackUints(userOp.gasFees); + bytes calldata paymasterAndData = userOp.paymasterAndData; + if (paymasterAndData.length > 0) { + require(paymasterAndData.length >= UserOperationLib.PAYMASTER_DATA_OFFSET, "AA93 invalid paymasterAndData"); + ( + mUserOp.paymaster, + mUserOp.paymasterVerificationGasLimit, + mUserOp.paymasterPostOpGasLimit + ) = UserOperationLib.unpackPaymasterStaticFields(paymasterAndData); + } else { + mUserOp.paymaster = address(0); + mUserOp.paymasterVerificationGasLimit = 0; + mUserOp.paymasterPostOpGasLimit = 0; + } + } + + /** + * Get the required prefunded gas fee amount for an operation. + * @param mUserOp - The user operation in memory. + */ + function _getRequiredPrefund(MemoryUserOp memory mUserOp) internal pure returns (uint256 requiredPrefund) { + unchecked { + uint256 requiredGas = mUserOp.verificationGasLimit + + mUserOp.callGasLimit + + mUserOp.paymasterVerificationGasLimit + + mUserOp.paymasterPostOpGasLimit + + mUserOp.preVerificationGas; + + requiredPrefund = requiredGas * mUserOp.maxFeePerGas; + } + } + + /** + * Create sender smart contract account if init code is provided. + * @param opIndex - The operation index. + * @param opInfo - The operation info. + * @param initCode - The init code for the smart contract account. + */ + function _createSenderIfNeeded(uint256 opIndex, UserOpInfo memory opInfo, bytes calldata initCode) internal { + if (initCode.length != 0) { + address sender = opInfo.mUserOp.sender; + if (sender.code.length != 0) revert FailedOp(opIndex, "AA10 sender already constructed"); + address sender1 = senderCreator().createSender{gas: opInfo.mUserOp.verificationGasLimit}(initCode); + if (sender1 == address(0)) revert FailedOp(opIndex, "AA13 initCode failed or OOG"); + if (sender1 != sender) revert FailedOp(opIndex, "AA14 initCode must return sender"); + if (sender1.code.length == 0) revert FailedOp(opIndex, "AA15 initCode must create sender"); + address factory = address(bytes20(initCode[0:20])); + emit AccountDeployed(opInfo.userOpHash, sender, factory, opInfo.mUserOp.paymaster); + } + } + + /// @inheritdoc IEntryPoint + function getSenderAddress(bytes calldata initCode) public { + address sender = senderCreator().createSender(initCode); + revert SenderAddressResult(sender); + } + + /** + * Call account.validateUserOp. + * Revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund. + * Decrement account's deposit if needed. + * @param opIndex - The operation index. + * @param op - The user operation. + * @param opInfo - The operation info. + * @param requiredPrefund - The required prefund amount. + */ + function _validateAccountPrepayment( + uint256 opIndex, + PackedUserOperation calldata op, + UserOpInfo memory opInfo, + uint256 requiredPrefund, + uint256 verificationGasLimit + ) internal returns (uint256 validationData) { + unchecked { + MemoryUserOp memory mUserOp = opInfo.mUserOp; + address sender = mUserOp.sender; + _createSenderIfNeeded(opIndex, opInfo, op.initCode); + address paymaster = mUserOp.paymaster; + uint256 missingAccountFunds = 0; + if (paymaster == address(0)) { + uint256 bal = balanceOf(sender); + missingAccountFunds = bal > requiredPrefund ? 0 : requiredPrefund - bal; + } + try + IAccount(sender).validateUserOp{gas: verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds) + returns (uint256 _validationData) { + validationData = _validationData; + } catch { + revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN)); + } + if (paymaster == address(0)) { + DepositInfo storage senderInfo = deposits[sender]; + uint256 deposit = senderInfo.deposit; + if (requiredPrefund > deposit) { + revert FailedOp(opIndex, "AA21 didn't pay prefund"); + } + senderInfo.deposit = deposit - requiredPrefund; + } + } + } + + /** + * In case the request has a paymaster: + * - Validate paymaster has enough deposit. + * - Call paymaster.validatePaymasterUserOp. + * - Revert with proper FailedOp in case paymaster reverts. + * - Decrement paymaster's deposit. + * @param opIndex - The operation index. + * @param op - The user operation. + * @param opInfo - The operation info. + * @param requiredPreFund - The required prefund amount. + */ + function _validatePaymasterPrepayment( + uint256 opIndex, + PackedUserOperation calldata op, + UserOpInfo memory opInfo, + uint256 requiredPreFund + ) internal returns (bytes memory context, uint256 validationData) { + unchecked { + uint256 preGas = gasleft(); + MemoryUserOp memory mUserOp = opInfo.mUserOp; + address paymaster = mUserOp.paymaster; + DepositInfo storage paymasterInfo = deposits[paymaster]; + uint256 deposit = paymasterInfo.deposit; + if (deposit < requiredPreFund) { + revert FailedOp(opIndex, "AA31 paymaster deposit too low"); + } + paymasterInfo.deposit = deposit - requiredPreFund; + uint256 pmVerificationGasLimit = mUserOp.paymasterVerificationGasLimit; + try + IPaymaster(paymaster).validatePaymasterUserOp{gas: pmVerificationGasLimit}( + op, + opInfo.userOpHash, + requiredPreFund + ) + returns (bytes memory _context, uint256 _validationData) { + context = _context; + validationData = _validationData; + } catch { + revert FailedOpWithRevert(opIndex, "AA33 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN)); + } + if (preGas - gasleft() > pmVerificationGasLimit) { + revert FailedOp(opIndex, "AA36 over paymasterVerificationGasLimit"); + } + } + } + + /** + * Revert if either account validationData or paymaster validationData is expired. + * @param opIndex - The operation index. + * @param validationData - The account validationData. + * @param paymasterValidationData - The paymaster validationData. + * @param expectedAggregator - The expected aggregator. + */ + function _validateAccountAndPaymasterValidationData( + uint256 opIndex, + uint256 validationData, + uint256 paymasterValidationData, + address expectedAggregator + ) internal view { + (address aggregator, bool outOfTimeRange) = _getValidationData(validationData); + if (expectedAggregator != aggregator) { + revert FailedOp(opIndex, "AA24 signature error"); + } + if (outOfTimeRange) { + revert FailedOp(opIndex, "AA22 expired or not due"); + } + // pmAggregator is not a real signature aggregator: we don't have logic to handle it as address. + // Non-zero address means that the paymaster fails due to some signature check (which is ok only during estimation). + address pmAggregator; + (pmAggregator, outOfTimeRange) = _getValidationData(paymasterValidationData); + if (pmAggregator != address(0)) { + revert FailedOp(opIndex, "AA34 signature error"); + } + if (outOfTimeRange) { + revert FailedOp(opIndex, "AA32 paymaster expired or not due"); + } + } + + /** + * Parse validationData into its components. + * @param validationData - The packed validation data (sigFailed, validAfter, validUntil). + * @return aggregator the aggregator of the validationData + * @return outOfTimeRange true if current time is outside the time range of this validationData. + */ + function _getValidationData( + uint256 validationData + ) internal view returns (address aggregator, bool outOfTimeRange) { + if (validationData == 0) { + return (address(0), false); + } + ValidationData memory data = _parseValidationData(validationData); + // solhint-disable-next-line not-rely-on-time + outOfTimeRange = block.timestamp > data.validUntil || block.timestamp < data.validAfter; + aggregator = data.aggregator; + } + + /** + * Validate account and paymaster (if defined) and + * also make sure total validation doesn't exceed verificationGasLimit. + * This method is called off-chain (simulateValidation()) and on-chain (from handleOps) + * @param opIndex - The index of this userOp into the "opInfos" array. + * @param userOp - The userOp to validate. + */ + function _validatePrepayment( + uint256 opIndex, + PackedUserOperation calldata userOp, + UserOpInfo memory outOpInfo + ) internal returns (uint256 validationData, uint256 paymasterValidationData) { + uint256 preGas = gasleft(); + MemoryUserOp memory mUserOp = outOpInfo.mUserOp; + _copyUserOpToMemory(userOp, mUserOp); + outOpInfo.userOpHash = getUserOpHash(userOp); + + // Validate all numeric values in userOp are well below 128 bit, so they can safely be added + // and multiplied without causing overflow. + uint256 verificationGasLimit = mUserOp.verificationGasLimit; + uint256 maxGasValues = mUserOp.preVerificationGas | + verificationGasLimit | + mUserOp.callGasLimit | + mUserOp.paymasterVerificationGasLimit | + mUserOp.paymasterPostOpGasLimit | + mUserOp.maxFeePerGas | + mUserOp.maxPriorityFeePerGas; + require(maxGasValues <= type(uint120).max, "AA94 gas values overflow"); + + uint256 requiredPreFund = _getRequiredPrefund(mUserOp); + validationData = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, verificationGasLimit); + + if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) { + revert FailedOp(opIndex, "AA25 invalid account nonce"); + } + + unchecked { + if (preGas - gasleft() > verificationGasLimit) { + revert FailedOp(opIndex, "AA26 over verificationGasLimit"); + } + } + + bytes memory context; + if (mUserOp.paymaster != address(0)) { + (context, paymasterValidationData) = _validatePaymasterPrepayment( + opIndex, + userOp, + outOpInfo, + requiredPreFund + ); + } + unchecked { + outOpInfo.prefund = requiredPreFund; + outOpInfo.contextOffset = getOffsetOfMemoryBytes(context); + outOpInfo.preOpGas = preGas - gasleft() + userOp.preVerificationGas; + } + } + + /** + * Process post-operation, called just after the callData is executed. + * If a paymaster is defined and its validation returned a non-empty context, its postOp is called. + * The excess amount is refunded to the account (or paymaster - if it was used in the request). + * @param mode - Whether is called from innerHandleOp, or outside (postOpReverted). + * @param opInfo - UserOp fields and info collected during validation. + * @param context - The context returned in validatePaymasterUserOp. + * @param actualGas - The gas used so far by this user operation. + */ + function _postExecution( + IPaymaster.PostOpMode mode, + UserOpInfo memory opInfo, + bytes memory context, + uint256 actualGas + ) private returns (uint256 actualGasCost) { + uint256 preGas = gasleft(); + unchecked { + address refundAddress; + MemoryUserOp memory mUserOp = opInfo.mUserOp; + uint256 gasPrice = getUserOpGasPrice(mUserOp); + + address paymaster = mUserOp.paymaster; + if (paymaster == address(0)) { + refundAddress = mUserOp.sender; + } else { + refundAddress = paymaster; + if (context.length > 0) { + actualGasCost = actualGas * gasPrice; + if (mode != IPaymaster.PostOpMode.postOpReverted) { + try + IPaymaster(paymaster).postOp{gas: mUserOp.paymasterPostOpGasLimit}( + mode, + context, + actualGasCost, + gasPrice + ) + // solhint-disable-next-line no-empty-blocks + { + + } catch { + bytes memory reason = Exec.getReturnData(REVERT_REASON_MAX_LEN); + revert PostOpReverted(reason); + } + } + } + } + actualGas += preGas - gasleft(); + + // Calculating a penalty for unused execution gas + { + uint256 executionGasLimit = mUserOp.callGasLimit + mUserOp.paymasterPostOpGasLimit; + uint256 executionGasUsed = actualGas - opInfo.preOpGas; + // this check is required for the gas used within EntryPoint and not covered by explicit gas limits + if (executionGasLimit > executionGasUsed) { + uint256 unusedGas = executionGasLimit - executionGasUsed; + uint256 unusedGasPenalty = (unusedGas * PENALTY_PERCENT) / 100; + actualGas += unusedGasPenalty; + } + } + + actualGasCost = actualGas * gasPrice; + uint256 prefund = opInfo.prefund; + if (prefund < actualGasCost) { + if (mode == IPaymaster.PostOpMode.postOpReverted) { + actualGasCost = prefund; + emitPrefundTooLow(opInfo); + emitUserOperationEvent(opInfo, false, actualGasCost, actualGas); + } else { + assembly ("memory-safe") { + mstore(0, INNER_REVERT_LOW_PREFUND) + revert(0, 32) + } + } + } else { + uint256 refund = prefund - actualGasCost; + _incrementDeposit(refundAddress, refund); + bool success = mode == IPaymaster.PostOpMode.opSucceeded; + emitUserOperationEvent(opInfo, success, actualGasCost, actualGas); + } + } // unchecked + } + + /** + * The gas price this UserOp agrees to pay. + * Relayer/block builder might submit the TX with higher priorityFee, but the user should not. + * @param mUserOp - The userOp to get the gas price from. + */ + function getUserOpGasPrice(MemoryUserOp memory mUserOp) internal view returns (uint256) { + unchecked { + uint256 maxFeePerGas = mUserOp.maxFeePerGas; + uint256 maxPriorityFeePerGas = mUserOp.maxPriorityFeePerGas; + if (maxFeePerGas == maxPriorityFeePerGas) { + //legacy mode (for networks that don't support basefee opcode) + return maxFeePerGas; + } + return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); + } + } + + /** + * The offset of the given bytes in memory. + * @param data - The bytes to get the offset of. + */ + function getOffsetOfMemoryBytes(bytes memory data) internal pure returns (uint256 offset) { + assembly { + offset := data + } + } + + /** + * The bytes in memory at the given offset. + * @param offset - The offset to get the bytes from. + */ + function getMemoryBytesFromOffset(uint256 offset) internal pure returns (bytes memory data) { + assembly ("memory-safe") { + data := offset + } + } + + /// @inheritdoc IEntryPoint + function delegateAndRevert(address target, bytes calldata data) external { + (bool success, bytes memory ret) = target.delegatecall(data); + revert DelegateAndRevert(success, ret); + } +} diff --git a/contracts/vendor/erc4337-entrypoint/core/Helpers.sol b/contracts/vendor/erc4337-entrypoint/core/Helpers.sol new file mode 100644 index 00000000000..cab842e29cb --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/core/Helpers.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +/* solhint-disable no-inline-assembly */ + +/* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * must return this value in case of signature failure, instead of revert. + */ +uint256 constant SIG_VALIDATION_FAILED = 1; + +/* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * return this value on success. + */ +uint256 constant SIG_VALIDATION_SUCCESS = 0; + +/** + * Returned data from validateUserOp. + * validateUserOp returns a uint256, which is created by `_packedValidationData` and + * parsed by `_parseValidationData`. + * @param aggregator - address(0) - The account validated the signature by itself. + * address(1) - The account failed to validate the signature. + * otherwise - This is an address of a signature aggregator that must + * be used to validate the signature. + * @param validAfter - This UserOp is valid only after this timestamp. + * @param validaUntil - This UserOp is valid only up to this timestamp. + */ +struct ValidationData { + address aggregator; + uint48 validAfter; + uint48 validUntil; +} + +/** + * Extract sigFailed, validAfter, validUntil. + * Also convert zero validUntil to type(uint48).max. + * @param validationData - The packed validation data. + */ +function _parseValidationData(uint256 validationData) pure returns (ValidationData memory data) { + address aggregator = address(uint160(validationData)); + uint48 validUntil = uint48(validationData >> 160); + if (validUntil == 0) { + validUntil = type(uint48).max; + } + uint48 validAfter = uint48(validationData >> (48 + 160)); + return ValidationData(aggregator, validAfter, validUntil); +} + +/** + * Helper to pack the return value for validateUserOp. + * @param data - The ValidationData to pack. + */ +function _packValidationData(ValidationData memory data) pure returns (uint256) { + return uint160(data.aggregator) | (uint256(data.validUntil) << 160) | (uint256(data.validAfter) << (160 + 48)); +} + +/** + * Helper to pack the return value for validateUserOp, when not using an aggregator. + * @param sigFailed - True for signature failure, false for success. + * @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite). + * @param validAfter - First timestamp this UserOperation is valid. + */ +function _packValidationData(bool sigFailed, uint48 validUntil, uint48 validAfter) pure returns (uint256) { + return (sigFailed ? 1 : 0) | (uint256(validUntil) << 160) | (uint256(validAfter) << (160 + 48)); +} + +/** + * keccak function over calldata. + * @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it. + */ +function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) { + assembly ("memory-safe") { + let mem := mload(0x40) + let len := data.length + calldatacopy(mem, data.offset, len) + ret := keccak256(mem, len) + } +} + +/** + * The minimum of two numbers. + * @param a - First number. + * @param b - Second number. + */ +function min(uint256 a, uint256 b) pure returns (uint256) { + return a < b ? a : b; +} diff --git a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol new file mode 100644 index 00000000000..ec16026ead3 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +import "../interfaces/INonceManager.sol"; + +/** + * nonce management functionality + */ +abstract contract NonceManager is INonceManager { + /** + * The next valid sequence number for a given nonce key. + */ + mapping(address => mapping(uint192 => uint256)) public nonceSequenceNumber; + + /// @inheritdoc INonceManager + function getNonce(address sender, uint192 key) public view override returns (uint256 nonce) { + return nonceSequenceNumber[sender][key] | (uint256(key) << 64); + } + + // allow an account to manually increment its own nonce. + // (mainly so that during construction nonce can be made non-zero, + // to "absorb" the gas cost of first nonce increment to 1st transaction (construction), + // not to 2nd transaction) + function incrementNonce(uint192 key) public override { + nonceSequenceNumber[msg.sender][key]++; + } + + /** + * validate nonce uniqueness for this account. + * called just after validateUserOp() + * @return true if the nonce was incremented successfully. + * false if the current nonce doesn't match the given one. + */ + function _validateAndUpdateNonce(address sender, uint256 nonce) internal returns (bool) { + uint192 key = uint192(nonce >> 64); + uint64 seq = uint64(nonce); + return nonceSequenceNumber[sender][key]++ == seq; + } +} diff --git a/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol b/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol new file mode 100644 index 00000000000..91ac6c882e1 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +/** + * Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address, + * which is explicitly not the entryPoint itself. + */ +contract SenderCreator { + /** + * Call the "initCode" factory to create and return the sender account address. + * @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address, + * followed by calldata. + * @return sender - The returned address of the created account, or zero address on failure. + */ + function createSender(bytes calldata initCode) external returns (address sender) { + address factory = address(bytes20(initCode[0:20])); + bytes memory initCallData = initCode[20:]; + bool success; + /* solhint-disable no-inline-assembly */ + assembly ("memory-safe") { + success := call(gas(), factory, 0, add(initCallData, 0x20), mload(initCallData), 0, 32) + sender := mload(0) + } + if (!success) { + sender = address(0); + } + } +} diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol new file mode 100644 index 00000000000..7e11373d0c8 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.23; + +import "../interfaces/IStakeManager.sol"; + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable not-rely-on-time */ + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by a paymaster. + */ +abstract contract StakeManager is IStakeManager { + /// maps paymaster to their deposits and stakes + mapping(address => DepositInfo) public deposits; + + /// @inheritdoc IStakeManager + function getDepositInfo(address account) public view returns (DepositInfo memory info) { + return deposits[account]; + } + + /** + * Internal method to return just the stake info. + * @param addr - The account to query. + */ + function _getStakeInfo(address addr) internal view returns (StakeInfo memory info) { + DepositInfo storage depositInfo = deposits[addr]; + info.stake = depositInfo.stake; + info.unstakeDelaySec = depositInfo.unstakeDelaySec; + } + + /// @inheritdoc IStakeManager + function balanceOf(address account) public view returns (uint256) { + return deposits[account].deposit; + } + + receive() external payable { + depositTo(msg.sender); + } + + /** + * Increments an account's deposit. + * @param account - The account to increment. + * @param amount - The amount to increment by. + * @return the updated deposit of this account + */ + function _incrementDeposit(address account, uint256 amount) internal returns (uint256) { + DepositInfo storage info = deposits[account]; + uint256 newAmount = info.deposit + amount; + info.deposit = newAmount; + return newAmount; + } + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) public payable virtual { + uint256 newDeposit = _incrementDeposit(account, msg.value); + emit Deposited(account, newDeposit); + } + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param unstakeDelaySec The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 unstakeDelaySec) public payable { + DepositInfo storage info = deposits[msg.sender]; + require(unstakeDelaySec > 0, "must specify unstake delay"); + require(unstakeDelaySec >= info.unstakeDelaySec, "cannot decrease unstake time"); + uint256 stake = info.stake + msg.value; + require(stake > 0, "no stake specified"); + require(stake <= type(uint112).max, "stake overflow"); + deposits[msg.sender] = DepositInfo(info.deposit, true, uint112(stake), unstakeDelaySec, 0); + emit StakeLocked(msg.sender, stake, unstakeDelaySec); + } + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external { + DepositInfo storage info = deposits[msg.sender]; + require(info.unstakeDelaySec != 0, "not staked"); + require(info.staked, "already unstaking"); + uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec; + info.withdrawTime = withdrawTime; + info.staked = false; + emit StakeUnlocked(msg.sender, withdrawTime); + } + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external { + DepositInfo storage info = deposits[msg.sender]; + uint256 stake = info.stake; + require(stake > 0, "No stake to withdraw"); + require(info.withdrawTime > 0, "must call unlockStake() first"); + require(info.withdrawTime <= block.timestamp, "Stake withdrawal is not due"); + info.unstakeDelaySec = 0; + info.withdrawTime = 0; + info.stake = 0; + emit StakeWithdrawn(msg.sender, withdrawAddress, stake); + (bool success, ) = withdrawAddress.call{value: stake}(""); + require(success, "failed to withdraw stake"); + } + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external { + DepositInfo storage info = deposits[msg.sender]; + require(withdrawAmount <= info.deposit, "Withdraw amount too large"); + info.deposit = info.deposit - withdrawAmount; + emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount); + (bool success, ) = withdrawAddress.call{value: withdrawAmount}(""); + require(success, "failed to withdraw"); + } +} diff --git a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol new file mode 100644 index 00000000000..f4049999fce --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +/* solhint-disable no-inline-assembly */ + +import "../interfaces/PackedUserOperation.sol"; +import {calldataKeccak, min} from "./Helpers.sol"; + +/** + * Utility functions helpful when working with UserOperation structs. + */ +library UserOperationLib { + uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20; + uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36; + uint256 public constant PAYMASTER_DATA_OFFSET = 52; + /** + * Get sender from user operation data. + * @param userOp - The user operation data. + */ + function getSender(PackedUserOperation calldata userOp) internal pure returns (address) { + address data; + //read sender from userOp, which is first userOp member (saves 800 gas...) + assembly { + data := calldataload(userOp) + } + return address(uint160(data)); + } + + /** + * Relayer/block builder might submit the TX with higher priorityFee, + * but the user should not pay above what he signed for. + * @param userOp - The user operation data. + */ + function gasPrice(PackedUserOperation calldata userOp) internal view returns (uint256) { + unchecked { + (uint256 maxPriorityFeePerGas, uint256 maxFeePerGas) = unpackUints(userOp.gasFees); + if (maxFeePerGas == maxPriorityFeePerGas) { + //legacy mode (for networks that don't support basefee opcode) + return maxFeePerGas; + } + return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee); + } + } + + /** + * Pack the user operation data into bytes for hashing. + * @param userOp - The user operation data. + */ + function encode(PackedUserOperation calldata userOp) internal pure returns (bytes memory ret) { + address sender = getSender(userOp); + uint256 nonce = userOp.nonce; + bytes32 hashInitCode = calldataKeccak(userOp.initCode); + bytes32 hashCallData = calldataKeccak(userOp.callData); + bytes32 accountGasLimits = userOp.accountGasLimits; + uint256 preVerificationGas = userOp.preVerificationGas; + bytes32 gasFees = userOp.gasFees; + bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData); + + return + abi.encode( + sender, + nonce, + hashInitCode, + hashCallData, + accountGasLimits, + preVerificationGas, + gasFees, + hashPaymasterAndData + ); + } + + function unpackUints(bytes32 packed) internal pure returns (uint256 high128, uint256 low128) { + return (uint128(bytes16(packed)), uint128(uint256(packed))); + } + + //unpack just the high 128-bits from a packed value + function unpackHigh128(bytes32 packed) internal pure returns (uint256) { + return uint256(packed) >> 128; + } + + // unpack just the low 128-bits from a packed value + function unpackLow128(bytes32 packed) internal pure returns (uint256) { + return uint128(uint256(packed)); + } + + function unpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackHigh128(userOp.gasFees); + } + + function unpackMaxFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackLow128(userOp.gasFees); + } + + function unpackVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackHigh128(userOp.accountGasLimits); + } + + function unpackCallGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return unpackLow128(userOp.accountGasLimits); + } + + function unpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])); + } + + function unpackPostOpGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])); + } + + function unpackPaymasterStaticFields( + bytes calldata paymasterAndData + ) internal pure returns (address paymaster, uint256 validationGasLimit, uint256 postOpGasLimit) { + return ( + address(bytes20(paymasterAndData[:PAYMASTER_VALIDATION_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])) + ); + } + + /** + * Hash the user operation data. + * @param userOp - The user operation data. + */ + function hash(PackedUserOperation calldata userOp) internal pure returns (bytes32) { + return keccak256(encode(userOp)); + } +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol new file mode 100644 index 00000000000..e3b355fbc27 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +import "./PackedUserOperation.sol"; + +interface IAccount { + /** + * Validate user's signature and nonce + * the entryPoint will make the call to the recipient only if this validation call returns successfully. + * signature failure should be reported by returning SIG_VALIDATION_FAILED (1). + * This allows making a "simulation call" without a valid signature + * Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. + * + * @dev Must validate caller is the entryPoint. + * Must validate the signature and nonce + * @param userOp - The operation that is about to be executed. + * @param userOpHash - Hash of the user's request data. can be used as the basis for signature. + * @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. + * This is the minimum amount to transfer to the sender(entryPoint) to be + * able to make the call. The excess is left as a deposit in the entrypoint + * for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". + * In case there is a paymaster in the request (or the current deposit is high + * enough), this value will be zero. + * @return validationData - Packaged ValidationData structure. use `_packValidationData` and + * `_unpackValidationData` to encode and decode. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * otherwise, an address of an "authorizer" contract. + * <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - First timestamp this operation is valid + * If an account doesn't use time-range, it is enough to + * return SIG_VALIDATION_FAILED value (1) for signature failure. + * Note that the validation code cannot use block.timestamp (or block.number) directly. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol new file mode 100644 index 00000000000..3fad1ca44f7 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +import "./PackedUserOperation.sol"; + +interface IAccountExecute { + /** + * Account may implement this execute method. + * passing this methodSig at the beginning of callData will cause the entryPoint to pass the full UserOp (and hash) + * to the account. + * The account should skip the methodSig, and use the callData (and optionally, other UserOp fields) + * + * @param userOp - The operation that was just validated. + * @param userOpHash - Hash of the user's request data. + */ + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol new file mode 100644 index 00000000000..b94d78d8092 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +import "./PackedUserOperation.sol"; + +/** + * Aggregated Signatures validator. + */ +interface IAggregator { + /** + * Validate aggregated signature. + * Revert if the aggregated signature does not match the given list of operations. + * @param userOps - Array of UserOperations to validate the signature for. + * @param signature - The aggregated signature. + */ + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; + + /** + * Validate signature of a single userOp. + * This method should be called by bundler after EntryPointSimulation.simulateValidation() returns + * the aggregator this account uses. + * First it validates the signature over the userOp. Then it returns data to be used when creating the handleOps. + * @param userOp - The userOperation received from the user. + * @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps. + * (usually empty, unless account and aggregator support some kind of "multisig". + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * Aggregate multiple signatures into a single value. + * This method is called off-chain to calculate the signature to pass with handleOps() + * bundler MAY use optimized custom code perform this aggregation. + * @param userOps - Array of UserOperations to collect the signatures from. + * @return aggregatedSignature - The aggregated signature. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatedSignature); +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol new file mode 100644 index 00000000000..169b9603ec8 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -0,0 +1,204 @@ +/** + ** Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + ** Only one instance required on each chain. + **/ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ +/* solhint-disable reason-string */ + +import "./PackedUserOperation.sol"; +import "./IStakeManager.sol"; +import "./IAggregator.sol"; +import "./INonceManager.sol"; + +interface IEntryPoint is IStakeManager, INonceManager { + /*** + * An event emitted after each successful request. + * @param userOpHash - Unique identifier for the request (hash its entire content, except signature). + * @param sender - The account that generates this request. + * @param paymaster - If non-null, the paymaster that pays for this request. + * @param nonce - The nonce value from the request. + * @param success - True if the sender transaction succeeded, false if reverted. + * @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation. + * @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation, + * validation and execution). + */ + event UserOperationEvent( + bytes32 indexed userOpHash, + address indexed sender, + address indexed paymaster, + uint256 nonce, + bool success, + uint256 actualGasCost, + uint256 actualGasUsed + ); + + /** + * Account "sender" was deployed. + * @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow. + * @param sender - The account that is deployed + * @param factory - The factory used to deploy this account (in the initCode) + * @param paymaster - The paymaster used by this UserOp + */ + event AccountDeployed(bytes32 indexed userOpHash, address indexed sender, address factory, address paymaster); + + /** + * An event emitted if the UserOperation "callData" reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event UserOperationRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); + + /** + * An event emitted if the UserOperation Paymaster's "postOp" call reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event PostOpRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason); + + /** + * UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + */ + event UserOperationPrefundTooLow(bytes32 indexed userOpHash, address indexed sender, uint256 nonce); + + /** + * An event emitted by handleOps(), before starting the execution loop. + * Any event emitted before this event, is part of the validation. + */ + event BeforeExecution(); + + /** + * Signature aggregator used by the following UserOperationEvents within this bundle. + * @param aggregator - The aggregator used for the following UserOperationEvents. + */ + event SignatureAggregatorChanged(address indexed aggregator); + + /** + * A custom revert error of handleOps, to identify the offending op. + * Should be caught in off-chain handleOps simulation and not happen on-chain. + * Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts. + * NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. The string starts with a unique code "AAmn", + * where "m" is "1" for factory, "2" for account and "3" for paymaster issues, + * so a failure can be attributed to the correct entity. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * A custom revert error of handleOps, to report a revert by account or paymaster. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. see FailedOp(uint256,string), above + * @param inner - data from inner cought revert reason + * @dev note that inner is truncated to 2048 bytes + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + error PostOpReverted(bytes returnData); + + /** + * Error case when a signature aggregator fails to verify the aggregated signature it had created. + * @param aggregator The aggregator that failed to verify the signature + */ + error SignatureValidationFailed(address aggregator); + + // Return value of getSenderAddress. + error SenderAddressResult(address sender); + + // UserOps handled, per aggregator. + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + // Aggregator address + IAggregator aggregator; + // Aggregated signature + bytes signature; + } + + /** + * Execute a batch of UserOperations. + * No signature aggregator is used. + * If any account requires an aggregator (that is, it returned an aggregator when + * performing simulateValidation), then handleAggregatedOps() must be used instead. + * @param ops - The operations to execute. + * @param beneficiary - The address to receive the fees. + */ + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + + /** + * Execute a batch of UserOperation with Aggregators + * @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts). + * @param beneficiary - The address to receive the fees. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; + + /** + * Generate a request Id - unique identifier for this request. + * The request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid. + * @param userOp - The user operation to generate the request ID for. + * @return hash the hash of this UserOperation + */ + function getUserOpHash(PackedUserOperation calldata userOp) external view returns (bytes32); + + /** + * Gas and return values during simulation. + * @param preOpGas - The gas used for validation (including preValidationGas) + * @param prefund - The required prefund for this operation + * @param accountValidationData - returned validationData from account. + * @param paymasterValidationData - return validationData from paymaster. + * @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp) + */ + struct ReturnInfo { + uint256 preOpGas; + uint256 prefund; + uint256 accountValidationData; + uint256 paymasterValidationData; + bytes paymasterContext; + } + + /** + * Returned aggregated signature info: + * The aggregator returned by the account, and its current stake. + */ + struct AggregatorStakeInfo { + address aggregator; + StakeInfo stakeInfo; + } + + /** + * Get counterfactual sender address. + * Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation. + * This method always revert, and returns the address in SenderAddressResult error + * @param initCode - The constructor code to be passed into the UserOperation. + */ + function getSenderAddress(bytes memory initCode) external; + + error DelegateAndRevert(bool success, bytes ret); + + /** + * Helper method for dry-run testing. + * @dev calling this method, the EntryPoint will make a delegatecall to the given data, and report (via revert) the result. + * The method always revert, so is only useful off-chain for dry run calls, in cases where state-override to replace + * actual EntryPoint code is less convenient. + * @param target a target contract to make a delegatecall from entrypoint + * @param data data to pass to target in a delegatecall + */ + function delegateAndRevert(address target, bytes calldata data) external; +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol b/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol new file mode 100644 index 00000000000..2a5a2725fd7 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +interface INonceManager { + /** + * Return the next nonce for this sender. + * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop) + * But UserOp with different keys can come with arbitrary order. + * + * @param sender the account address + * @param key the high 192 bit of the nonce + * @return nonce a full nonce to pass for next UserOp with this sender. + */ + function getNonce(address sender, uint192 key) external view returns (uint256 nonce); + + /** + * Manually increment the nonce of the sender. + * This method is exposed just for completeness.. + * Account does NOT need to call it, neither during validation, nor elsewhere, + * as the EntryPoint will update the nonce regardless. + * Possible use-case is call it with various keys to "initialize" their nonces to one, so that future + * UserOperations will not pay extra for the first transaction with a given key. + */ + function incrementNonce(uint192 key) external; +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol new file mode 100644 index 00000000000..9176a0b242a --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +import "./PackedUserOperation.sol"; + +/** + * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. + * A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + */ +interface IPaymaster { + enum PostOpMode { + // User op succeeded. + opSucceeded, + // User op reverted. Still has to pay for gas. + opReverted, + // Only used internally in the EntryPoint (cleanup after postOp reverts). Never calling paymaster with this value + postOpReverted + } + + /** + * Payment validation: check if paymaster agrees to pay. + * Must verify sender is the entryPoint. + * Revert to reject this request. + * Note that bundlers will reject this method if it changes the state, unless the paymaster is trusted (whitelisted). + * The paymaster pre-pays using its deposit, and receive back a refund after the postOp method returns. + * @param userOp - The user operation. + * @param userOpHash - Hash of the user's request data. + * @param maxCost - The maximum cost of this transaction (based on maximum gas and gas price from userOp). + * @return context - Value to send to a postOp. Zero length to signify postOp is not required. + * @return validationData - Signature and time-range of this operation, encoded the same as the return + * value of validateUserOperation. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * other values are invalid for paymaster. + * <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - first timestamp this operation is valid + * Note that the validation code cannot use block.timestamp (or block.number) directly. + */ + function validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); + + /** + * Post-operation handler. + * Must verify sender is the entryPoint. + * @param mode - Enum with the following options: + * opSucceeded - User operation succeeded. + * opReverted - User op reverted. The paymaster still has to pay for gas. + * postOpReverted - never passed in a call to postOp(). + * @param context - The context value returned by validatePaymasterUserOp + * @param actualGasCost - Actual gas used so far (without this postOp call). + * @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas + * and maxPriorityFee (and basefee) + * It is not the same as tx.gasprice, which is what the bundler pays. + */ + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) external; +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol new file mode 100644 index 00000000000..01612d9c0b4 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.7.5; + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by the staked entity. + */ +interface IStakeManager { + event Deposited(address indexed account, uint256 totalDeposit); + + event Withdrawn(address indexed account, address withdrawAddress, uint256 amount); + + // Emitted when stake or unstake delay are modified. + event StakeLocked(address indexed account, uint256 totalStaked, uint256 unstakeDelaySec); + + // Emitted once a stake is scheduled for withdrawal. + event StakeUnlocked(address indexed account, uint256 withdrawTime); + + event StakeWithdrawn(address indexed account, address withdrawAddress, uint256 amount); + + /** + * @param deposit - The entity's deposit. + * @param staked - True if this entity is staked. + * @param stake - Actual amount of ether staked for this entity. + * @param unstakeDelaySec - Minimum delay to withdraw the stake. + * @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked. + * @dev Sizes were chosen so that deposit fits into one cell (used during handleOp) + * and the rest fit into a 2nd cell (used during stake/unstake) + * - 112 bit allows for 10^15 eth + * - 48 bit for full timestamp + * - 32 bit allows 150 years for unstake delay + */ + struct DepositInfo { + uint256 deposit; + bool staked; + uint112 stake; + uint32 unstakeDelaySec; + uint48 withdrawTime; + } + + // API struct used by getStakeInfo and simulateValidation. + struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; + } + + /** + * Get deposit info. + * @param account - The account to query. + * @return info - Full deposit information of given account. + */ + function getDepositInfo(address account) external view returns (DepositInfo memory info); + + /** + * Get account balance. + * @param account - The account to query. + * @return - The deposit (for gas payment) of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) external payable; + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 _unstakeDelaySec) external payable; + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external; + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external; + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; +} diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/PackedUserOperation.sol b/contracts/vendor/erc4337-entrypoint/interfaces/PackedUserOperation.sol new file mode 100644 index 00000000000..fe20de56573 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/interfaces/PackedUserOperation.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; +} diff --git a/contracts/vendor/erc4337-entrypoint/utils/Exec.sol b/contracts/vendor/erc4337-entrypoint/utils/Exec.sol new file mode 100644 index 00000000000..a245deddd5a --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/utils/Exec.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.23; + +// solhint-disable no-inline-assembly + +/** + * Utility functions helpful when making different kinds of contract calls in Solidity. + */ +library Exec { + function call(address to, uint256 value, bytes memory data, uint256 txGas) internal returns (bool success) { + assembly ("memory-safe") { + success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) + } + } + + function staticcall(address to, bytes memory data, uint256 txGas) internal view returns (bool success) { + assembly ("memory-safe") { + success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } + + function delegateCall(address to, bytes memory data, uint256 txGas) internal returns (bool success) { + assembly ("memory-safe") { + success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } + + // get returned data from last call or calldelegate + function getReturnData(uint256 maxLen) internal pure returns (bytes memory returnData) { + assembly ("memory-safe") { + let len := returndatasize() + if gt(len, maxLen) { + len := maxLen + } + let ptr := mload(0x40) + mstore(0x40, add(ptr, add(len, 0x20))) + mstore(ptr, len) + returndatacopy(add(ptr, 0x20), 0, len) + returnData := ptr + } + } + + // revert with explicit byte array (probably reverted info from call) + function revertWithData(bytes memory returnData) internal pure { + assembly ("memory-safe") { + revert(add(returnData, 32), mload(returnData)) + } + } + + function callAndRevert(address to, bytes memory data, uint256 maxLen) internal { + bool success = call(to, 0, data, gasleft()); + if (!success) { + revertWithData(getReturnData(maxLen)); + } + } +} diff --git a/scripts/generate/templates/Packing.opts.js b/scripts/generate/templates/Packing.opts.js index de9ab77ff53..893ad6297cf 100644 --- a/scripts/generate/templates/Packing.opts.js +++ b/scripts/generate/templates/Packing.opts.js @@ -1,3 +1,3 @@ module.exports = { - SIZES: [1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], + SIZES: [1, 2, 4, 6, 8, 10, 12, 16, 20, 22, 24, 28, 32], }; diff --git a/scripts/generate/templates/Packing.t.js b/scripts/generate/templates/Packing.t.js index 56e9c0cc7c4..1feec28f5a5 100644 --- a/scripts/generate/templates/Packing.t.js +++ b/scripts/generate/templates/Packing.t.js @@ -11,14 +11,14 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; `; const testPack = (left, right) => `\ -function testPack(bytes${left} left, bytes${right} right) external { +function testPack(bytes${left} left, bytes${right} right) external pure { assertEq(left, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${left}(0)); assertEq(right, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${right}(${left})); } `; const testReplace = (outer, inner) => `\ -function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external { +function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, ${outer - inner})); bytes${inner} oldValue = container.extract_${outer}_${inner}(offset); diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index 9531f1bffbb..40f052c80ff 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -9,182 +9,287 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; contract PackingTest is Test { using Packing for *; - function testPack(bytes1 left, bytes1 right) external { + function testPack(bytes1 left, bytes1 right) external pure { assertEq(left, Packing.pack_1_1(left, right).extract_2_1(0)); assertEq(right, Packing.pack_1_1(left, right).extract_2_1(1)); } - function testPack(bytes2 left, bytes2 right) external { + function testPack(bytes2 left, bytes2 right) external pure { assertEq(left, Packing.pack_2_2(left, right).extract_4_2(0)); assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2)); } - function testPack(bytes2 left, bytes4 right) external { + function testPack(bytes2 left, bytes4 right) external pure { assertEq(left, Packing.pack_2_4(left, right).extract_6_2(0)); assertEq(right, Packing.pack_2_4(left, right).extract_6_4(2)); } - function testPack(bytes2 left, bytes6 right) external { + function testPack(bytes2 left, bytes6 right) external pure { assertEq(left, Packing.pack_2_6(left, right).extract_8_2(0)); assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); } - function testPack(bytes4 left, bytes2 right) external { + function testPack(bytes2 left, bytes8 right) external pure { + assertEq(left, Packing.pack_2_8(left, right).extract_10_2(0)); + assertEq(right, Packing.pack_2_8(left, right).extract_10_8(2)); + } + + function testPack(bytes2 left, bytes10 right) external pure { + assertEq(left, Packing.pack_2_10(left, right).extract_12_2(0)); + assertEq(right, Packing.pack_2_10(left, right).extract_12_10(2)); + } + + function testPack(bytes2 left, bytes20 right) external pure { + assertEq(left, Packing.pack_2_20(left, right).extract_22_2(0)); + assertEq(right, Packing.pack_2_20(left, right).extract_22_20(2)); + } + + function testPack(bytes2 left, bytes22 right) external pure { + assertEq(left, Packing.pack_2_22(left, right).extract_24_2(0)); + assertEq(right, Packing.pack_2_22(left, right).extract_24_22(2)); + } + + function testPack(bytes4 left, bytes2 right) external pure { assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); } - function testPack(bytes4 left, bytes4 right) external { + function testPack(bytes4 left, bytes4 right) external pure { assertEq(left, Packing.pack_4_4(left, right).extract_8_4(0)); assertEq(right, Packing.pack_4_4(left, right).extract_8_4(4)); } - function testPack(bytes4 left, bytes8 right) external { + function testPack(bytes4 left, bytes6 right) external pure { + assertEq(left, Packing.pack_4_6(left, right).extract_10_4(0)); + assertEq(right, Packing.pack_4_6(left, right).extract_10_6(4)); + } + + function testPack(bytes4 left, bytes8 right) external pure { assertEq(left, Packing.pack_4_8(left, right).extract_12_4(0)); assertEq(right, Packing.pack_4_8(left, right).extract_12_8(4)); } - function testPack(bytes4 left, bytes12 right) external { + function testPack(bytes4 left, bytes12 right) external pure { assertEq(left, Packing.pack_4_12(left, right).extract_16_4(0)); assertEq(right, Packing.pack_4_12(left, right).extract_16_12(4)); } - function testPack(bytes4 left, bytes16 right) external { + function testPack(bytes4 left, bytes16 right) external pure { assertEq(left, Packing.pack_4_16(left, right).extract_20_4(0)); assertEq(right, Packing.pack_4_16(left, right).extract_20_16(4)); } - function testPack(bytes4 left, bytes20 right) external { + function testPack(bytes4 left, bytes20 right) external pure { assertEq(left, Packing.pack_4_20(left, right).extract_24_4(0)); assertEq(right, Packing.pack_4_20(left, right).extract_24_20(4)); } - function testPack(bytes4 left, bytes24 right) external { + function testPack(bytes4 left, bytes24 right) external pure { assertEq(left, Packing.pack_4_24(left, right).extract_28_4(0)); assertEq(right, Packing.pack_4_24(left, right).extract_28_24(4)); } - function testPack(bytes4 left, bytes28 right) external { + function testPack(bytes4 left, bytes28 right) external pure { assertEq(left, Packing.pack_4_28(left, right).extract_32_4(0)); assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4)); } - function testPack(bytes6 left, bytes2 right) external { + function testPack(bytes6 left, bytes2 right) external pure { assertEq(left, Packing.pack_6_2(left, right).extract_8_6(0)); assertEq(right, Packing.pack_6_2(left, right).extract_8_2(6)); } - function testPack(bytes6 left, bytes6 right) external { + function testPack(bytes6 left, bytes4 right) external pure { + assertEq(left, Packing.pack_6_4(left, right).extract_10_6(0)); + assertEq(right, Packing.pack_6_4(left, right).extract_10_4(6)); + } + + function testPack(bytes6 left, bytes6 right) external pure { assertEq(left, Packing.pack_6_6(left, right).extract_12_6(0)); assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); } - function testPack(bytes8 left, bytes4 right) external { + function testPack(bytes6 left, bytes10 right) external pure { + assertEq(left, Packing.pack_6_10(left, right).extract_16_6(0)); + assertEq(right, Packing.pack_6_10(left, right).extract_16_10(6)); + } + + function testPack(bytes6 left, bytes16 right) external pure { + assertEq(left, Packing.pack_6_16(left, right).extract_22_6(0)); + assertEq(right, Packing.pack_6_16(left, right).extract_22_16(6)); + } + + function testPack(bytes6 left, bytes22 right) external pure { + assertEq(left, Packing.pack_6_22(left, right).extract_28_6(0)); + assertEq(right, Packing.pack_6_22(left, right).extract_28_22(6)); + } + + function testPack(bytes8 left, bytes2 right) external pure { + assertEq(left, Packing.pack_8_2(left, right).extract_10_8(0)); + assertEq(right, Packing.pack_8_2(left, right).extract_10_2(8)); + } + + function testPack(bytes8 left, bytes4 right) external pure { assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0)); assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8)); } - function testPack(bytes8 left, bytes8 right) external { + function testPack(bytes8 left, bytes8 right) external pure { assertEq(left, Packing.pack_8_8(left, right).extract_16_8(0)); assertEq(right, Packing.pack_8_8(left, right).extract_16_8(8)); } - function testPack(bytes8 left, bytes12 right) external { + function testPack(bytes8 left, bytes12 right) external pure { assertEq(left, Packing.pack_8_12(left, right).extract_20_8(0)); assertEq(right, Packing.pack_8_12(left, right).extract_20_12(8)); } - function testPack(bytes8 left, bytes16 right) external { + function testPack(bytes8 left, bytes16 right) external pure { assertEq(left, Packing.pack_8_16(left, right).extract_24_8(0)); assertEq(right, Packing.pack_8_16(left, right).extract_24_16(8)); } - function testPack(bytes8 left, bytes20 right) external { + function testPack(bytes8 left, bytes20 right) external pure { assertEq(left, Packing.pack_8_20(left, right).extract_28_8(0)); assertEq(right, Packing.pack_8_20(left, right).extract_28_20(8)); } - function testPack(bytes8 left, bytes24 right) external { + function testPack(bytes8 left, bytes24 right) external pure { assertEq(left, Packing.pack_8_24(left, right).extract_32_8(0)); assertEq(right, Packing.pack_8_24(left, right).extract_32_24(8)); } - function testPack(bytes12 left, bytes4 right) external { + function testPack(bytes10 left, bytes2 right) external pure { + assertEq(left, Packing.pack_10_2(left, right).extract_12_10(0)); + assertEq(right, Packing.pack_10_2(left, right).extract_12_2(10)); + } + + function testPack(bytes10 left, bytes6 right) external pure { + assertEq(left, Packing.pack_10_6(left, right).extract_16_10(0)); + assertEq(right, Packing.pack_10_6(left, right).extract_16_6(10)); + } + + function testPack(bytes10 left, bytes10 right) external pure { + assertEq(left, Packing.pack_10_10(left, right).extract_20_10(0)); + assertEq(right, Packing.pack_10_10(left, right).extract_20_10(10)); + } + + function testPack(bytes10 left, bytes12 right) external pure { + assertEq(left, Packing.pack_10_12(left, right).extract_22_10(0)); + assertEq(right, Packing.pack_10_12(left, right).extract_22_12(10)); + } + + function testPack(bytes10 left, bytes22 right) external pure { + assertEq(left, Packing.pack_10_22(left, right).extract_32_10(0)); + assertEq(right, Packing.pack_10_22(left, right).extract_32_22(10)); + } + + function testPack(bytes12 left, bytes4 right) external pure { assertEq(left, Packing.pack_12_4(left, right).extract_16_12(0)); assertEq(right, Packing.pack_12_4(left, right).extract_16_4(12)); } - function testPack(bytes12 left, bytes8 right) external { + function testPack(bytes12 left, bytes8 right) external pure { assertEq(left, Packing.pack_12_8(left, right).extract_20_12(0)); assertEq(right, Packing.pack_12_8(left, right).extract_20_8(12)); } - function testPack(bytes12 left, bytes12 right) external { + function testPack(bytes12 left, bytes10 right) external pure { + assertEq(left, Packing.pack_12_10(left, right).extract_22_12(0)); + assertEq(right, Packing.pack_12_10(left, right).extract_22_10(12)); + } + + function testPack(bytes12 left, bytes12 right) external pure { assertEq(left, Packing.pack_12_12(left, right).extract_24_12(0)); assertEq(right, Packing.pack_12_12(left, right).extract_24_12(12)); } - function testPack(bytes12 left, bytes16 right) external { + function testPack(bytes12 left, bytes16 right) external pure { assertEq(left, Packing.pack_12_16(left, right).extract_28_12(0)); assertEq(right, Packing.pack_12_16(left, right).extract_28_16(12)); } - function testPack(bytes12 left, bytes20 right) external { + function testPack(bytes12 left, bytes20 right) external pure { assertEq(left, Packing.pack_12_20(left, right).extract_32_12(0)); assertEq(right, Packing.pack_12_20(left, right).extract_32_20(12)); } - function testPack(bytes16 left, bytes4 right) external { + function testPack(bytes16 left, bytes4 right) external pure { assertEq(left, Packing.pack_16_4(left, right).extract_20_16(0)); assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16)); } - function testPack(bytes16 left, bytes8 right) external { + function testPack(bytes16 left, bytes6 right) external pure { + assertEq(left, Packing.pack_16_6(left, right).extract_22_16(0)); + assertEq(right, Packing.pack_16_6(left, right).extract_22_6(16)); + } + + function testPack(bytes16 left, bytes8 right) external pure { assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0)); assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16)); } - function testPack(bytes16 left, bytes12 right) external { + function testPack(bytes16 left, bytes12 right) external pure { assertEq(left, Packing.pack_16_12(left, right).extract_28_16(0)); assertEq(right, Packing.pack_16_12(left, right).extract_28_12(16)); } - function testPack(bytes16 left, bytes16 right) external { + function testPack(bytes16 left, bytes16 right) external pure { assertEq(left, Packing.pack_16_16(left, right).extract_32_16(0)); assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16)); } - function testPack(bytes20 left, bytes4 right) external { + function testPack(bytes20 left, bytes2 right) external pure { + assertEq(left, Packing.pack_20_2(left, right).extract_22_20(0)); + assertEq(right, Packing.pack_20_2(left, right).extract_22_2(20)); + } + + function testPack(bytes20 left, bytes4 right) external pure { assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0)); assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20)); } - function testPack(bytes20 left, bytes8 right) external { + function testPack(bytes20 left, bytes8 right) external pure { assertEq(left, Packing.pack_20_8(left, right).extract_28_20(0)); assertEq(right, Packing.pack_20_8(left, right).extract_28_8(20)); } - function testPack(bytes20 left, bytes12 right) external { + function testPack(bytes20 left, bytes12 right) external pure { assertEq(left, Packing.pack_20_12(left, right).extract_32_20(0)); assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20)); } - function testPack(bytes24 left, bytes4 right) external { + function testPack(bytes22 left, bytes2 right) external pure { + assertEq(left, Packing.pack_22_2(left, right).extract_24_22(0)); + assertEq(right, Packing.pack_22_2(left, right).extract_24_2(22)); + } + + function testPack(bytes22 left, bytes6 right) external pure { + assertEq(left, Packing.pack_22_6(left, right).extract_28_22(0)); + assertEq(right, Packing.pack_22_6(left, right).extract_28_6(22)); + } + + function testPack(bytes22 left, bytes10 right) external pure { + assertEq(left, Packing.pack_22_10(left, right).extract_32_22(0)); + assertEq(right, Packing.pack_22_10(left, right).extract_32_10(22)); + } + + function testPack(bytes24 left, bytes4 right) external pure { assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0)); assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24)); } - function testPack(bytes24 left, bytes8 right) external { + function testPack(bytes24 left, bytes8 right) external pure { assertEq(left, Packing.pack_24_8(left, right).extract_32_24(0)); assertEq(right, Packing.pack_24_8(left, right).extract_32_8(24)); } - function testPack(bytes28 left, bytes4 right) external { + function testPack(bytes28 left, bytes4 right) external pure { assertEq(left, Packing.pack_28_4(left, right).extract_32_28(0)); assertEq(right, Packing.pack_28_4(left, right).extract_32_4(28)); } - function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 1)); bytes1 oldValue = container.extract_2_1(offset); @@ -193,7 +298,7 @@ contract PackingTest is Test { assertEq(container, container.replace_2_1(newValue, offset).replace_2_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 3)); bytes1 oldValue = container.extract_4_1(offset); @@ -202,7 +307,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_1(newValue, offset).replace_4_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes2 oldValue = container.extract_4_2(offset); @@ -211,7 +316,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 5)); bytes1 oldValue = container.extract_6_1(offset); @@ -220,7 +325,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_1(newValue, offset).replace_6_1(oldValue, offset)); } - function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes2 oldValue = container.extract_6_2(offset); @@ -229,7 +334,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_2(newValue, offset).replace_6_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes4 oldValue = container.extract_6_4(offset); @@ -238,7 +343,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_4(newValue, offset).replace_6_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 7)); bytes1 oldValue = container.extract_8_1(offset); @@ -247,7 +352,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_1(newValue, offset).replace_8_1(oldValue, offset)); } - function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes2 oldValue = container.extract_8_2(offset); @@ -256,7 +361,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_2(newValue, offset).replace_8_2(oldValue, offset)); } - function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes4 oldValue = container.extract_8_4(offset); @@ -265,7 +370,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes6 oldValue = container.extract_8_6(offset); @@ -274,7 +379,52 @@ contract PackingTest is Test { assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 9)); + + bytes1 oldValue = container.extract_10_1(offset); + + assertEq(newValue, container.replace_10_1(newValue, offset).extract_10_1(offset)); + assertEq(container, container.replace_10_1(newValue, offset).replace_10_1(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 8)); + + bytes2 oldValue = container.extract_10_2(offset); + + assertEq(newValue, container.replace_10_2(newValue, offset).extract_10_2(offset)); + assertEq(container, container.replace_10_2(newValue, offset).replace_10_2(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes4 oldValue = container.extract_10_4(offset); + + assertEq(newValue, container.replace_10_4(newValue, offset).extract_10_4(offset)); + assertEq(container, container.replace_10_4(newValue, offset).replace_10_4(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 4)); + + bytes6 oldValue = container.extract_10_6(offset); + + assertEq(newValue, container.replace_10_6(newValue, offset).extract_10_6(offset)); + assertEq(container, container.replace_10_6(newValue, offset).replace_10_6(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes8 oldValue = container.extract_10_8(offset); + + assertEq(newValue, container.replace_10_8(newValue, offset).extract_10_8(offset)); + assertEq(container, container.replace_10_8(newValue, offset).replace_10_8(oldValue, offset)); + } + + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 11)); bytes1 oldValue = container.extract_12_1(offset); @@ -283,7 +433,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_1(newValue, offset).replace_12_1(oldValue, offset)); } - function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes2 oldValue = container.extract_12_2(offset); @@ -292,7 +442,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_2(newValue, offset).replace_12_2(oldValue, offset)); } - function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes4 oldValue = container.extract_12_4(offset); @@ -301,7 +451,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset)); } - function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes6 oldValue = container.extract_12_6(offset); @@ -310,7 +460,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_6(newValue, offset).replace_12_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes8 oldValue = container.extract_12_8(offset); @@ -319,7 +469,16 @@ contract PackingTest is Test { assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes10 oldValue = container.extract_12_10(offset); + + assertEq(newValue, container.replace_12_10(newValue, offset).extract_12_10(offset)); + assertEq(container, container.replace_12_10(newValue, offset).replace_12_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 15)); bytes1 oldValue = container.extract_16_1(offset); @@ -328,7 +487,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_1(newValue, offset).replace_16_1(oldValue, offset)); } - function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes2 oldValue = container.extract_16_2(offset); @@ -337,7 +496,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_2(newValue, offset).replace_16_2(oldValue, offset)); } - function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes4 oldValue = container.extract_16_4(offset); @@ -346,7 +505,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset)); } - function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes6 oldValue = container.extract_16_6(offset); @@ -355,7 +514,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_6(newValue, offset).replace_16_6(oldValue, offset)); } - function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes8 oldValue = container.extract_16_8(offset); @@ -364,7 +523,16 @@ contract PackingTest is Test { assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes10 oldValue = container.extract_16_10(offset); + + assertEq(newValue, container.replace_16_10(newValue, offset).extract_16_10(offset)); + assertEq(container, container.replace_16_10(newValue, offset).replace_16_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes12 oldValue = container.extract_16_12(offset); @@ -373,7 +541,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_12(newValue, offset).replace_16_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 19)); bytes1 oldValue = container.extract_20_1(offset); @@ -382,7 +550,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_1(newValue, offset).replace_20_1(oldValue, offset)); } - function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes2 oldValue = container.extract_20_2(offset); @@ -391,7 +559,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_2(newValue, offset).replace_20_2(oldValue, offset)); } - function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes4 oldValue = container.extract_20_4(offset); @@ -400,7 +568,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset)); } - function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes6 oldValue = container.extract_20_6(offset); @@ -409,7 +577,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_6(newValue, offset).replace_20_6(oldValue, offset)); } - function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes8 oldValue = container.extract_20_8(offset); @@ -418,7 +586,16 @@ contract PackingTest is Test { assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset)); } - function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes10 oldValue = container.extract_20_10(offset); + + assertEq(newValue, container.replace_20_10(newValue, offset).extract_20_10(offset)); + assertEq(container, container.replace_20_10(newValue, offset).replace_20_10(oldValue, offset)); + } + + function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes12 oldValue = container.extract_20_12(offset); @@ -427,7 +604,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_12(newValue, offset).replace_20_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes16 oldValue = container.extract_20_16(offset); @@ -436,7 +613,88 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 21)); + + bytes1 oldValue = container.extract_22_1(offset); + + assertEq(newValue, container.replace_22_1(newValue, offset).extract_22_1(offset)); + assertEq(container, container.replace_22_1(newValue, offset).replace_22_1(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 20)); + + bytes2 oldValue = container.extract_22_2(offset); + + assertEq(newValue, container.replace_22_2(newValue, offset).extract_22_2(offset)); + assertEq(container, container.replace_22_2(newValue, offset).replace_22_2(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes4 oldValue = container.extract_22_4(offset); + + assertEq(newValue, container.replace_22_4(newValue, offset).extract_22_4(offset)); + assertEq(container, container.replace_22_4(newValue, offset).replace_22_4(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 16)); + + bytes6 oldValue = container.extract_22_6(offset); + + assertEq(newValue, container.replace_22_6(newValue, offset).extract_22_6(offset)); + assertEq(container, container.replace_22_6(newValue, offset).replace_22_6(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes8 oldValue = container.extract_22_8(offset); + + assertEq(newValue, container.replace_22_8(newValue, offset).extract_22_8(offset)); + assertEq(container, container.replace_22_8(newValue, offset).replace_22_8(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 12)); + + bytes10 oldValue = container.extract_22_10(offset); + + assertEq(newValue, container.replace_22_10(newValue, offset).extract_22_10(offset)); + assertEq(container, container.replace_22_10(newValue, offset).replace_22_10(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes12 oldValue = container.extract_22_12(offset); + + assertEq(newValue, container.replace_22_12(newValue, offset).extract_22_12(offset)); + assertEq(container, container.replace_22_12(newValue, offset).replace_22_12(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes16 oldValue = container.extract_22_16(offset); + + assertEq(newValue, container.replace_22_16(newValue, offset).extract_22_16(offset)); + assertEq(container, container.replace_22_16(newValue, offset).replace_22_16(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes20 oldValue = container.extract_22_20(offset); + + assertEq(newValue, container.replace_22_20(newValue, offset).extract_22_20(offset)); + assertEq(container, container.replace_22_20(newValue, offset).replace_22_20(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 23)); bytes1 oldValue = container.extract_24_1(offset); @@ -445,7 +703,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_1(newValue, offset).replace_24_1(oldValue, offset)); } - function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes2 oldValue = container.extract_24_2(offset); @@ -454,7 +712,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_2(newValue, offset).replace_24_2(oldValue, offset)); } - function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes4 oldValue = container.extract_24_4(offset); @@ -463,7 +721,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset)); } - function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes6 oldValue = container.extract_24_6(offset); @@ -472,7 +730,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_6(newValue, offset).replace_24_6(oldValue, offset)); } - function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes8 oldValue = container.extract_24_8(offset); @@ -481,7 +739,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset)); } - function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes10 oldValue = container.extract_24_10(offset); + + assertEq(newValue, container.replace_24_10(newValue, offset).extract_24_10(offset)); + assertEq(container, container.replace_24_10(newValue, offset).replace_24_10(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes12 oldValue = container.extract_24_12(offset); @@ -490,7 +757,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_12(newValue, offset).replace_24_12(oldValue, offset)); } - function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes16 oldValue = container.extract_24_16(offset); @@ -499,7 +766,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_16(newValue, offset).replace_24_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes20 oldValue = container.extract_24_20(offset); @@ -508,7 +775,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes22 oldValue = container.extract_24_22(offset); + + assertEq(newValue, container.replace_24_22(newValue, offset).extract_24_22(offset)); + assertEq(container, container.replace_24_22(newValue, offset).replace_24_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 27)); bytes1 oldValue = container.extract_28_1(offset); @@ -517,7 +793,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_1(newValue, offset).replace_28_1(oldValue, offset)); } - function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes2 oldValue = container.extract_28_2(offset); @@ -526,7 +802,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_2(newValue, offset).replace_28_2(oldValue, offset)); } - function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes4 oldValue = container.extract_28_4(offset); @@ -535,7 +811,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset)); } - function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes6 oldValue = container.extract_28_6(offset); @@ -544,7 +820,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_6(newValue, offset).replace_28_6(oldValue, offset)); } - function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes8 oldValue = container.extract_28_8(offset); @@ -553,7 +829,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset)); } - function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes10 oldValue = container.extract_28_10(offset); + + assertEq(newValue, container.replace_28_10(newValue, offset).extract_28_10(offset)); + assertEq(container, container.replace_28_10(newValue, offset).replace_28_10(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes12 oldValue = container.extract_28_12(offset); @@ -562,7 +847,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_12(newValue, offset).replace_28_12(oldValue, offset)); } - function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes16 oldValue = container.extract_28_16(offset); @@ -571,7 +856,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_16(newValue, offset).replace_28_16(oldValue, offset)); } - function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes20 oldValue = container.extract_28_20(offset); @@ -580,7 +865,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes22 oldValue = container.extract_28_22(offset); + + assertEq(newValue, container.replace_28_22(newValue, offset).extract_28_22(offset)); + assertEq(container, container.replace_28_22(newValue, offset).replace_28_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes24 oldValue = container.extract_28_24(offset); @@ -589,7 +883,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_24(newValue, offset).replace_28_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 31)); bytes1 oldValue = container.extract_32_1(offset); @@ -598,7 +892,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_1(newValue, offset).replace_32_1(oldValue, offset)); } - function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 30)); bytes2 oldValue = container.extract_32_2(offset); @@ -607,7 +901,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_2(newValue, offset).replace_32_2(oldValue, offset)); } - function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 28)); bytes4 oldValue = container.extract_32_4(offset); @@ -616,7 +910,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset)); } - function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes6 oldValue = container.extract_32_6(offset); @@ -625,7 +919,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_6(newValue, offset).replace_32_6(oldValue, offset)); } - function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes8 oldValue = container.extract_32_8(offset); @@ -634,7 +928,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset)); } - function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 22)); + + bytes10 oldValue = container.extract_32_10(offset); + + assertEq(newValue, container.replace_32_10(newValue, offset).extract_32_10(offset)); + assertEq(container, container.replace_32_10(newValue, offset).replace_32_10(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes12 oldValue = container.extract_32_12(offset); @@ -643,7 +946,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_12(newValue, offset).replace_32_12(oldValue, offset)); } - function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes16 oldValue = container.extract_32_16(offset); @@ -652,7 +955,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_16(newValue, offset).replace_32_16(oldValue, offset)); } - function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes20 oldValue = container.extract_32_20(offset); @@ -661,7 +964,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } - function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes22 oldValue = container.extract_32_22(offset); + + assertEq(newValue, container.replace_32_22(newValue, offset).extract_32_22(offset)); + assertEq(container, container.replace_32_22(newValue, offset).replace_32_22(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes24 oldValue = container.extract_32_24(offset); @@ -670,7 +982,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_24(newValue, offset).replace_32_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes28 oldValue = container.extract_32_28(offset); From 6dc7749e0f8a046bc528a6ef1311fc23d05c0297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:11:04 -0600 Subject: [PATCH 02/41] Lint --- contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol index f4049999fce..7f6b04d3454 100644 --- a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol +++ b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol @@ -13,6 +13,7 @@ library UserOperationLib { uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20; uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36; uint256 public constant PAYMASTER_DATA_OFFSET = 52; + /** * Get sender from user operation data. * @param userOp - The user operation data. From efa9e2be20c8115b935a9a8bd129cf4855aa00a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:29:20 -0600 Subject: [PATCH 03/41] Lint --- .changeset/hot-shrimps-wait.md | 5 +++++ .changeset/small-seahorses-bathe.md | 5 +++++ .changeset/weak-roses-bathe.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/hot-shrimps-wait.md create mode 100644 .changeset/small-seahorses-bathe.md create mode 100644 .changeset/weak-roses-bathe.md diff --git a/.changeset/hot-shrimps-wait.md b/.changeset/hot-shrimps-wait.md new file mode 100644 index 00000000000..e4e96a981ad --- /dev/null +++ b/.changeset/hot-shrimps-wait.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Packing`: Add variants for packing `bytes10` and `bytes22` diff --git a/.changeset/small-seahorses-bathe.md b/.changeset/small-seahorses-bathe.md new file mode 100644 index 00000000000..d88c1807d57 --- /dev/null +++ b/.changeset/small-seahorses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC7579Utils`: Add a reusable library to interact with ERC7579 modular accounts diff --git a/.changeset/weak-roses-bathe.md b/.changeset/weak-roses-bathe.md new file mode 100644 index 00000000000..416b2e746d3 --- /dev/null +++ b/.changeset/weak-roses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts From 211770ba7dfce0a151b85ff635ca9a456021bee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:41:31 -0600 Subject: [PATCH 04/41] Remove linting for vendored contracts and change to explicit imports --- .../erc4337-entrypoint/core/EntryPoint.sol | 30 ++++++++++--------- .../erc4337-entrypoint/core/NonceManager.sol | 2 +- .../erc4337-entrypoint/core/StakeManager.sol | 2 +- .../core/UserOperationLib.sol | 2 +- .../interfaces/IAccount.sol | 2 +- .../interfaces/IAccountExecute.sol | 2 +- .../interfaces/IAggregator.sol | 2 +- .../interfaces/IEntryPoint.sol | 8 ++--- .../interfaces/IPaymaster.sol | 2 +- package.json | 4 +-- 10 files changed, 29 insertions(+), 27 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 175ea2b18d2..7ec7d53687e 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -3,22 +3,24 @@ pragma solidity ^0.8.23; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-inline-assembly */ -import "../interfaces/IAccount.sol"; -import "../interfaces/IAccountExecute.sol"; -import "../interfaces/IPaymaster.sol"; -import "../interfaces/IEntryPoint.sol"; - -import "../utils/Exec.sol"; -import "./StakeManager.sol"; -import "./SenderCreator.sol"; -import "./Helpers.sol"; -import "./NonceManager.sol"; -import "./UserOperationLib.sol"; +import {IAccount} from "../interfaces/IAccount.sol"; // OZ edit +import {IAccountExecute} from "../interfaces/IAccountExecute.sol"; // OZ edit +import {IPaymaster} from "../interfaces/IPaymaster.sol"; // OZ edit +import {IEntryPoint} from "../interfaces/IEntryPoint.sol"; // OZ edit + +import {Exec} from "../utils/Exec.sol"; // OZ edit +import {IStakeManager, StakeManager} from "./StakeManager.sol"; // OZ edit +import {SenderCreator} from "./SenderCreator.sol"; // OZ edit +import {ValidationData, _parseValidationData,min} from "./Helpers.sol"; // OZ edit +import {INonceManager, NonceManager} from "./NonceManager.sol"; // OZ edit +import {UserOperationLib, PackedUserOperation} from "./UserOperationLib.sol"; // OZ edit + +import {IAggregator} from "../interfaces/IAggregator.sol"; // OZ edit // import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; // import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "../../../utils/introspection/ERC165.sol"; // OZ edit -import "../../../utils/ReentrancyGuard.sol"; // OZ edit +import {ERC165} from "../../../utils/introspection/ERC165.sol"; // OZ edit +import {ReentrancyGuard} from "../../../utils/ReentrancyGuard.sol"; // OZ edit /* * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. @@ -46,7 +48,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 private constant REVERT_REASON_MAX_LEN = 2048; uint256 private constant PENALTY_PERCENT = 10; - /// @inheritdoc IERC165 + /// @inheritdoc ERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything return diff --git a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol index ec16026ead3..be4abbdd8c3 100644 --- a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; -import "../interfaces/INonceManager.sol"; +import {INonceManager} from "../interfaces/INonceManager.sol"; // OZ edit /** * nonce management functionality diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 7e11373d0c8..6ed1388c1fe 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.23; -import "../interfaces/IStakeManager.sol"; +import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit /* solhint-disable avoid-low-level-calls */ /* solhint-disable not-rely-on-time */ diff --git a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol index 7f6b04d3454..e9b8d2d0079 100644 --- a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol +++ b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; /* solhint-disable no-inline-assembly */ -import "../interfaces/PackedUserOperation.sol"; +import {PackedUserOperation} from "../interfaces/PackedUserOperation.sol"; import {calldataKeccak, min} from "./Helpers.sol"; /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol index e3b355fbc27..03c11aaa2ae 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit interface IAccount { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol index 3fad1ca44f7..99afbb43862 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit interface IAccountExecute { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol index b94d78d8092..1a8ba1f52a5 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit /** * Aggregated Signatures validator. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol index 169b9603ec8..ce1e7971277 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -9,10 +9,10 @@ pragma solidity >=0.7.5; /* solhint-disable no-inline-assembly */ /* solhint-disable reason-string */ -import "./PackedUserOperation.sol"; -import "./IStakeManager.sol"; -import "./IAggregator.sol"; -import "./INonceManager.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import {IStakeManager} from "./IStakeManager.sol"; // OZ Edit +import {IAggregator} from "./IAggregator.sol"; // OZ Edit +import {INonceManager} from "./INonceManager.sol"; // OZ Edit interface IEntryPoint is IStakeManager, INonceManager { /*** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol index 9176a0b242a..d39a60068fb 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit /** * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. diff --git a/package.json b/package.json index f7c0f519737..a82ea47dd08 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint --ignore-path .gitignore .", "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint --ignore-path .gitignore . --fix", - "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'", - "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write", + "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/!(vendor)/**/*.sol' --check && solhint '{contracts,test}/!(vendor)/**/*.sol'", + "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/!(vendor)/**/*.sol' '!contracts/vendor/**/*.sol' --write", "clean": "hardhat clean && rimraf build contracts/build", "prepack": "scripts/prepack.sh", "generate": "scripts/generate/run.js", From 1a25df6159ca4ac0c073cf25f10f0db71e95ce48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:44:37 -0600 Subject: [PATCH 05/41] Codespell --- contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol | 2 +- contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol | 2 +- test/account/utils/draft-ERC4337Utils.test.js | 0 test/account/utils/draft-ERC7579Utils.test.js | 0 4 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 test/account/utils/draft-ERC4337Utils.test.js create mode 100644 test/account/utils/draft-ERC7579Utils.test.js diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 7ec7d53687e..9f0814f5d21 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -11,7 +11,7 @@ import {IEntryPoint} from "../interfaces/IEntryPoint.sol"; // OZ edit import {Exec} from "../utils/Exec.sol"; // OZ edit import {IStakeManager, StakeManager} from "./StakeManager.sol"; // OZ edit import {SenderCreator} from "./SenderCreator.sol"; // OZ edit -import {ValidationData, _parseValidationData,min} from "./Helpers.sol"; // OZ edit +import {ValidationData, _parseValidationData, min} from "./Helpers.sol"; // OZ edit import {INonceManager, NonceManager} from "./NonceManager.sol"; // OZ edit import {UserOperationLib, PackedUserOperation} from "./UserOperationLib.sol"; // OZ edit diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol index ce1e7971277..1a08e91839d 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -104,7 +104,7 @@ interface IEntryPoint is IStakeManager, INonceManager { * A custom revert error of handleOps, to report a revert by account or paymaster. * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). * @param reason - Revert reason. see FailedOp(uint256,string), above - * @param inner - data from inner cought revert reason + * @param inner - data from inner caught revert reason * @dev note that inner is truncated to 2048 bytes */ error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js new file mode 100644 index 00000000000..e69de29bb2d From 353f278c7ce9d75ef2bbc0fa17ffa0385c534ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 20:51:39 -0600 Subject: [PATCH 06/41] Complete tests --- contracts/account/utils/draft-ERC433Utils.sol | 70 ---- .../account/utils/draft-ERC7579Utils.sol | 10 +- .../mocks/account/utils/ERC7579UtilsMock.sol | 23 ++ test/account/utils/draft-ERC4337Utils.test.js | 203 +++++++++++ test/account/utils/draft-ERC7579Utils.test.js | 319 ++++++++++++++++++ test/helpers/erc4337.js | 82 +++++ test/helpers/erc7579.js | 58 ++++ 7 files changed, 692 insertions(+), 73 deletions(-) create mode 100644 contracts/mocks/account/utils/ERC7579UtilsMock.sol create mode 100644 test/helpers/erc4337.js create mode 100644 test/helpers/erc7579.js diff --git a/contracts/account/utils/draft-ERC433Utils.sol b/contracts/account/utils/draft-ERC433Utils.sol index fddd7789715..ee2925f9023 100644 --- a/contracts/account/utils/draft-ERC433Utils.sol +++ b/contracts/account/utils/draft-ERC433Utils.sol @@ -186,74 +186,4 @@ library ERC4337Utils { function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(bytes16(self.paymasterAndData[36:52])); } - - struct UserOpInfo { - // Static fields - address sender; - uint256 nonce; - uint256 verificationGasLimit; - uint256 callGasLimit; - uint256 paymasterVerificationGasLimit; - uint256 paymasterPostOpGasLimit; - uint256 preVerificationGas; - address paymaster; - uint256 maxFeePerGas; - uint256 maxPriorityFeePerGas; - // Extra fields - bytes32 userOpHash; - uint256 prefund; - uint256 preOpGas; - bytes context; - } - - /** - * @dev Loads the {UserOpInfo} from a {PackedUserOperation}. - */ - function load(UserOpInfo memory self, PackedUserOperation calldata source) internal view { - self.sender = source.sender; - self.nonce = source.nonce; - self.verificationGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x00)); - self.callGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x10)); - self.preVerificationGas = source.preVerificationGas; - self.maxPriorityFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x00)); - self.maxFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x10)); - - if (source.paymasterAndData.length > 0) { - require(source.paymasterAndData.length >= 52, "AA93 invalid paymasterAndData"); - self.paymaster = paymaster(source); - self.paymasterVerificationGasLimit = paymasterVerificationGasLimit(source); - self.paymasterPostOpGasLimit = paymasterPostOpGasLimit(source); - } else { - self.paymaster = address(0); - self.paymasterVerificationGasLimit = 0; - self.paymasterPostOpGasLimit = 0; - } - self.userOpHash = hash(source); - self.prefund = 0; - self.preOpGas = 0; - self.context = ""; - } - - /** - * @dev Returns the required prefund for the user operation. - */ - function requiredPrefund(UserOpInfo memory self) internal pure returns (uint256) { - return - (self.verificationGasLimit + - self.callGasLimit + - self.paymasterVerificationGasLimit + - self.paymasterPostOpGasLimit + - self.preVerificationGas) * self.maxFeePerGas; - } - - /** - * @dev Returns the required prefund for the user operation. - */ - function gasPrice(UserOpInfo memory self) internal view returns (uint256) { - unchecked { - uint256 maxFee = self.maxFeePerGas; - uint256 maxPriorityFee = self.maxPriorityFeePerGas; - return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); - } - } } diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index c2ea6d91b36..565326fbe40 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -177,8 +177,10 @@ library ERC7579Utils { function decodeSingle( bytes calldata executionCalldata ) internal pure returns (address target, uint256 value, bytes calldata callData) { - target = abi.decode(executionCalldata[0:20], (address)); - value = abi.decode(executionCalldata[20:52], (uint256)); + assembly ("memory-safe") { + target := shr(96, calldataload(executionCalldata.offset)) + value := calldataload(add(executionCalldata.offset, 20)) + } callData = executionCalldata[52:]; } @@ -198,7 +200,9 @@ library ERC7579Utils { function decodeDelegate( bytes calldata executionCalldata ) internal pure returns (address target, bytes calldata callData) { - target = abi.decode(executionCalldata[0:20], (address)); + assembly ("memory-safe") { + target := shr(96, calldataload(executionCalldata.offset)) + } callData = executionCalldata[20:]; } diff --git a/contracts/mocks/account/utils/ERC7579UtilsMock.sol b/contracts/mocks/account/utils/ERC7579UtilsMock.sol new file mode 100644 index 00000000000..b64a1d53fa6 --- /dev/null +++ b/contracts/mocks/account/utils/ERC7579UtilsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {_eqCallTypeGlobal, _eqExecTypeGlobal, _eqModeSelectorGlobal, _eqModePayloadGlobal, CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; + +contract ERC7579UtilsGlobalMock { + function eqCallTypeGlobal(CallType callType1, CallType callType2) internal pure returns (bool) { + return _eqCallTypeGlobal(callType1, callType2); + } + + function eqExecTypeGlobal(ExecType execType1, ExecType execType2) internal pure returns (bool) { + return _eqExecTypeGlobal(execType1, execType2); + } + + function eqModeSelectorGlobal(ModeSelector modeSelector1, ModeSelector modeSelector2) internal pure returns (bool) { + return _eqModeSelectorGlobal(modeSelector1, modeSelector2); + } + + function eqModePayloadGlobal(ModePayload modePayload1, ModePayload modePayload2) internal pure returns (bool) { + return _eqModePayloadGlobal(modePayload1, modePayload2); + } +} diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index e69de29bb2d..a3b03e8d3d1 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -0,0 +1,203 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { + SIG_VALIDATION_SUCCESS, + SIG_VALIDATION_FAILURE, + toAuthorizer, + packValidationData, + packPaymasterData, + UserOperation, +} = require('../../helpers/erc4337'); +const { ZeroAddress } = require('ethers'); +const { MAX_UINT48 } = require('../../helpers/constants'); + +const fixture = async () => { + const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC4337Utils'); + return { utils, authorizer, sender, entrypoint, paymaster }; +}; + +describe('ERC4337Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('parseValidationData', function () { + it('parses the validation data', async function () { + const authorizer = this.authorizer.address; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const result = await this.utils.$parseValidationData(packValidationData(validAfter, validUntil, authorizer)); + expect(result).to.deep.equal([authorizer, validAfter, validUntil]); + }); + + it('returns an type(uint48).max if until is 0', async function () { + const authorizer = this.authorizer.address; + const validAfter = 0x12345678n; + const result = await this.utils.$parseValidationData(packValidationData(validAfter, 0, authorizer)); + expect(result).to.deep.equal([authorizer, validAfter, MAX_UINT48]); + }); + }); + + describe('packValidationData', function () { + it('packs the validation data', async function () { + const authorizer = this.authorizer.address; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const result = await this.utils.$packValidationData(ethers.Typed.address(authorizer), validAfter, validUntil); + expect(result).to.equal(packValidationData(validAfter, validUntil, authorizer)); + }); + + it('packs the validation data (bool)', async function () { + const success = false; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const result = await this.utils.$packValidationData(ethers.Typed.bool(success), validAfter, validUntil); + expect(result).to.equal(packValidationData(validAfter, validUntil, toAuthorizer(SIG_VALIDATION_FAILURE))); + }); + }); + + describe('combineValidationData', function () { + it('combines the validation data', async function () { + const authorizer1 = ZeroAddress; + const validUntil1 = 0x12345678n; + const validAfter1 = 0x9abcdef0n; + + const authorizer2 = ZeroAddress; + const validUntil2 = 0x87654321n; + const validAfter2 = 0xabcdef90n; + + const result = await this.utils.$combineValidationData( + packValidationData(validAfter1, validUntil1, authorizer1), + packValidationData(validAfter2, validUntil2, authorizer2), + ); + expect(result).to.equal(packValidationData(validAfter2, validUntil1, toAuthorizer(SIG_VALIDATION_SUCCESS))); + }); + + // address(bytes20(keccak256('openzeppelin.erc4337.tests'))) + for (const authorizers of [ + [ZeroAddress, '0xbf023313b891fd6000544b79e353323aa94a4f29'], + ['0xbf023313b891fd6000544b79e353323aa94a4f29', ZeroAddress], + ]) { + it('returns SIG_VALIDATION_FAILURE if one of the authorizers is not address(0)', async function () { + const validUntil1 = 0x12345678n; + const validAfter1 = 0x9abcdef0n; + + const validUntil2 = 0x87654321n; + const validAfter2 = 0xabcdef90n; + + const result = await this.utils.$combineValidationData( + packValidationData(validAfter1, validUntil1, authorizers[0]), + packValidationData(validAfter2, validUntil2, authorizers[1]), + ); + expect(result).to.equal(packValidationData(validAfter2, validUntil1, toAuthorizer(SIG_VALIDATION_FAILURE))); + }); + } + }); + + describe('getValidationData', function () { + it('returns the validation data with valid validity range', async function () { + const aggregator = this.authorizer.address; + const validAfter = 0; + const validUntil = MAX_UINT48; + const result = await this.utils.$getValidationData(packValidationData(validAfter, validUntil, aggregator)); + expect(result).to.deep.equal([aggregator, false]); + }); + + it('returns the validation data with invalid validity range (expired)', async function () { + const aggregator = this.authorizer.address; + const validAfter = 0; + const validUntil = 1; + const result = await this.utils.$getValidationData(packValidationData(validAfter, validUntil, aggregator)); + expect(result).to.deep.equal([aggregator, true]); + }); + + it('returns the validation data with invalid validity range (not yet valid)', async function () { + const aggregator = this.authorizer.address; + const validAfter = MAX_UINT48; + const validUntil = MAX_UINT48; + const result = await this.utils.$getValidationData(packValidationData(validAfter, validUntil, aggregator)); + expect(result).to.deep.equal([aggregator, true]); + }); + }); + + describe('hash', function () { + it('returns the user operation hash', async function () { + const userOp = new UserOperation({ sender: this.sender.address, nonce: 1 }); + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + const hash = await this.utils.$hash(userOp.packed); + expect(hash).to.equal(userOp.hash(this.utils.target, chainId)); + }); + + it('returns the operation hash with specified entrypoint and chainId', async function () { + const userOp = new UserOperation({ sender: this.sender.address, nonce: 1 }); + const chainId = 0xdeadbeef; + const hash = await this.utils.$hash(userOp.packed, this.entrypoint.address, chainId); + expect(hash).to.equal(userOp.hash(this.entrypoint.address, chainId)); + }); + }); + + describe('userOp values', function () { + it('returns verificationGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender.address, nonce: 1, verificationGas: 0x12345678n }); + expect(await this.utils.$verificationGasLimit(userOp.packed)).to.equal(userOp.verificationGas); + }); + + it('returns callGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender.address, nonce: 1, callGas: 0x12345678n }); + expect(await this.utils.$callGasLimit(userOp.packed)).to.equal(userOp.callGas); + }); + + it('returns maxPriorityFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender.address, nonce: 1, maxPriorityFee: 0x12345678n }); + expect(await this.utils.$maxPriorityFeePerGas(userOp.packed)).to.equal(userOp.maxPriorityFee); + }); + + it('returns maxFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender.address, nonce: 1, maxFeePerGas: 0x12345678n }); + expect(await this.utils.$maxFeePerGas(userOp.packed)).to.equal(userOp.maxFeePerGas); + }); + + it('returns gasPrice', async function () { + const userOp = new UserOperation({ + sender: this.sender.address, + nonce: 1, + maxPriorityFee: 0x12345678n, + maxFeePerGas: 0x87654321n, + }); + expect( + await this.utils['$gasPrice((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes))'](userOp.packed), + ).to.equal(userOp.maxPriorityFee); + }); + + describe('paymasterAndData', function () { + beforeEach(async function () { + this.verificationGasLimit = 0x12345678n; + this.postOpGasLimit = 0x87654321n; + this.paymasterAndData = packPaymasterData( + this.paymaster.address, + this.verificationGasLimit, + this.postOpGasLimit, + ); + this.userOp = new UserOperation({ + sender: this.sender.address, + nonce: 1, + paymasterAndData: this.paymasterAndData, + }); + }); + + it('returns paymaster', async function () { + expect(await this.utils.$paymaster(this.userOp.packed)).to.equal(this.paymaster.address); + }); + + it('returns verificationGasLimit', async function () { + expect(await this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.equal(this.verificationGasLimit); + }); + + it('returns postOpGasLimit', async function () { + expect(await this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.equal(this.postOpGasLimit); + }); + }); + }); +}); diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js index e69de29bb2d..8a2733d7562 100644 --- a/test/account/utils/draft-ERC7579Utils.test.js +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -0,0 +1,319 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); +const { + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + encodeSingle, + encodeBatch, + encodeDelegate, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + encodeMode, +} = require('../../helpers/erc7579'); +const { selector } = require('../../helpers/methods'); + +const coder = ethers.AbiCoder.defaultAbiCoder(); + +const fixture = async () => { + const [sender] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC7579Utils'); + const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock'); + const target = await ethers.deployContract('CallReceiverMock'); + const anotherTarget = await ethers.deployContract('CallReceiverMock'); + await setBalance(utils.target, ethers.parseEther('1')); + return { utils, utilsGlobal, target, anotherTarget, sender }; +}; + +describe('ERC7579Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('execSingle', function () { + it('calls the target with value', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.emit(this.target, 'MockFunctionCalled'); + expect(await ethers.provider.getBalance(this.target)).to.equal(value); + }); + + it('calls the target with value and args', async function () { + const value = 0x432; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ); + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .withArgs(42, '0x1234'); + expect(await ethers.provider.getBalance(this.target)).to.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + }); + + describe('execBatch', function () { + it('calls the targets with value', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalled') + .to.emit(this.anotherTarget, 'MockFunctionCalled'); + expect(await ethers.provider.getBalance(this.target)).to.equal(value1); + expect(await ethers.provider.getBalance(this.anotherTarget)).to.equal(value2); + }); + + it('calls the targets with value and args', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234'])], + [ + this.anotherTarget, + value2, + this.anotherTarget.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ], + ); + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs'); + expect(await ethers.provider.getBalance(this.target)).to.equal(value1); + expect(await ethers.provider.getBalance(this.anotherTarget)).to.equal(value2); + }); + + it('reverts when any target reverts in default ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_BATCH, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + + // Check balances + expect(await ethers.provider.getBalance(this.target)).to.equal(value1); + expect(await ethers.provider.getBalance(this.anotherTarget)).to.equal(0); + }); + }); + + describe('execDelegateCall', function () { + it('delegate calls the target', async function () { + const slot = ethers.hexlify(ethers.randomBytes(32)); + const value = ethers.hexlify(ethers.randomBytes(32)); + + const data = encodeDelegate( + this.target, + this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]), + ); + + expect(await ethers.provider.getStorage(this.utils.target, slot)).to.equal(ethers.ZeroHash); + await this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data); + expect(await ethers.provider.getStorage(this.utils.target, slot)).to.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith( + 'CallReceiverMock: reverting', + ); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + }); + + it('encodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + const mode = await this.utils.$encodeMode(callType, execType, selector, payload); + expect(mode).to.equal( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ); + }); + + it('decodes Mode', async function () { + const mode = encodeMode({ + callType: CALL_TYPE_BATCH, + execType: EXEC_TYPE_TRY, + selector: '0x12345678', + payload: ethers.toBeHex(0, 22), + }); + + expect(await this.utils.$decodeMode(mode)).to.deep.eq([ + CALL_TYPE_BATCH, + EXEC_TYPE_TRY, + '0x12345678', + ethers.toBeHex(0, 22), + ]); + }); + + it('encodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + const encoded = await this.utils.$encodeSingle(target, value, data); + expect(encoded).to.equal(encodeSingle(target, value, data)); + }); + + it('decodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + const encoded = encodeSingle(target, value, data); + expect(await this.utils.$decodeSingle(encoded)).to.deep.eq([target.target, value, data]); + }); + + it('encodes batch', async function () { + const entries = [ + [this.target, 0x123, '0x12345678'], + [this.anotherTarget, 0x456, '0x12345678'], + ]; + + const encoded = await this.utils.$encodeBatch(entries); + expect(encoded).to.equal(encodeBatch(...entries)); + }); + + it('decodes batch', async function () { + const entries = [ + [this.target.target, 0x123, '0x12345678'], + [this.anotherTarget.target, 0x456, '0x12345678'], + ]; + + const encoded = encodeBatch(...entries); + expect(await this.utils.$decodeBatch(encoded)).to.deep.eq(entries); + }); + + it('encodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + const encoded = await this.utils.$encodeDelegate(target, data); + expect(encoded).to.equal(encodeDelegate(target, data)); + }); + + it('decodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + const encoded = encodeDelegate(target, data); + expect(await this.utils.$decodeDelegate(encoded)).to.deep.eq([target.target, data]); + }); + + describe('global', function () { + describe('eqCallTypeGlobal', function () { + it('returns true if both call types are equal', async function () { + const callType = CALL_TYPE_BATCH; + expect(await this.utilsGlobal.$eqCallTypeGlobal(callType, callType)).to.be.true; + }); + + it('returns false if both call types are different', async function () { + expect(await this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_CALL, CALL_TYPE_BATCH)).to.be.false; + }); + }); + + describe('eqExecTypeGlobal', function () { + it('returns true if both exec types are equal', async function () { + const execType = EXEC_TYPE_TRY; + expect(await this.utilsGlobal.$eqExecTypeGlobal(execType, execType)).to.be.true; + }); + + it('returns false if both exec types are different', async function () { + expect(await this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY)).to.be.false; + }); + }); + + describe('eqModeSelectorGlobal', function () { + it('returns true if both selectors are equal', async function () { + const selector = '0x12345678'; + expect(await this.utilsGlobal.$eqModeSelectorGlobal(selector, selector)).to.be.true; + }); + + it('returns false if both selectors are different', async function () { + expect(await this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x87654321')).to.be.false; + }); + }); + + describe('eqModePayloadGlobal', function () { + it('returns true if both payloads are equal', async function () { + const payload = ethers.toBeHex(0, 22); + expect(await this.utilsGlobal.$eqModePayloadGlobal(payload, payload)).to.be.true; + }); + + it('returns false if both payloads are different', async function () { + expect(await this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(1, 22))).to.be.false; + }); + }); + }); +}); diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js new file mode 100644 index 00000000000..c529f5f64cf --- /dev/null +++ b/test/helpers/erc4337.js @@ -0,0 +1,82 @@ +const { ethers } = require('hardhat'); + +const SIG_VALIDATION_SUCCESS = 0; +const SIG_VALIDATION_FAILURE = 1; + +function toAuthorizer(sigValidatonSuccess) { + return `0x000000000000000000000000000000000000000${sigValidatonSuccess}`; +} + +function pack(left, right) { + return ethers.solidityPacked(['uint128', 'uint128'], [left, right]); +} + +function packValidationData(validAfter, validUntil, authorizer) { + return ethers.solidityPacked(['uint48', 'uint48', 'address'], [validAfter, validUntil, authorizer]); +} + +function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) { + return ethers.solidityPacked(['address', 'uint128', 'uint128'], [paymaster, verificationGasLimit, postOpGasLimit]); +} + +/// Represent one user operation +class UserOperation { + constructor(params) { + this.sender = params.sender; + this.nonce = params.nonce; + this.initCode = params.initCode ?? '0x'; + this.callData = params.callData ?? '0x'; + this.verificationGas = params.verificationGas ?? 10_000_000n; + this.callGas = params.callGas ?? 100_000n; + this.preVerificationGas = params.preVerificationGas ?? 100_000n; + this.maxPriorityFee = params.maxPriorityFee ?? 100_000n; + this.maxFeePerGas = params.maxFeePerGas ?? 100_000n; + this.paymasterAndData = params.paymasterAndData ?? '0x'; + this.signature = params.signature ?? '0x'; + } + + get packed() { + return { + sender: this.sender, + nonce: this.nonce, + initCode: this.initCode, + callData: this.callData, + accountGasLimits: pack(this.verificationGas, this.callGas), + preVerificationGas: this.preVerificationGas, + gasFees: pack(this.maxPriorityFee, this.maxFeePerGas), + paymasterAndData: this.paymasterAndData, + signature: this.signature, + }; + } + + hash(entrypoint, chainId) { + const p = this.packed; + const h = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint256', 'uint256', 'uint256'], + [ + p.sender, + p.nonce, + ethers.keccak256(p.initCode), + ethers.keccak256(p.callData), + p.accountGasLimits, + p.preVerificationGas, + p.gasFees, + ethers.keccak256(p.paymasterAndData), + ], + ), + ); + return ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'address', 'uint256'], [h, entrypoint, chainId]), + ); + } +} + +module.exports = { + SIG_VALIDATION_SUCCESS, + SIG_VALIDATION_FAILURE, + toAuthorizer, + packValidationData, + packPaymasterData, + UserOperation, +}; diff --git a/test/helpers/erc7579.js b/test/helpers/erc7579.js new file mode 100644 index 00000000000..6c3b4759b73 --- /dev/null +++ b/test/helpers/erc7579.js @@ -0,0 +1,58 @@ +const { ethers } = require('hardhat'); + +const MODULE_TYPE_VALIDATOR = 1; +const MODULE_TYPE_EXECUTOR = 2; +const MODULE_TYPE_FALLBACK = 3; +const MODULE_TYPE_HOOK = 4; + +const EXEC_TYPE_DEFAULT = '0x00'; +const EXEC_TYPE_TRY = '0x01'; + +const CALL_TYPE_CALL = '0x00'; +const CALL_TYPE_BATCH = '0x01'; +const CALL_TYPE_DELEGATE = '0xff'; + +const encodeMode = ({ + callType = '0x00', + execType = '0x00', + selector = '0x00000000', + payload = '0x00000000000000000000000000000000000000000000', +} = {}) => + ethers.solidityPacked( + ['bytes1', 'bytes1', 'bytes4', 'bytes4', 'bytes22'], + [callType, execType, '0x00000000', selector, payload], + ); + +const encodeSingle = (target, value = 0n, data = '0x') => + ethers.solidityPacked(['address', 'uint256', 'bytes'], [target.target ?? target.address ?? target, value, data]); + +const encodeBatch = (...entries) => + ethers.AbiCoder.defaultAbiCoder().encode( + ['(address,uint256,bytes)[]'], + [ + entries.map(entry => + Array.isArray(entry) + ? [entry[0].target ?? entry[0].address ?? entry[0], entry[1] ?? 0n, entry[2] ?? '0x'] + : [entry.target.target ?? entry.target.address ?? entry.target, entry.value ?? 0n, entry.data ?? '0x'], + ), + ], + ); + +const encodeDelegate = (target, data = '0x') => + ethers.solidityPacked(['address', 'bytes'], [target.target ?? target.address ?? target, data]); + +module.exports = { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK, + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + CALL_TYPE_DELEGATE, + encodeMode, + encodeSingle, + encodeBatch, + encodeDelegate, +}; From 33b0c1b98bc8d5db01f23686b48656ea63cc16c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 20:57:31 -0600 Subject: [PATCH 07/41] Fix transpilation --- contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 9f0814f5d21..a95906031b3 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -41,13 +41,13 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, // allow some slack for future gas price changes. uint256 private constant INNER_GAS_OVERHEAD = 10000; + uint256 private constant REVERT_REASON_MAX_LEN = 2048; + uint256 private constant PENALTY_PERCENT = 10; + // Marker for inner call revert on out of gas bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead"; bytes32 private constant INNER_REVERT_LOW_PREFUND = hex"deadaa51"; - uint256 private constant REVERT_REASON_MAX_LEN = 2048; - uint256 private constant PENALTY_PERCENT = 10; - /// @inheritdoc ERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything From a013306d1ea0a2a1a837bfa965748cf6fa575f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:01:53 -0600 Subject: [PATCH 08/41] Exclude vendored contracts from slither detection --- slither.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither.config.json b/slither.config.json index 069da1f3a21..3f2f4434a42 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +1,5 @@ { "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-state,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,cyclomatic-complexity,deprecated-standards,erc20-indexed,function-init-state,pragma,unused-state,reentrancy-unlimited-gas,constable-states,immutable-states,var-read-using-this", - "filter_paths": "contracts/mocks,contracts-exposed", + "filter_paths": "contracts/mocks,contracts-exposed,vendor", "compile_force_framework": "hardhat" } From 90fffbf58bb3dbd00341db27189b8161ca98ac8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:07:09 -0600 Subject: [PATCH 09/41] Fix transpilation (?) --- .../vendor/erc4337-entrypoint/core/EntryPoint.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index a95906031b3..1e05db88d8a 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -33,21 +33,21 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, SenderCreator private immutable _senderCreator = new SenderCreator(); - function senderCreator() internal view virtual returns (SenderCreator) { - return _senderCreator; - } - //compensate for innerHandleOps' emit message and deposit refund. // allow some slack for future gas price changes. uint256 private constant INNER_GAS_OVERHEAD = 10000; - uint256 private constant REVERT_REASON_MAX_LEN = 2048; - uint256 private constant PENALTY_PERCENT = 10; - // Marker for inner call revert on out of gas bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead"; bytes32 private constant INNER_REVERT_LOW_PREFUND = hex"deadaa51"; + uint256 private constant REVERT_REASON_MAX_LEN = 2048; + uint256 private constant PENALTY_PERCENT = 10; + + function senderCreator() internal view virtual returns (SenderCreator) { + return _senderCreator; + } + /// @inheritdoc ERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything From 913e522d85c3a31b736e5d6cef673a1d226b4e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:09:23 -0600 Subject: [PATCH 10/41] Filter constants un ERC7579Utils --- contracts/account/utils/draft-ERC7579Utils.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index 565326fbe40..d0e0d0a1772 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -23,26 +23,31 @@ library ERC7579Utils { /** * @dev A single `call` execution. */ + // slither-disable-next-line unused-state-variable CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); /** * @dev A batch of `call` executions. */ + // slither-disable-next-line unused-state-variable CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); /** * @dev A `delegatecall` execution. */ + // slither-disable-next-line unused-state-variable CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); /** * @dev Default execution type that reverts on failure. */ + // slither-disable-next-line unused-state-variable ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); /** * @dev Execution type that does not revert on failure. */ + // slither-disable-next-line unused-state-variable ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); /** From 01e19a1a7a99512ccc151a6c18b437831d7e27d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:09:54 -0600 Subject: [PATCH 11/41] Make StakeManager's deposit's private --- contracts/vendor/erc4337-entrypoint/core/StakeManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 6ed1388c1fe..2fc2ee5c4c4 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -13,7 +13,7 @@ import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit */ abstract contract StakeManager is IStakeManager { /// maps paymaster to their deposits and stakes - mapping(address => DepositInfo) public deposits; + mapping(address => DepositInfo) private deposits; // OZ edit /// @inheritdoc IStakeManager function getDepositInfo(address account) public view returns (DepositInfo memory info) { From 8030e4218b9da85ee353ea235fae2f55b7f36046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:43:08 -0600 Subject: [PATCH 12/41] up --- contracts/vendor/erc4337-entrypoint/core/StakeManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 2fc2ee5c4c4..0d1a20d6d07 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -13,7 +13,7 @@ import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit */ abstract contract StakeManager is IStakeManager { /// maps paymaster to their deposits and stakes - mapping(address => DepositInfo) private deposits; // OZ edit + mapping(address => DepositInfo) internal deposits; // OZ edit /// @inheritdoc IStakeManager function getDepositInfo(address account) public view returns (DepositInfo memory info) { From 81b0caa0718fc92feb610eb503337865b16071d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:48:26 -0600 Subject: [PATCH 13/41] up --- .../erc4337-entrypoint/core/EntryPoint.sol | 4 +-- .../erc4337-entrypoint/core/StakeManager.sol | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 1e05db88d8a..324cc2a4ce4 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -438,7 +438,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN)); } if (paymaster == address(0)) { - DepositInfo storage senderInfo = deposits[sender]; + DepositInfo storage senderInfo = deposits(sender); uint256 deposit = senderInfo.deposit; if (requiredPrefund > deposit) { revert FailedOp(opIndex, "AA21 didn't pay prefund"); @@ -469,7 +469,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 preGas = gasleft(); MemoryUserOp memory mUserOp = opInfo.mUserOp; address paymaster = mUserOp.paymaster; - DepositInfo storage paymasterInfo = deposits[paymaster]; + DepositInfo storage paymasterInfo = deposits(paymaster); uint256 deposit = paymasterInfo.deposit; if (deposit < requiredPreFund) { revert FailedOp(opIndex, "AA31 paymaster deposit too low"); diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 0d1a20d6d07..4fee5870492 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -13,11 +13,16 @@ import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit */ abstract contract StakeManager is IStakeManager { /// maps paymaster to their deposits and stakes - mapping(address => DepositInfo) internal deposits; // OZ edit + mapping(address => DepositInfo) private _deposits; // OZ edit + + // OZ edit + function deposits(address account) public view returns (DepositInfo storage) { + return _deposits[account]; + } /// @inheritdoc IStakeManager function getDepositInfo(address account) public view returns (DepositInfo memory info) { - return deposits[account]; + return _deposits[account]; } /** @@ -25,14 +30,14 @@ abstract contract StakeManager is IStakeManager { * @param addr - The account to query. */ function _getStakeInfo(address addr) internal view returns (StakeInfo memory info) { - DepositInfo storage depositInfo = deposits[addr]; + DepositInfo storage depositInfo = _deposits[addr]; info.stake = depositInfo.stake; info.unstakeDelaySec = depositInfo.unstakeDelaySec; } /// @inheritdoc IStakeManager function balanceOf(address account) public view returns (uint256) { - return deposits[account].deposit; + return _deposits[account].deposit; } receive() external payable { @@ -46,7 +51,7 @@ abstract contract StakeManager is IStakeManager { * @return the updated deposit of this account */ function _incrementDeposit(address account, uint256 amount) internal returns (uint256) { - DepositInfo storage info = deposits[account]; + DepositInfo storage info = _deposits[account]; uint256 newAmount = info.deposit + amount; info.deposit = newAmount; return newAmount; @@ -67,13 +72,13 @@ abstract contract StakeManager is IStakeManager { * @param unstakeDelaySec The new lock duration before the deposit can be withdrawn. */ function addStake(uint32 unstakeDelaySec) public payable { - DepositInfo storage info = deposits[msg.sender]; + DepositInfo storage info = _deposits[msg.sender]; require(unstakeDelaySec > 0, "must specify unstake delay"); require(unstakeDelaySec >= info.unstakeDelaySec, "cannot decrease unstake time"); uint256 stake = info.stake + msg.value; require(stake > 0, "no stake specified"); require(stake <= type(uint112).max, "stake overflow"); - deposits[msg.sender] = DepositInfo(info.deposit, true, uint112(stake), unstakeDelaySec, 0); + _deposits[msg.sender] = DepositInfo(info.deposit, true, uint112(stake), unstakeDelaySec, 0); emit StakeLocked(msg.sender, stake, unstakeDelaySec); } @@ -82,7 +87,7 @@ abstract contract StakeManager is IStakeManager { * The value can be withdrawn (using withdrawStake) after the unstake delay. */ function unlockStake() external { - DepositInfo storage info = deposits[msg.sender]; + DepositInfo storage info = _deposits[msg.sender]; require(info.unstakeDelaySec != 0, "not staked"); require(info.staked, "already unstaking"); uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec; @@ -97,7 +102,7 @@ abstract contract StakeManager is IStakeManager { * @param withdrawAddress - The address to send withdrawn value. */ function withdrawStake(address payable withdrawAddress) external { - DepositInfo storage info = deposits[msg.sender]; + 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"); @@ -116,7 +121,7 @@ abstract contract StakeManager is IStakeManager { * @param withdrawAmount - The amount to withdraw. */ function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external { - DepositInfo storage info = deposits[msg.sender]; + DepositInfo storage info = _deposits[msg.sender]; require(withdrawAmount <= info.deposit, "Withdraw amount too large"); info.deposit = info.deposit - withdrawAmount; emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount); From d83ed9e647ac7a416fdb49bcacee926330a2424c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:49:41 -0600 Subject: [PATCH 14/41] up --- contracts/account/utils/draft-ERC7579Utils.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index d0e0d0a1772..48b065cb705 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -23,31 +23,31 @@ library ERC7579Utils { /** * @dev A single `call` execution. */ - // slither-disable-next-line unused-state-variable + // slither-disable-next-line unused-state CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); /** * @dev A batch of `call` executions. */ - // slither-disable-next-line unused-state-variable + // slither-disable-next-line unused-state CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); /** * @dev A `delegatecall` execution. */ - // slither-disable-next-line unused-state-variable + // slither-disable-next-line unused-state CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); /** * @dev Default execution type that reverts on failure. */ - // slither-disable-next-line unused-state-variable + // slither-disable-next-line unused-state ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); /** * @dev Execution type that does not revert on failure. */ - // slither-disable-next-line unused-state-variable + // slither-disable-next-line unused-state ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); /** From d6767b933a698baf383cc612d1fc475d6ae9fa54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 21:53:37 -0600 Subject: [PATCH 15/41] up --- contracts/vendor/erc4337-entrypoint/core/StakeManager.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 4fee5870492..8cf3bae96fb 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -16,8 +16,9 @@ abstract contract StakeManager is IStakeManager { mapping(address => DepositInfo) private _deposits; // OZ edit // OZ edit - function deposits(address account) public view returns (DepositInfo storage) { - return _deposits[account]; + function deposits(address account) internal view returns (DepositInfo storage d) { + DepositInfo storage depositInfo = _deposits[account]; + return depositInfo; } /// @inheritdoc IStakeManager From 8aecee30f91a164547d30b909a4dd87c5b48d5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 22:01:17 -0600 Subject: [PATCH 16/41] Add doc --- contracts/account/README.adoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 contracts/account/README.adoc diff --git a/contracts/account/README.adoc b/contracts/account/README.adoc new file mode 100644 index 00000000000..d2eb9db5ee9 --- /dev/null +++ b/contracts/account/README.adoc @@ -0,0 +1,12 @@ += Account + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account + +This directory includes contracts to build accounts for ERC-4337. + +== Utilities + +{{ERC4337Utils}} + +{{ERC7579Utils}} From c6b5aaca70239b9df3ccaa87de0e6ab1de7a9bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 22:27:47 -0600 Subject: [PATCH 17/41] Fix coverage --- .codecov.yml | 1 + test/account/utils/draft-ERC4337Utils.test.js | 4 +++ test/account/utils/draft-ERC7579Utils.test.js | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 5bee9146ab9..cb718b2fc21 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,3 +13,4 @@ coverage: ignore: - "test" - "contracts/mocks" + - "vendor" diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index a3b03e8d3d1..c76f89845ac 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -120,6 +120,10 @@ describe('ERC4337Utils', function () { const result = await this.utils.$getValidationData(packValidationData(validAfter, validUntil, aggregator)); expect(result).to.deep.equal([aggregator, true]); }); + + it('returns address(0) and false for validationData = 0', function () { + return expect(this.utils.$getValidationData(0n)).to.eventually.deep.equal([ZeroAddress, false]); + }); }); describe('hash', function () { diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js index 8a2733d7562..9d6154f78bd 100644 --- a/test/account/utils/draft-ERC7579Utils.test.js +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -79,6 +79,14 @@ describe('ERC7579Utils', function () { ), ); }); + + it('reverts with an invalid exec type', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + await expect(this.utils.$execSingle('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); }); describe('execBatch', function () { @@ -146,6 +154,18 @@ describe('ERC7579Utils', function () { expect(await ethers.provider.getBalance(this.target)).to.equal(value1); expect(await ethers.provider.getBalance(this.anotherTarget)).to.equal(0); }); + + it('reverts with an invalid exec type', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + await expect(this.utils.$execBatch('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); }); describe('execDelegateCall', function () { @@ -182,6 +202,13 @@ describe('ERC7579Utils', function () { ), ); }); + + it('reverts with an invalid exec type', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction')); + await expect(this.utils.$execDelegateCall('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); }); it('encodes Mode', async function () { From d1bc68d02506a5b05107dbe453ee98e3bd6d336a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 23:04:03 -0600 Subject: [PATCH 18/41] draft-ERC433Utils -> draft-ERC4337Utils --- contracts/account/draft-AccountECDSA.sol | 60 +++++++++++++++++ contracts/account/draft-AccountP256.sol | 65 ++++++++++++++++++ contracts/account/draft-AccountRSA.sol | 67 +++++++++++++++++++ ...ERC433Utils.sol => draft-ERC4337Utils.sol} | 0 contracts/mocks/account/AccountBaseMock.sol | 20 ++++++ 5 files changed, 212 insertions(+) create mode 100644 contracts/account/draft-AccountECDSA.sol create mode 100644 contracts/account/draft-AccountP256.sol create mode 100644 contracts/account/draft-AccountRSA.sol rename contracts/account/utils/{draft-ERC433Utils.sol => draft-ERC4337Utils.sol} (100%) create mode 100644 contracts/mocks/account/AccountBaseMock.sol diff --git a/contracts/account/draft-AccountECDSA.sol b/contracts/account/draft-AccountECDSA.sol new file mode 100644 index 00000000000..8d42b10a594 --- /dev/null +++ b/contracts/account/draft-AccountECDSA.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "../interfaces/draft-IERC4337.sol"; +import {AccountBase} from "./draft-AccountBase.sol"; +import {ERC1271TypedSigner} from "../utils/cryptography/ERC1271TypedSigner.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155HolderLean, IERC1155Receiver} from "../token/ERC1155/utils/ERC1155HolderLean.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; +import {IERC165} from "../utils/introspection/IERC165.sol"; + +/** + * @dev Account implementation using {ECDSA} signatures and {ERC1271TypedSigner} for replay protection. + */ +abstract contract AccountECDSA is ERC165, ERC1271TypedSigner, ERC721Holder, ERC1155HolderLean, AccountBase { + address private immutable _signer; + + /** + * @dev Initializes the account with the address of the native signer. + */ + constructor(address signerAddr) { + _signer = signerAddr; + } + + /** + * @dev Return the account's signer address. + */ + function signer() public view virtual returns (address) { + return _signer; + } + + /** + * @dev Internal version of {validateUserOp} that relies on {_isValidSignature}. + */ + function _validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) internal virtual override returns (uint256) { + return + _isValidSignature(userOpHash, userOp.signature) + ? ERC4337Utils.SIG_VALIDATION_SUCCESS + : ERC4337Utils.SIG_VALIDATION_FAILED; + } + + /** + * @dev Validates the signature using the account's signer.S + */ + function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) { + (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature); + return signer() == recovered && err == ECDSA.RecoverError.NoError; + } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/contracts/account/draft-AccountP256.sol b/contracts/account/draft-AccountP256.sol new file mode 100644 index 00000000000..2380d9c0e59 --- /dev/null +++ b/contracts/account/draft-AccountP256.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "../interfaces/draft-IERC4337.sol"; +import {AccountBase} from "./draft-AccountBase.sol"; +import {ERC1271TypedSigner} from "../utils/cryptography/ERC1271TypedSigner.sol"; +import {P256} from "../utils/cryptography/P256.sol"; +import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155HolderLean, IERC1155Receiver} from "../token/ERC1155/utils/ERC1155HolderLean.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; +import {IERC165} from "../utils/introspection/IERC165.sol"; + +/** + * @dev Account implementation using {P256} signatures and {ERC1271TypedSigner} for replay protection. + */ +abstract contract AccountP256 is ERC165, ERC1271TypedSigner, ERC721Holder, ERC1155HolderLean, AccountBase { + bytes32 private immutable _qx; + bytes32 private immutable _qy; + + /** + * @dev Initializes the account with the P256 public key. + */ + constructor(bytes32 qx, bytes32 qy) { + _qx = qx; + _qy = qy; + } + + /** + * @dev Return the account's signer P256 public key. + */ + function signer() public view virtual returns (bytes32 qx, bytes32 qy) { + return (_qx, _qy); + } + + /** + * @dev Internal version of {validateUserOp} that relies on {_isValidSignature}. + */ + function _validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) internal virtual override returns (uint256) { + return + _isValidSignature(userOpHash, userOp.signature) + ? ERC4337Utils.SIG_VALIDATION_SUCCESS + : ERC4337Utils.SIG_VALIDATION_FAILED; + } + + /** + * @dev Validates the signature using the account's signer. + */ + function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) { + if (signature.length < 0x40) return false; + bytes32 r = bytes32(signature[0x00:0x20]); + bytes32 s = bytes32(signature[0x20:0x40]); + (bytes32 qx, bytes32 qy) = signer(); + return P256.verify(hash, r, s, qx, qy); + } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/contracts/account/draft-AccountRSA.sol b/contracts/account/draft-AccountRSA.sol new file mode 100644 index 00000000000..32ea675e624 --- /dev/null +++ b/contracts/account/draft-AccountRSA.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "../interfaces/draft-IERC4337.sol"; +import {AccountBase} from "./draft-AccountBase.sol"; +import {ERC1271TypedSigner} from "../utils/cryptography/ERC1271TypedSigner.sol"; +import {RSA} from "../utils/cryptography/RSA.sol"; +import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155HolderLean, IERC1155Receiver} from "../token/ERC1155/utils/ERC1155HolderLean.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; +import {IERC165} from "../utils/introspection/IERC165.sol"; + +/** + * @dev Account implementation using {RSA} signatures and {ERC1271TypedSigner} for replay protection. + * + * NOTE: Storing `_e` and `_n` in regular storage violate ERC-7562 validation rules if the contract + * is used as an ERC-1271 signer during the validation phase of a different account contract. + * Consider deploying this contract through a factory that sets `_e` and `_n` as immutable arguments + * (see {Clones-cloneDeterministicWithImmutableArgs}). + */ +abstract contract AccountRSA is ERC165, ERC1271TypedSigner, ERC721Holder, ERC1155HolderLean, AccountBase { + bytes private _e; + bytes private _n; + + /** + * @dev Initializes the account with the RSA public key. + */ + constructor(bytes memory e, bytes memory n) { + _e = e; + _n = n; + } + + /** + * @dev Return the account's signer RSA public key. + */ + function signer() public view virtual returns (bytes memory e, bytes memory n) { + return (_e, _n); + } + + /** + * @dev Internal version of {validateUserOp} that relies on {_isValidSignature}. + */ + function _validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) internal virtual override returns (uint256) { + return + _isValidSignature(userOpHash, userOp.signature) + ? ERC4337Utils.SIG_VALIDATION_SUCCESS + : ERC4337Utils.SIG_VALIDATION_FAILED; + } + + /** + * @dev Validates the signature using the account's signer. + */ + function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) { + (bytes memory e, bytes memory n) = signer(); + return RSA.pkcs1Sha256(hash, signature, e, n); + } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/contracts/account/utils/draft-ERC433Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol similarity index 100% rename from contracts/account/utils/draft-ERC433Utils.sol rename to contracts/account/utils/draft-ERC4337Utils.sol diff --git a/contracts/mocks/account/AccountBaseMock.sol b/contracts/mocks/account/AccountBaseMock.sol new file mode 100644 index 00000000000..8ac9c9f6662 --- /dev/null +++ b/contracts/mocks/account/AccountBaseMock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {AccountBase} from "../../account/draft-AccountBase.sol"; +import {ERC4337Utils} from "../../account/utils/draft-ERC4337Utils.sol"; + +contract AccountBaseMock is AccountBase { + /// Validates a user operation with a boolean signature. + function _validateUserOp( + PackedUserOperation calldata userOp, + bytes32 /* userOpHash */ + ) internal pure override returns (uint256 validationData) { + return + bytes1(userOp.signature[0:1]) == bytes1(0x01) + ? ERC4337Utils.SIG_VALIDATION_SUCCESS + : ERC4337Utils.SIG_VALIDATION_FAILED; + } +} From 4dab8d053ea84ba72dbf702bc1f07adc5493d955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 23:29:02 -0600 Subject: [PATCH 19/41] Remove mixed contracts --- contracts/account/draft-AccountECDSA.sol | 60 ------------------ contracts/account/draft-AccountP256.sol | 65 -------------------- contracts/account/draft-AccountRSA.sol | 67 --------------------- contracts/mocks/account/AccountBaseMock.sol | 20 ------ 4 files changed, 212 deletions(-) delete mode 100644 contracts/account/draft-AccountECDSA.sol delete mode 100644 contracts/account/draft-AccountP256.sol delete mode 100644 contracts/account/draft-AccountRSA.sol delete mode 100644 contracts/mocks/account/AccountBaseMock.sol diff --git a/contracts/account/draft-AccountECDSA.sol b/contracts/account/draft-AccountECDSA.sol deleted file mode 100644 index 8d42b10a594..00000000000 --- a/contracts/account/draft-AccountECDSA.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {PackedUserOperation} from "../interfaces/draft-IERC4337.sol"; -import {AccountBase} from "./draft-AccountBase.sol"; -import {ERC1271TypedSigner} from "../utils/cryptography/ERC1271TypedSigner.sol"; -import {ECDSA} from "../utils/cryptography/ECDSA.sol"; -import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol"; -import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; -import {ERC1155HolderLean, IERC1155Receiver} from "../token/ERC1155/utils/ERC1155HolderLean.sol"; -import {ERC165} from "../utils/introspection/ERC165.sol"; -import {IERC165} from "../utils/introspection/IERC165.sol"; - -/** - * @dev Account implementation using {ECDSA} signatures and {ERC1271TypedSigner} for replay protection. - */ -abstract contract AccountECDSA is ERC165, ERC1271TypedSigner, ERC721Holder, ERC1155HolderLean, AccountBase { - address private immutable _signer; - - /** - * @dev Initializes the account with the address of the native signer. - */ - constructor(address signerAddr) { - _signer = signerAddr; - } - - /** - * @dev Return the account's signer address. - */ - function signer() public view virtual returns (address) { - return _signer; - } - - /** - * @dev Internal version of {validateUserOp} that relies on {_isValidSignature}. - */ - function _validateUserOp( - PackedUserOperation calldata userOp, - bytes32 userOpHash - ) internal virtual override returns (uint256) { - return - _isValidSignature(userOpHash, userOp.signature) - ? ERC4337Utils.SIG_VALIDATION_SUCCESS - : ERC4337Utils.SIG_VALIDATION_FAILED; - } - - /** - * @dev Validates the signature using the account's signer.S - */ - function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) { - (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature); - return signer() == recovered && err == ECDSA.RecoverError.NoError; - } - - /// @inheritdoc ERC165 - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/contracts/account/draft-AccountP256.sol b/contracts/account/draft-AccountP256.sol deleted file mode 100644 index 2380d9c0e59..00000000000 --- a/contracts/account/draft-AccountP256.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {PackedUserOperation} from "../interfaces/draft-IERC4337.sol"; -import {AccountBase} from "./draft-AccountBase.sol"; -import {ERC1271TypedSigner} from "../utils/cryptography/ERC1271TypedSigner.sol"; -import {P256} from "../utils/cryptography/P256.sol"; -import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol"; -import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; -import {ERC1155HolderLean, IERC1155Receiver} from "../token/ERC1155/utils/ERC1155HolderLean.sol"; -import {ERC165} from "../utils/introspection/ERC165.sol"; -import {IERC165} from "../utils/introspection/IERC165.sol"; - -/** - * @dev Account implementation using {P256} signatures and {ERC1271TypedSigner} for replay protection. - */ -abstract contract AccountP256 is ERC165, ERC1271TypedSigner, ERC721Holder, ERC1155HolderLean, AccountBase { - bytes32 private immutable _qx; - bytes32 private immutable _qy; - - /** - * @dev Initializes the account with the P256 public key. - */ - constructor(bytes32 qx, bytes32 qy) { - _qx = qx; - _qy = qy; - } - - /** - * @dev Return the account's signer P256 public key. - */ - function signer() public view virtual returns (bytes32 qx, bytes32 qy) { - return (_qx, _qy); - } - - /** - * @dev Internal version of {validateUserOp} that relies on {_isValidSignature}. - */ - function _validateUserOp( - PackedUserOperation calldata userOp, - bytes32 userOpHash - ) internal virtual override returns (uint256) { - return - _isValidSignature(userOpHash, userOp.signature) - ? ERC4337Utils.SIG_VALIDATION_SUCCESS - : ERC4337Utils.SIG_VALIDATION_FAILED; - } - - /** - * @dev Validates the signature using the account's signer. - */ - function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) { - if (signature.length < 0x40) return false; - bytes32 r = bytes32(signature[0x00:0x20]); - bytes32 s = bytes32(signature[0x20:0x40]); - (bytes32 qx, bytes32 qy) = signer(); - return P256.verify(hash, r, s, qx, qy); - } - - /// @inheritdoc ERC165 - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/contracts/account/draft-AccountRSA.sol b/contracts/account/draft-AccountRSA.sol deleted file mode 100644 index 32ea675e624..00000000000 --- a/contracts/account/draft-AccountRSA.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {PackedUserOperation} from "../interfaces/draft-IERC4337.sol"; -import {AccountBase} from "./draft-AccountBase.sol"; -import {ERC1271TypedSigner} from "../utils/cryptography/ERC1271TypedSigner.sol"; -import {RSA} from "../utils/cryptography/RSA.sol"; -import {ERC4337Utils} from "./utils/draft-ERC4337Utils.sol"; -import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; -import {ERC1155HolderLean, IERC1155Receiver} from "../token/ERC1155/utils/ERC1155HolderLean.sol"; -import {ERC165} from "../utils/introspection/ERC165.sol"; -import {IERC165} from "../utils/introspection/IERC165.sol"; - -/** - * @dev Account implementation using {RSA} signatures and {ERC1271TypedSigner} for replay protection. - * - * NOTE: Storing `_e` and `_n` in regular storage violate ERC-7562 validation rules if the contract - * is used as an ERC-1271 signer during the validation phase of a different account contract. - * Consider deploying this contract through a factory that sets `_e` and `_n` as immutable arguments - * (see {Clones-cloneDeterministicWithImmutableArgs}). - */ -abstract contract AccountRSA is ERC165, ERC1271TypedSigner, ERC721Holder, ERC1155HolderLean, AccountBase { - bytes private _e; - bytes private _n; - - /** - * @dev Initializes the account with the RSA public key. - */ - constructor(bytes memory e, bytes memory n) { - _e = e; - _n = n; - } - - /** - * @dev Return the account's signer RSA public key. - */ - function signer() public view virtual returns (bytes memory e, bytes memory n) { - return (_e, _n); - } - - /** - * @dev Internal version of {validateUserOp} that relies on {_isValidSignature}. - */ - function _validateUserOp( - PackedUserOperation calldata userOp, - bytes32 userOpHash - ) internal virtual override returns (uint256) { - return - _isValidSignature(userOpHash, userOp.signature) - ? ERC4337Utils.SIG_VALIDATION_SUCCESS - : ERC4337Utils.SIG_VALIDATION_FAILED; - } - - /** - * @dev Validates the signature using the account's signer. - */ - function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) { - (bytes memory e, bytes memory n) = signer(); - return RSA.pkcs1Sha256(hash, signature, e, n); - } - - /// @inheritdoc ERC165 - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/contracts/mocks/account/AccountBaseMock.sol b/contracts/mocks/account/AccountBaseMock.sol deleted file mode 100644 index 8ac9c9f6662..00000000000 --- a/contracts/mocks/account/AccountBaseMock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; -import {AccountBase} from "../../account/draft-AccountBase.sol"; -import {ERC4337Utils} from "../../account/utils/draft-ERC4337Utils.sol"; - -contract AccountBaseMock is AccountBase { - /// Validates a user operation with a boolean signature. - function _validateUserOp( - PackedUserOperation calldata userOp, - bytes32 /* userOpHash */ - ) internal pure override returns (uint256 validationData) { - return - bytes1(userOp.signature[0:1]) == bytes1(0x01) - ? ERC4337Utils.SIG_VALIDATION_SUCCESS - : ERC4337Utils.SIG_VALIDATION_FAILED; - } -} From 7934421d89e498d69f5f794c21a1137c14b4dc64 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 15:21:26 +0200 Subject: [PATCH 20/41] reset vendored contract to the match the source --- contracts/vendor/erc4337-entrypoint/README.md | 1 + .../erc4337-entrypoint/core/EntryPoint.sol | 241 ++++++++++++------ .../erc4337-entrypoint/core/Helpers.sol | 56 ++-- .../erc4337-entrypoint/core/NonceManager.sol | 8 +- .../erc4337-entrypoint/core/SenderCreator.sol | 14 +- .../erc4337-entrypoint/core/StakeManager.sol | 63 +++-- .../core/UserOperationLib.sol | 69 ++--- .../interfaces/IAccount.sol | 2 +- .../interfaces/IAccountExecute.sol | 7 +- .../interfaces/IAggregator.sol | 7 +- .../interfaces/IEntryPoint.sol | 39 ++- .../interfaces/INonceManager.sol | 4 +- .../interfaces/IPaymaster.sol | 2 +- .../interfaces/IStakeManager.sol | 27 +- .../vendor/erc4337-entrypoint/utils/Exec.sol | 22 +- 15 files changed, 375 insertions(+), 187 deletions(-) create mode 100644 contracts/vendor/erc4337-entrypoint/README.md diff --git a/contracts/vendor/erc4337-entrypoint/README.md b/contracts/vendor/erc4337-entrypoint/README.md new file mode 100644 index 00000000000..e0b91fe9dd6 --- /dev/null +++ b/contracts/vendor/erc4337-entrypoint/README.md @@ -0,0 +1 @@ +Files in this directory are vendored from https://github.com/eth-infinitism/account-abstraction/commit/6f02f5a28a20e804d0410b4b5b570dd4b076dcf9 diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 324cc2a4ce4..778115b1637 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -3,24 +3,22 @@ pragma solidity ^0.8.23; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-inline-assembly */ -import {IAccount} from "../interfaces/IAccount.sol"; // OZ edit -import {IAccountExecute} from "../interfaces/IAccountExecute.sol"; // OZ edit -import {IPaymaster} from "../interfaces/IPaymaster.sol"; // OZ edit -import {IEntryPoint} from "../interfaces/IEntryPoint.sol"; // OZ edit - -import {Exec} from "../utils/Exec.sol"; // OZ edit -import {IStakeManager, StakeManager} from "./StakeManager.sol"; // OZ edit -import {SenderCreator} from "./SenderCreator.sol"; // OZ edit -import {ValidationData, _parseValidationData, min} from "./Helpers.sol"; // OZ edit -import {INonceManager, NonceManager} from "./NonceManager.sol"; // OZ edit -import {UserOperationLib, PackedUserOperation} from "./UserOperationLib.sol"; // OZ edit - -import {IAggregator} from "../interfaces/IAggregator.sol"; // OZ edit +import "../interfaces/IAccount.sol"; +import "../interfaces/IAccountExecute.sol"; +import "../interfaces/IPaymaster.sol"; +import "../interfaces/IEntryPoint.sol"; + +import "../utils/Exec.sol"; +import "./StakeManager.sol"; +import "./SenderCreator.sol"; +import "./Helpers.sol"; +import "./NonceManager.sol"; +import "./UserOperationLib.sol"; // import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; // import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {ERC165} from "../../../utils/introspection/ERC165.sol"; // OZ edit -import {ReentrancyGuard} from "../../../utils/ReentrancyGuard.sol"; // OZ edit +import "../../../utils/introspection/ERC165.sol"; // OZ edit +import "../../../utils/ReentrancyGuard.sol"; // OZ edit /* * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. @@ -29,10 +27,15 @@ import {ReentrancyGuard} from "../../../utils/ReentrancyGuard.sol"; // OZ edit /// @custom:security-contact https://bounty.ethereum.org contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ERC165 { + using UserOperationLib for PackedUserOperation; SenderCreator private immutable _senderCreator = new SenderCreator(); + function senderCreator() internal view virtual returns (SenderCreator) { + return _senderCreator; + } + //compensate for innerHandleOps' emit message and deposit refund. // allow some slack for future gas price changes. uint256 private constant INNER_GAS_OVERHEAD = 10000; @@ -44,16 +47,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 private constant REVERT_REASON_MAX_LEN = 2048; uint256 private constant PENALTY_PERCENT = 10; - function senderCreator() internal view virtual returns (SenderCreator) { - return _senderCreator; - } - - /// @inheritdoc ERC165 + /// @inheritdoc IERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything - return - interfaceId == - (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) || + return interfaceId == (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) || interfaceId == type(IEntryPoint).interfaceId || interfaceId == type(IStakeManager).interfaceId || interfaceId == type(INonceManager).interfaceId || @@ -82,7 +79,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 opIndex, PackedUserOperation calldata userOp, UserOpInfo memory opInfo - ) internal returns (uint256 collected) { + ) + internal + returns + (uint256 collected) { uint256 preGas = gasleft(); bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset); bool success; @@ -103,7 +103,8 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, if (methodSig == IAccountExecute.executeUserOp.selector) { bytes memory executeUserOp = abi.encodeCall(IAccountExecute.executeUserOp, (userOp, opInfo.userOpHash)); innerCall = abi.encodeCall(this.innerHandleOp, (executeUserOp, opInfo, context)); - } else { + } else + { innerCall = abi.encodeCall(this.innerHandleOp, (callData, opInfo, context)); } assembly ("memory-safe") { @@ -116,7 +117,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, bytes32 innerRevertCode; assembly ("memory-safe") { let len := returndatasize() - if eq(32, len) { + if eq(32,len) { returndatacopy(0, 0, 32) innerRevertCode := mload(0) } @@ -141,17 +142,17 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ); uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; - collected = _postExecution(IPaymaster.PostOpMode.postOpReverted, opInfo, context, actualGas); + collected = _postExecution( + IPaymaster.PostOpMode.postOpReverted, + opInfo, + context, + actualGas + ); } } } - function emitUserOperationEvent( - UserOpInfo memory opInfo, - bool success, - uint256 actualGasCost, - uint256 actualGas - ) internal virtual { + function emitUserOperationEvent(UserOpInfo memory opInfo, bool success, uint256 actualGasCost, uint256 actualGas) internal virtual { emit UserOperationEvent( opInfo.userOpHash, opInfo.mUserOp.sender, @@ -164,19 +165,34 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, } function emitPrefundTooLow(UserOpInfo memory opInfo) internal virtual { - emit UserOperationPrefundTooLow(opInfo.userOpHash, opInfo.mUserOp.sender, opInfo.mUserOp.nonce); + emit UserOperationPrefundTooLow( + opInfo.userOpHash, + opInfo.mUserOp.sender, + opInfo.mUserOp.nonce + ); } /// @inheritdoc IEntryPoint - function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) public nonReentrant { + function handleOps( + PackedUserOperation[] calldata ops, + address payable beneficiary + ) public nonReentrant { uint256 opslen = ops.length; UserOpInfo[] memory opInfos = new UserOpInfo[](opslen); unchecked { for (uint256 i = 0; i < opslen; i++) { UserOpInfo memory opInfo = opInfos[i]; - (uint256 validationData, uint256 pmValidationData) = _validatePrepayment(i, ops[i], opInfo); - _validateAccountAndPaymasterValidationData(i, validationData, pmValidationData, address(0)); + ( + uint256 validationData, + uint256 pmValidationData + ) = _validatePrepayment(i, ops[i], opInfo); + _validateAccountAndPaymasterValidationData( + i, + validationData, + pmValidationData, + address(0) + ); } uint256 collected = 0; @@ -195,6 +211,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, UserOpsPerAggregator[] calldata opsPerAggregator, address payable beneficiary ) public nonReentrant { + uint256 opasLen = opsPerAggregator.length; uint256 totalOps = 0; for (uint256 i = 0; i < opasLen; i++) { @@ -203,7 +220,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, 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 @@ -226,11 +246,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, 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 - ); + ( + uint256 validationData, + uint256 paymasterValidationData + ) = _validatePrepayment(opIndex, ops[i], opInfo); _validateAccountAndPaymasterValidationData( i, validationData, @@ -306,7 +325,12 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 callGasLimit = mUserOp.callGasLimit; unchecked { // handleOps was called with gas limit too low. abort entire bundle. - if ((gasleft() * 63) / 64 < callGasLimit + mUserOp.paymasterPostOpGasLimit + INNER_GAS_OVERHEAD) { + if ( + gasleft() * 63 / 64 < + callGasLimit + + mUserOp.paymasterPostOpGasLimit + + INNER_GAS_OVERHEAD + ) { assembly ("memory-safe") { mstore(0, INNER_OUT_OF_GAS) revert(0, 32) @@ -320,7 +344,12 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, 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; } @@ -333,8 +362,11 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, } /// @inheritdoc IEntryPoint - function getUserOpHash(PackedUserOperation calldata userOp) public view returns (bytes32) { - return keccak256(abi.encode(userOp.hash(), address(this), block.chainid)); + function getUserOpHash( + PackedUserOperation calldata userOp + ) public view returns (bytes32) { + return + keccak256(abi.encode(userOp.hash(), address(this), block.chainid)); } /** @@ -342,7 +374,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, * @param userOp - The user operation. * @param mUserOp - The memory user operation. */ - function _copyUserOpToMemory(PackedUserOperation calldata userOp, MemoryUserOp memory mUserOp) internal pure { + function _copyUserOpToMemory( + PackedUserOperation calldata userOp, + MemoryUserOp memory mUserOp + ) internal pure { mUserOp.sender = userOp.sender; mUserOp.nonce = userOp.nonce; (mUserOp.verificationGasLimit, mUserOp.callGasLimit) = UserOperationLib.unpackUints(userOp.accountGasLimits); @@ -350,12 +385,11 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, (mUserOp.maxPriorityFeePerGas, mUserOp.maxFeePerGas) = UserOperationLib.unpackUints(userOp.gasFees); bytes calldata paymasterAndData = userOp.paymasterAndData; if (paymasterAndData.length > 0) { - require(paymasterAndData.length >= UserOperationLib.PAYMASTER_DATA_OFFSET, "AA93 invalid paymasterAndData"); - ( - mUserOp.paymaster, - mUserOp.paymasterVerificationGasLimit, - mUserOp.paymasterPostOpGasLimit - ) = UserOperationLib.unpackPaymasterStaticFields(paymasterAndData); + require( + paymasterAndData.length >= UserOperationLib.PAYMASTER_DATA_OFFSET, + "AA93 invalid paymasterAndData" + ); + (mUserOp.paymaster, mUserOp.paymasterVerificationGasLimit, mUserOp.paymasterPostOpGasLimit) = UserOperationLib.unpackPaymasterStaticFields(paymasterAndData); } else { mUserOp.paymaster = address(0); mUserOp.paymasterVerificationGasLimit = 0; @@ -367,7 +401,9 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, * 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) { + function _getRequiredPrefund( + MemoryUserOp memory mUserOp + ) internal pure returns (uint256 requiredPrefund) { unchecked { uint256 requiredGas = mUserOp.verificationGasLimit + mUserOp.callGasLimit + @@ -385,16 +421,31 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, * @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 { + 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"); + 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); + emit AccountDeployed( + opInfo.userOpHash, + sender, + factory, + opInfo.mUserOp.paymaster + ); } } @@ -419,7 +470,12 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, UserOpInfo memory opInfo, uint256 requiredPrefund, uint256 verificationGasLimit - ) internal returns (uint256 validationData) { + ) + internal + returns ( + uint256 validationData + ) + { unchecked { MemoryUserOp memory mUserOp = opInfo.mUserOp; address sender = mUserOp.sender; @@ -428,17 +484,21 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 missingAccountFunds = 0; if (paymaster == address(0)) { uint256 bal = balanceOf(sender); - missingAccountFunds = bal > requiredPrefund ? 0 : requiredPrefund - bal; + missingAccountFunds = bal > requiredPrefund + ? 0 + : requiredPrefund - bal; } try - IAccount(sender).validateUserOp{gas: verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds) + IAccount(sender).validateUserOp{ + gas: verificationGasLimit + }(op, opInfo.userOpHash, missingAccountFunds) returns (uint256 _validationData) { validationData = _validationData; } catch { revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN)); } if (paymaster == address(0)) { - DepositInfo storage senderInfo = deposits(sender); + DepositInfo storage senderInfo = deposits[sender]; uint256 deposit = senderInfo.deposit; if (requiredPrefund > deposit) { revert FailedOp(opIndex, "AA21 didn't pay prefund"); @@ -469,7 +529,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 preGas = gasleft(); MemoryUserOp memory mUserOp = opInfo.mUserOp; address paymaster = mUserOp.paymaster; - DepositInfo storage paymasterInfo = deposits(paymaster); + DepositInfo storage paymasterInfo = deposits[paymaster]; uint256 deposit = paymasterInfo.deposit; if (deposit < requiredPreFund) { revert FailedOp(opIndex, "AA31 paymaster deposit too low"); @@ -507,7 +567,9 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 paymasterValidationData, address expectedAggregator ) internal view { - (address aggregator, bool outOfTimeRange) = _getValidationData(validationData); + (address aggregator, bool outOfTimeRange) = _getValidationData( + validationData + ); if (expectedAggregator != aggregator) { revert FailedOp(opIndex, "AA24 signature error"); } @@ -517,7 +579,9 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, // 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"); } @@ -555,7 +619,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 opIndex, PackedUserOperation calldata userOp, UserOpInfo memory outOpInfo - ) internal returns (uint256 validationData, uint256 paymasterValidationData) { + ) + internal + returns (uint256 validationData, uint256 paymasterValidationData) + { uint256 preGas = gasleft(); MemoryUserOp memory mUserOp = outOpInfo.mUserOp; _copyUserOpToMemory(userOp, mUserOp); @@ -574,7 +641,13 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, require(maxGasValues <= type(uint120).max, "AA94 gas values overflow"); uint256 requiredPreFund = _getRequiredPrefund(mUserOp); - validationData = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, verificationGasLimit); + validationData = _validateAccountPrepayment( + opIndex, + userOp, + outOpInfo, + requiredPreFund, + verificationGasLimit + ); if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) { revert FailedOp(opIndex, "AA25 invalid account nonce"); @@ -631,17 +704,11 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, if (context.length > 0) { actualGasCost = actualGas * gasPrice; if (mode != IPaymaster.PostOpMode.postOpReverted) { - try - IPaymaster(paymaster).postOp{gas: mUserOp.paymasterPostOpGasLimit}( - mode, - context, - actualGasCost, - gasPrice - ) + try IPaymaster(paymaster).postOp{ + gas: mUserOp.paymasterPostOpGasLimit + }(mode, context, actualGasCost, gasPrice) // solhint-disable-next-line no-empty-blocks - { - - } catch { + {} catch { bytes memory reason = Exec.getReturnData(REVERT_REASON_MAX_LEN); revert PostOpReverted(reason); } @@ -689,7 +756,9 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, * 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) { + function getUserOpGasPrice( + MemoryUserOp memory mUserOp + ) internal view returns (uint256) { unchecked { uint256 maxFeePerGas = mUserOp.maxFeePerGas; uint256 maxPriorityFeePerGas = mUserOp.maxPriorityFeePerGas; @@ -705,7 +774,9 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, * 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) { + function getOffsetOfMemoryBytes( + bytes memory data + ) internal pure returns (uint256 offset) { assembly { offset := data } @@ -715,7 +786,9 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, * 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) { + function getMemoryBytesFromOffset( + uint256 offset + ) internal pure returns (bytes memory data) { assembly ("memory-safe") { data := offset } diff --git a/contracts/vendor/erc4337-entrypoint/core/Helpers.sol b/contracts/vendor/erc4337-entrypoint/core/Helpers.sol index cab842e29cb..8579008613a 100644 --- a/contracts/vendor/erc4337-entrypoint/core/Helpers.sol +++ b/contracts/vendor/erc4337-entrypoint/core/Helpers.sol @@ -3,18 +3,21 @@ pragma solidity ^0.8.23; /* solhint-disable no-inline-assembly */ -/* - * For simulation purposes, validateUserOp (and validatePaymasterUserOp) - * must return this value in case of signature failure, instead of revert. - */ + + /* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * must return this value in case of signature failure, instead of revert. + */ uint256 constant SIG_VALIDATION_FAILED = 1; + /* * For simulation purposes, validateUserOp (and validatePaymasterUserOp) * return this value on success. */ uint256 constant SIG_VALIDATION_SUCCESS = 0; + /** * Returned data from validateUserOp. * validateUserOp returns a uint256, which is created by `_packedValidationData` and @@ -37,7 +40,9 @@ struct ValidationData { * Also convert zero validUntil to type(uint48).max. * @param validationData - The packed validation data. */ -function _parseValidationData(uint256 validationData) pure returns (ValidationData memory data) { +function _parseValidationData( + uint256 validationData +) pure returns (ValidationData memory data) { address aggregator = address(uint160(validationData)); uint48 validUntil = uint48(validationData >> 160); if (validUntil == 0) { @@ -51,8 +56,13 @@ function _parseValidationData(uint256 validationData) pure returns (ValidationDa * 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)); } /** @@ -61,28 +71,36 @@ function _packValidationData(ValidationData memory data) pure returns (uint256) * @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)); } /** * keccak function over calldata. * @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it. */ -function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) { - assembly ("memory-safe") { - let mem := mload(0x40) - let len := data.length - calldatacopy(mem, data.offset, len) - ret := keccak256(mem, len) + function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) { + assembly ("memory-safe") { + let mem := mload(0x40) + let len := data.length + calldatacopy(mem, data.offset, len) + ret := keccak256(mem, len) + } } -} + /** * The minimum of two numbers. * @param a - First number. * @param b - Second number. */ -function min(uint256 a, uint256 b) pure returns (uint256) { - return a < b ? a : b; -} + function min(uint256 a, uint256 b) pure returns (uint256) { + return a < b ? a : b; + } diff --git a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol index be4abbdd8c3..7bef62e99dd 100644 --- a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol @@ -1,19 +1,21 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; -import {INonceManager} from "../interfaces/INonceManager.sol"; // OZ edit +import "../interfaces/INonceManager.sol"; /** * nonce management functionality */ abstract contract NonceManager is INonceManager { + /** * The next valid sequence number for a given nonce key. */ mapping(address => mapping(uint192 => uint256)) public nonceSequenceNumber; /// @inheritdoc INonceManager - function getNonce(address sender, uint192 key) public view override returns (uint256 nonce) { + function getNonce(address sender, uint192 key) + public view override returns (uint256 nonce) { return nonceSequenceNumber[sender][key] | (uint256(key) << 64); } @@ -32,8 +34,10 @@ abstract contract NonceManager is INonceManager { * false if the current nonce doesn't match the given one. */ function _validateAndUpdateNonce(address sender, uint256 nonce) internal returns (bool) { + uint192 key = uint192(nonce >> 64); uint64 seq = uint64(nonce); return nonceSequenceNumber[sender][key]++ == seq; } + } diff --git a/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol b/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol index 91ac6c882e1..43ea80367ef 100644 --- a/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol +++ b/contracts/vendor/erc4337-entrypoint/core/SenderCreator.sol @@ -12,13 +12,23 @@ contract SenderCreator { * 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) { + function createSender( + bytes calldata initCode + ) external returns (address sender) { address factory = address(bytes20(initCode[0:20])); bytes memory initCallData = initCode[20:]; bool success; /* solhint-disable no-inline-assembly */ assembly ("memory-safe") { - success := call(gas(), factory, 0, add(initCallData, 0x20), mload(initCallData), 0, 32) + success := call( + gas(), + factory, + 0, + add(initCallData, 0x20), + mload(initCallData), + 0, + 32 + ) sender := mload(0) } if (!success) { diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 8cf3bae96fb..f90210b7e38 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.23; -import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit +import "../interfaces/IStakeManager.sol"; /* solhint-disable avoid-low-level-calls */ /* solhint-disable not-rely-on-time */ @@ -13,32 +13,30 @@ import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit */ abstract contract StakeManager is IStakeManager { /// maps paymaster to their deposits and stakes - mapping(address => DepositInfo) private _deposits; // OZ edit - - // OZ edit - function deposits(address account) internal view returns (DepositInfo storage d) { - DepositInfo storage depositInfo = _deposits[account]; - return depositInfo; - } + mapping(address => DepositInfo) public deposits; /// @inheritdoc IStakeManager - function getDepositInfo(address account) public view returns (DepositInfo memory info) { - return _deposits[account]; + function getDepositInfo( + address account + ) public view returns (DepositInfo memory info) { + return deposits[account]; } /** * Internal method to return just the stake info. * @param addr - The account to query. */ - function _getStakeInfo(address addr) internal view returns (StakeInfo memory info) { - DepositInfo storage depositInfo = _deposits[addr]; + function _getStakeInfo( + address addr + ) internal view returns (StakeInfo memory info) { + DepositInfo storage depositInfo = deposits[addr]; info.stake = depositInfo.stake; info.unstakeDelaySec = depositInfo.unstakeDelaySec; } /// @inheritdoc IStakeManager function balanceOf(address account) public view returns (uint256) { - return _deposits[account].deposit; + return deposits[account].deposit; } receive() external payable { @@ -52,7 +50,7 @@ abstract contract StakeManager is IStakeManager { * @return the updated deposit of this account */ function _incrementDeposit(address account, uint256 amount) internal returns (uint256) { - DepositInfo storage info = _deposits[account]; + DepositInfo storage info = deposits[account]; uint256 newAmount = info.deposit + amount; info.deposit = newAmount; return newAmount; @@ -62,7 +60,7 @@ abstract contract StakeManager is IStakeManager { * Add to the deposit of the given account. * @param account - The account to add to. */ - function depositTo(address account) public payable virtual { + function depositTo(address account) public virtual payable { uint256 newDeposit = _incrementDeposit(account, msg.value); emit Deposited(account, newDeposit); } @@ -73,13 +71,22 @@ abstract contract StakeManager is IStakeManager { * @param unstakeDelaySec The new lock duration before the deposit can be withdrawn. */ function addStake(uint32 unstakeDelaySec) public payable { - DepositInfo storage info = _deposits[msg.sender]; + 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"); - _deposits[msg.sender] = DepositInfo(info.deposit, true, uint112(stake), unstakeDelaySec, 0); + deposits[msg.sender] = DepositInfo( + info.deposit, + true, + uint112(stake), + unstakeDelaySec, + 0 + ); emit StakeLocked(msg.sender, stake, unstakeDelaySec); } @@ -88,7 +95,7 @@ abstract contract StakeManager is IStakeManager { * The value can be withdrawn (using withdrawStake) after the unstake delay. */ function unlockStake() external { - DepositInfo storage info = _deposits[msg.sender]; + DepositInfo storage info = deposits[msg.sender]; require(info.unstakeDelaySec != 0, "not staked"); require(info.staked, "already unstaking"); uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec; @@ -103,16 +110,19 @@ abstract contract StakeManager is IStakeManager { * @param withdrawAddress - The address to send withdrawn value. */ function withdrawStake(address payable withdrawAddress) external { - DepositInfo storage info = _deposits[msg.sender]; + 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"); } @@ -121,12 +131,15 @@ abstract contract StakeManager is IStakeManager { * @param withdrawAddress - The address to send withdrawn value. * @param withdrawAmount - The amount to withdraw. */ - function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external { - DepositInfo storage info = _deposits[msg.sender]; + function withdrawTo( + address payable withdrawAddress, + uint256 withdrawAmount + ) external { + DepositInfo storage info = deposits[msg.sender]; require(withdrawAmount <= info.deposit, "Withdraw amount too large"); info.deposit = info.deposit - withdrawAmount; emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount); - (bool success, ) = withdrawAddress.call{value: withdrawAmount}(""); + (bool success,) = withdrawAddress.call{value: withdrawAmount}(""); require(success, "failed to withdraw"); } } diff --git a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol index e9b8d2d0079..dcf5740cc28 100644 --- a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol +++ b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol @@ -3,22 +3,24 @@ pragma solidity ^0.8.23; /* solhint-disable no-inline-assembly */ -import {PackedUserOperation} from "../interfaces/PackedUserOperation.sol"; +import "../interfaces/PackedUserOperation.sol"; import {calldataKeccak, min} from "./Helpers.sol"; /** * Utility functions helpful when working with UserOperation structs. */ library UserOperationLib { + uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20; uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36; uint256 public constant PAYMASTER_DATA_OFFSET = 52; - /** * Get sender from user operation data. * @param userOp - The user operation data. */ - function getSender(PackedUserOperation calldata userOp) internal pure returns (address) { + function getSender( + PackedUserOperation calldata userOp + ) internal pure returns (address) { address data; //read sender from userOp, which is first userOp member (saves 800 gas...) assembly { @@ -32,7 +34,9 @@ library UserOperationLib { * but the user should not pay above what he signed for. * @param userOp - The user operation data. */ - function gasPrice(PackedUserOperation calldata userOp) internal view returns (uint256) { + function gasPrice( + PackedUserOperation calldata userOp + ) internal view returns (uint256) { unchecked { (uint256 maxPriorityFeePerGas, uint256 maxFeePerGas) = unpackUints(userOp.gasFees); if (maxFeePerGas == maxPriorityFeePerGas) { @@ -47,7 +51,9 @@ library UserOperationLib { * Pack the user operation data into bytes for hashing. * @param userOp - The user operation data. */ - function encode(PackedUserOperation calldata userOp) internal pure returns (bytes memory ret) { + function encode( + PackedUserOperation calldata userOp + ) internal pure returns (bytes memory ret) { address sender = getSender(userOp); uint256 nonce = userOp.nonce; bytes32 hashInitCode = calldataKeccak(userOp.initCode); @@ -57,20 +63,17 @@ library UserOperationLib { bytes32 gasFees = userOp.gasFees; bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData); - return - abi.encode( - sender, - nonce, - hashInitCode, - hashCallData, - accountGasLimits, - preVerificationGas, - gasFees, - hashPaymasterAndData - ); + return abi.encode( + sender, nonce, + hashInitCode, hashCallData, + accountGasLimits, preVerificationGas, gasFees, + hashPaymasterAndData + ); } - function unpackUints(bytes32 packed) internal pure returns (uint256 high128, uint256 low128) { + function unpackUints( + bytes32 packed + ) internal pure returns (uint256 high128, uint256 low128) { return (uint128(bytes16(packed)), uint128(uint256(packed))); } @@ -84,37 +87,43 @@ library UserOperationLib { return uint128(uint256(packed)); } - function unpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + function unpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp) + internal pure returns (uint256) { return unpackHigh128(userOp.gasFees); } - function unpackMaxFeePerGas(PackedUserOperation calldata userOp) internal pure returns (uint256) { + function unpackMaxFeePerGas(PackedUserOperation calldata userOp) + internal pure returns (uint256) { return unpackLow128(userOp.gasFees); } - function unpackVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + function unpackVerificationGasLimit(PackedUserOperation calldata userOp) + internal pure returns (uint256) { return unpackHigh128(userOp.accountGasLimits); } - function unpackCallGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { + function unpackCallGasLimit(PackedUserOperation calldata userOp) + internal pure returns (uint256) { return unpackLow128(userOp.accountGasLimits); } - function unpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { - return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])); + function unpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp) + internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET])); } - function unpackPostOpGasLimit(PackedUserOperation calldata userOp) internal pure returns (uint256) { - return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])); + function unpackPostOpGasLimit(PackedUserOperation calldata userOp) + internal pure returns (uint256) { + return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET])); } function unpackPaymasterStaticFields( bytes calldata paymasterAndData ) internal pure returns (address paymaster, uint256 validationGasLimit, uint256 postOpGasLimit) { return ( - address(bytes20(paymasterAndData[:PAYMASTER_VALIDATION_GAS_OFFSET])), - uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_POSTOP_GAS_OFFSET])), - uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET:PAYMASTER_DATA_OFFSET])) + address(bytes20(paymasterAndData[: PAYMASTER_VALIDATION_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET])), + uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET])) ); } @@ -122,7 +131,9 @@ library UserOperationLib { * Hash the user operation data. * @param userOp - The user operation data. */ - function hash(PackedUserOperation calldata userOp) internal pure returns (bytes32) { + function hash( + PackedUserOperation calldata userOp + ) internal pure returns (bytes32) { return keccak256(encode(userOp)); } } diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol index 03c11aaa2ae..e3b355fbc27 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; interface IAccount { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol index 99afbb43862..4433c80ce3e 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; interface IAccountExecute { /** @@ -13,5 +13,8 @@ interface IAccountExecute { * @param userOp - The operation that was just validated. * @param userOpHash - Hash of the user's request data. */ - function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; + function executeUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) external; } diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol index 1a8ba1f52a5..070d8f27a2c 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; /** * Aggregated Signatures validator. @@ -13,7 +13,10 @@ interface IAggregator { * @param userOps - Array of UserOperations to validate the signature for. * @param signature - The aggregated signature. */ - function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; + function validateSignatures( + PackedUserOperation[] calldata userOps, + bytes calldata signature + ) external view; /** * Validate signature of a single userOp. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol index 1a08e91839d..28c26f98e6c 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -9,10 +9,10 @@ pragma solidity >=0.7.5; /* solhint-disable no-inline-assembly */ /* solhint-disable reason-string */ -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit -import {IStakeManager} from "./IStakeManager.sol"; // OZ Edit -import {IAggregator} from "./IAggregator.sol"; // OZ Edit -import {INonceManager} from "./INonceManager.sol"; // OZ Edit +import "./PackedUserOperation.sol"; +import "./IStakeManager.sol"; +import "./IAggregator.sol"; +import "./INonceManager.sol"; interface IEntryPoint is IStakeManager, INonceManager { /*** @@ -43,7 +43,12 @@ interface IEntryPoint is IStakeManager, INonceManager { * @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); + event AccountDeployed( + bytes32 indexed userOpHash, + address indexed sender, + address factory, + address paymaster + ); /** * An event emitted if the UserOperation "callData" reverted with non-zero length. @@ -66,7 +71,12 @@ interface IEntryPoint is IStakeManager, INonceManager { * @param nonce - The nonce used in the request. * @param revertReason - The return bytes from the (reverted) call to "callData". */ - event PostOpRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason); + event PostOpRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); /** * UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made. @@ -74,7 +84,11 @@ interface IEntryPoint is IStakeManager, INonceManager { * @param sender - The sender of this request. * @param nonce - The nonce used in the request. */ - event UserOperationPrefundTooLow(bytes32 indexed userOpHash, address indexed sender, uint256 nonce); + event UserOperationPrefundTooLow( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce + ); /** * An event emitted by handleOps(), before starting the execution loop. @@ -104,7 +118,7 @@ interface IEntryPoint is IStakeManager, INonceManager { * A custom revert error of handleOps, to report a revert by account or paymaster. * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). * @param reason - Revert reason. see FailedOp(uint256,string), above - * @param inner - data from inner caught revert reason + * @param inner - data from inner cought revert reason * @dev note that inner is truncated to 2048 bytes */ error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); @@ -137,7 +151,10 @@ interface IEntryPoint is IStakeManager, INonceManager { * @param ops - The operations to execute. * @param beneficiary - The address to receive the fees. */ - function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + function handleOps( + PackedUserOperation[] calldata ops, + address payable beneficiary + ) external; /** * Execute a batch of UserOperation with Aggregators @@ -155,7 +172,9 @@ interface IEntryPoint is IStakeManager, INonceManager { * @param userOp - The user operation to generate the request ID for. * @return hash the hash of this UserOperation */ - function getUserOpHash(PackedUserOperation calldata userOp) external view returns (bytes32); + function getUserOpHash( + PackedUserOperation calldata userOp + ) external view returns (bytes32); /** * Gas and return values during simulation. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol b/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol index 2a5a2725fd7..2f993f6875c 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/INonceManager.sol @@ -2,6 +2,7 @@ pragma solidity >=0.7.5; interface INonceManager { + /** * Return the next nonce for this sender. * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop) @@ -11,7 +12,8 @@ interface INonceManager { * @param key the high 192 bit of the nonce * @return nonce a full nonce to pass for next UserOp with this sender. */ - function getNonce(address sender, uint192 key) external view returns (uint256 nonce); + function getNonce(address sender, uint192 key) + external view returns (uint256 nonce); /** * Manually increment the nonce of the sender. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol index d39a60068fb..9176a0b242a 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; /** * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol index 01612d9c0b4..69083e93f5a 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IStakeManager.sol @@ -9,15 +9,27 @@ pragma solidity >=0.7.5; interface IStakeManager { event Deposited(address indexed account, uint256 totalDeposit); - event Withdrawn(address indexed account, address withdrawAddress, uint256 amount); + event Withdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); // Emitted when stake or unstake delay are modified. - event StakeLocked(address indexed account, uint256 totalStaked, uint256 unstakeDelaySec); + event StakeLocked( + address indexed account, + uint256 totalStaked, + uint256 unstakeDelaySec + ); // Emitted once a stake is scheduled for withdrawal. event StakeUnlocked(address indexed account, uint256 withdrawTime); - event StakeWithdrawn(address indexed account, address withdrawAddress, uint256 amount); + event StakeWithdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); /** * @param deposit - The entity's deposit. @@ -50,7 +62,9 @@ interface IStakeManager { * @param account - The account to query. * @return info - Full deposit information of given account. */ - function getDepositInfo(address account) external view returns (DepositInfo memory info); + function getDepositInfo( + address account + ) external view returns (DepositInfo memory info); /** * Get account balance. @@ -90,5 +104,8 @@ interface IStakeManager { * @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/vendor/erc4337-entrypoint/utils/Exec.sol b/contracts/vendor/erc4337-entrypoint/utils/Exec.sol index a245deddd5a..ee8d71ac0d8 100644 --- a/contracts/vendor/erc4337-entrypoint/utils/Exec.sol +++ b/contracts/vendor/erc4337-entrypoint/utils/Exec.sol @@ -7,19 +7,33 @@ pragma solidity ^0.8.23; * Utility functions helpful when making different kinds of contract calls in Solidity. */ library Exec { - function call(address to, uint256 value, bytes memory data, uint256 txGas) internal returns (bool success) { + + function call( + address to, + uint256 value, + bytes memory data, + uint256 txGas + ) internal returns (bool success) { assembly ("memory-safe") { success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) } } - function staticcall(address to, bytes memory data, uint256 txGas) internal view returns (bool success) { + function staticcall( + address to, + bytes memory data, + uint256 txGas + ) internal view returns (bool success) { assembly ("memory-safe") { success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0) } } - function delegateCall(address to, bytes memory data, uint256 txGas) internal returns (bool success) { + function delegateCall( + address to, + bytes memory data, + uint256 txGas + ) internal returns (bool success) { assembly ("memory-safe") { success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) } @@ -48,7 +62,7 @@ library Exec { } function callAndRevert(address to, bytes memory data, uint256 maxLen) internal { - bool success = call(to, 0, data, gasleft()); + bool success = call(to,0,data,gasleft()); if (!success) { revertWithData(getReturnData(maxLen)); } From a19428970ffc77a1c72bc9d7b313e1fd3d47f9be Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 15:33:42 +0200 Subject: [PATCH 21/41] remove contracts/abstraction, that was replaced by contracts/account --- contracts/abstraction/utils/ERC4337Utils.sol | 198 ------------------- contracts/abstraction/utils/ERC7579Utils.sol | 115 ----------- 2 files changed, 313 deletions(-) delete mode 100644 contracts/abstraction/utils/ERC4337Utils.sol delete mode 100644 contracts/abstraction/utils/ERC7579Utils.sol diff --git a/contracts/abstraction/utils/ERC4337Utils.sol b/contracts/abstraction/utils/ERC4337Utils.sol deleted file mode 100644 index 3d95db10815..00000000000 --- a/contracts/abstraction/utils/ERC4337Utils.sol +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {IEntryPoint, PackedUserOperation} from "../../interfaces/IERC4337.sol"; -import {Math} from "../../utils/math/Math.sol"; -// import {Memory} from "../../utils/Memory.sol"; -import {Packing} from "../../utils/Packing.sol"; - -library ERC4337Utils { - using Packing for *; - /* - * For simulation purposes, validateUserOp (and validatePaymasterUserOp) - * return this value on success. - */ - uint256 internal constant SIG_VALIDATION_SUCCESS = 0; - - /* - * For simulation purposes, validateUserOp (and validatePaymasterUserOp) - * must return this value in case of signature failure, instead of revert. - */ - uint256 internal constant SIG_VALIDATION_FAILED = 1; - - // Validation data - function parseValidationData( - uint256 validationData - ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { - validAfter = uint48(bytes32(validationData).extract_32_6(0x00)); - validUntil = uint48(bytes32(validationData).extract_32_6(0x06)); - aggregator = address(bytes32(validationData).extract_32_20(0x0c)); - if (validUntil == 0) validUntil = type(uint48).max; - } - - function packValidationData( - address aggregator, - uint48 validAfter, - uint48 validUntil - ) internal pure returns (uint256) { - return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); - } - - function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { - return - uint256( - bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20( - bytes20(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))) - ) - ); - } - - function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) { - (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1); - (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2); - - bool success = aggregator1 == address(0) && aggregator2 == address(0); - uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); - uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); - return packValidationData(success, validAfter, validUntil); - } - - function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { - if (validationData == 0) { - return (address(0), false); - } else { - (address agregator, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData); - return (agregator, block.timestamp > validUntil || block.timestamp < validAfter); - } - } - - // Packed user operation - function hash(PackedUserOperation calldata self) internal view returns (bytes32) { - return hash(self, address(this), block.chainid); - } - - function hash( - PackedUserOperation calldata self, - address entrypoint, - uint256 chainid - ) internal pure returns (bytes32) { - // Memory.FreePtr ptr = Memory.save(); - bytes32 result = keccak256( - abi.encode( - keccak256( - abi.encode( - self.sender, - self.nonce, - keccak256(self.initCode), - keccak256(self.callData), - self.accountGasLimits, - self.preVerificationGas, - self.gasFees, - keccak256(self.paymasterAndData) - ) - ), - entrypoint, - chainid - ) - ); - // Memory.load(ptr); - return result; - } - - function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.accountGasLimits.extract_32_16(0x00)); - } - - function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.accountGasLimits.extract_32_16(0x10)); - } - - function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.gasFees.extract_32_16(0x00)); - } - - function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.gasFees.extract_32_16(0x10)); - } - - function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { - unchecked { - // Following values are "per gas" - uint256 maxPriorityFee = maxPriorityFeePerGas(self); - uint256 maxFee = maxFeePerGas(self); - return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); - } - } - - function paymaster(PackedUserOperation calldata self) internal pure returns (address) { - return address(bytes20(self.paymasterAndData[0:20])); - } - - function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(bytes16(self.paymasterAndData[20:36])); - } - - function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(bytes16(self.paymasterAndData[36:52])); - } - - struct UserOpInfo { - address sender; - uint256 nonce; - uint256 verificationGasLimit; - uint256 callGasLimit; - uint256 paymasterVerificationGasLimit; - uint256 paymasterPostOpGasLimit; - uint256 preVerificationGas; - address paymaster; - uint256 maxFeePerGas; - uint256 maxPriorityFeePerGas; - bytes32 userOpHash; - uint256 prefund; - uint256 preOpGas; - bytes context; - } - - function load(UserOpInfo memory self, PackedUserOperation calldata source) internal view { - self.sender = source.sender; - self.nonce = source.nonce; - self.verificationGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x00)); - self.callGasLimit = uint128(bytes32(source.accountGasLimits).extract_32_16(0x10)); - self.preVerificationGas = source.preVerificationGas; - self.maxPriorityFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x00)); - self.maxFeePerGas = uint128(bytes32(source.gasFees).extract_32_16(0x10)); - - if (source.paymasterAndData.length > 0) { - require(source.paymasterAndData.length >= 52, "AA93 invalid paymasterAndData"); - self.paymaster = paymaster(source); - self.paymasterVerificationGasLimit = paymasterVerificationGasLimit(source); - self.paymasterPostOpGasLimit = paymasterPostOpGasLimit(source); - } else { - self.paymaster = address(0); - self.paymasterVerificationGasLimit = 0; - self.paymasterPostOpGasLimit = 0; - } - self.userOpHash = hash(source); - self.prefund = 0; - self.preOpGas = 0; - self.context = ""; - } - - function requiredPrefund(UserOpInfo memory self) internal pure returns (uint256) { - return - (self.verificationGasLimit + - self.callGasLimit + - self.paymasterVerificationGasLimit + - self.paymasterPostOpGasLimit + - self.preVerificationGas) * self.maxFeePerGas; - } - - function gasPrice(UserOpInfo memory self) internal view returns (uint256) { - unchecked { - uint256 maxFee = self.maxFeePerGas; - uint256 maxPriorityFee = self.maxPriorityFeePerGas; - return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); - } - } -} diff --git a/contracts/abstraction/utils/ERC7579Utils.sol b/contracts/abstraction/utils/ERC7579Utils.sol deleted file mode 100644 index b35e157101b..00000000000 --- a/contracts/abstraction/utils/ERC7579Utils.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Execution} from "../../interfaces/IERC7579Account.sol"; -import {Packing} from "../../utils/Packing.sol"; - -type Mode is bytes32; -type CallType is bytes1; -type ExecType is bytes1; -type ModeSelector is bytes4; -type ModePayload is bytes22; - -library ERC7579Utils { - using Packing for *; - - CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); - CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); - CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); - ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); - ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); - - function encodeMode( - CallType callType, - ExecType execType, - ModeSelector selector, - ModePayload payload - ) internal pure returns (Mode mode) { - return - Mode.wrap( - CallType - .unwrap(callType) - .pack_1_1(ExecType.unwrap(execType)) - .pack_2_4(bytes4(0)) - .pack_6_4(ModeSelector.unwrap(selector)) - .pack_10_22(ModePayload.unwrap(payload)) - ); - } - - function decodeMode( - Mode mode - ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) { - return ( - CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)), - ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)), - ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)), - ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10)) - ); - } - - function encodeSingle( - address target, - uint256 value, - bytes memory callData - ) internal pure returns (bytes memory executionCalldata) { - return abi.encodePacked(target, value, callData); - } - - function decodeSingle( - bytes calldata executionCalldata - ) internal pure returns (address target, uint256 value, bytes calldata callData) { - target = address(bytes20(executionCalldata[0:20])); - value = uint256(bytes32(executionCalldata[20:52])); - callData = executionCalldata[52:]; - } - - function encodeDelegate( - address target, - bytes memory callData - ) internal pure returns (bytes memory executionCalldata) { - return abi.encodePacked(target, callData); - } - - function decodeDelegate( - bytes calldata executionCalldata - ) internal pure returns (address target, bytes calldata callData) { - target = address(bytes20(executionCalldata[0:20])); - callData = executionCalldata[20:]; - } - - function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) { - return abi.encode(executionBatch); - } - - function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { - assembly ("memory-safe") { - let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) - // Extract the ERC7579 Executions - executionBatch.offset := add(ptr, 32) - executionBatch.length := calldataload(ptr) - } - } -} - -// Operators -using {eqCallType as ==} for CallType global; -using {eqExecType as ==} for ExecType global; -using {eqModeSelector as ==} for ModeSelector global; -using {eqModePayload as ==} for ModePayload global; - -function eqCallType(CallType a, CallType b) pure returns (bool) { - return CallType.unwrap(a) == CallType.unwrap(b); -} - -function eqExecType(ExecType a, ExecType b) pure returns (bool) { - return ExecType.unwrap(a) == ExecType.unwrap(b); -} - -function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { - return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); -} - -function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) { - return ModePayload.unwrap(a) == ModePayload.unwrap(b); -} From 8be86a7bf1bd67dc5c198f987d496a371af4206f Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 15:59:25 +0200 Subject: [PATCH 22/41] remove duplicated --- contracts/interfaces/IERC4337.sol | 118 ----------------------- contracts/interfaces/IERC7579Account.sol | 114 ---------------------- contracts/interfaces/IERC7579Module.sol | 89 ----------------- 3 files changed, 321 deletions(-) delete mode 100644 contracts/interfaces/IERC4337.sol delete mode 100644 contracts/interfaces/IERC7579Account.sol delete mode 100644 contracts/interfaces/IERC7579Module.sol diff --git a/contracts/interfaces/IERC4337.sol b/contracts/interfaces/IERC4337.sol deleted file mode 100644 index 0f681b74aba..00000000000 --- a/contracts/interfaces/IERC4337.sol +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -/* -struct UserOperation { - address sender; // The account making the operation - uint256 nonce; // Anti-replay parameter (see “Semi-abstracted Nonce Support” ) - address factory; // account factory, only for new accounts - bytes factoryData; // data for account factory (only if account factory exists) - bytes callData; // The data to pass to the sender during the main execution call - uint256 callGasLimit; // The amount of gas to allocate the main execution call - uint256 verificationGasLimit; // The amount of gas to allocate for the verification step - uint256 preVerificationGas; // Extra gas to pay the bunder - uint256 maxFeePerGas; // Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) - uint256 maxPriorityFeePerGas; // Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) - address paymaster; // Address of paymaster contract, (or empty, if account pays for itself) - uint256 paymasterVerificationGasLimit; // The amount of gas to allocate for the paymaster validation code - uint256 paymasterPostOpGasLimit; // The amount of gas to allocate for the paymaster post-operation code - bytes paymasterData; // Data for paymaster (only if paymaster exists) - bytes signature; // Data passed into the account to verify authorization -} -*/ - -struct PackedUserOperation { - address sender; - uint256 nonce; - bytes initCode; // concatenation of factory address and factoryData (or empty) - bytes callData; - bytes32 accountGasLimits; // concatenation of verificationGas (16 bytes) and callGas (16 bytes) - uint256 preVerificationGas; - bytes32 gasFees; // concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) - bytes paymasterAndData; // concatenation of paymaster fields (or empty) - bytes signature; -} - -interface IAggregator { - function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; - - function validateUserOpSignature( - PackedUserOperation calldata userOp - ) external view returns (bytes memory sigForUserOp); - - function aggregateSignatures( - PackedUserOperation[] calldata userOps - ) external view returns (bytes memory aggregatesSignature); -} - -interface IEntryPointNonces { - function getNonce(address sender, uint192 key) external view returns (uint256 nonce); -} - -interface IEntryPointStake { - function balanceOf(address account) external view returns (uint256); - - function depositTo(address account) external payable; - - function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; - - function addStake(uint32 unstakeDelaySec) external payable; - - function unlockStake() external; - - function withdrawStake(address payable withdrawAddress) external; -} - -interface IEntryPoint is IEntryPointNonces, IEntryPointStake { - error FailedOp(uint256 opIndex, string reason); - error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); - - struct UserOpsPerAggregator { - PackedUserOperation[] userOps; - IAggregator aggregator; - bytes signature; - } - - function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; - - function handleAggregatedOps( - UserOpsPerAggregator[] calldata opsPerAggregator, - address payable beneficiary - ) external; -} - -// TODO: EntryPointSimulation - -interface IAccount { - function validateUserOp( - PackedUserOperation calldata userOp, - bytes32 userOpHash, - uint256 missingAccountFunds - ) external returns (uint256 validationData); -} - -interface IAccountExecute { - function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; -} - -interface IPaymaster { - enum PostOpMode { - opSucceeded, - opReverted, - postOpReverted - } - - function validatePaymasterUserOp( - PackedUserOperation calldata userOp, - bytes32 userOpHash, - uint256 maxCost - ) external returns (bytes memory context, uint256 validationData); - - function postOp( - PostOpMode mode, - bytes calldata context, - uint256 actualGasCost, - uint256 actualUserOpFeePerGas - ) external; -} diff --git a/contracts/interfaces/IERC7579Account.sol b/contracts/interfaces/IERC7579Account.sol deleted file mode 100644 index 0be805f5b1f..00000000000 --- a/contracts/interfaces/IERC7579Account.sol +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -// import { CallType, ExecType, ModeCode } from "../lib/ModeLib.sol"; -import {IERC165} from "./IERC165.sol"; -import {IERC1271} from "./IERC1271.sol"; - -struct Execution { - address target; - uint256 value; - bytes callData; -} - -interface IERC7579Execution { - /** - * @dev Executes a transaction on behalf of the account. - * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details - * @param executionCalldata The encoded execution call data - * - * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 - * If a mode is requested that is not supported by the Account, it MUST revert - */ - function execute(bytes32 mode, bytes calldata executionCalldata) external; - - /** - * @dev Executes a transaction on behalf of the account. - * This function is intended to be called by Executor Modules - * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details - * @param executionCalldata The encoded execution call data - * - * MUST ensure adequate authorization control: i.e. onlyExecutorModule - * If a mode is requested that is not supported by the Account, it MUST revert - */ - function executeFromExecutor( - bytes32 mode, - bytes calldata executionCalldata - ) external returns (bytes[] memory returnData); -} - -interface IERC7579AccountConfig { - /** - * @dev Returns the account id of the smart account - * @return accountImplementationId the account id of the smart account - * - * MUST return a non-empty string - * The accountId SHOULD be structured like so: - * "vendorname.accountname.semver" - * The id SHOULD be unique across all smart accounts - */ - function accountId() external view returns (string memory accountImplementationId); - - /** - * @dev Function to check if the account supports a certain execution mode (see above) - * @param encodedMode the encoded mode - * - * MUST return true if the account supports the mode and false otherwise - */ - function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); - - /** - * @dev Function to check if the account supports a certain module typeId - * @param moduleTypeId the module type ID according to the ERC-7579 spec - * - * MUST return true if the account supports the module type and false otherwise - */ - function supportsModule(uint256 moduleTypeId) external view returns (bool); -} - -interface IERC7579ModuleConfig { - event ModuleInstalled(uint256 moduleTypeId, address module); - event ModuleUninstalled(uint256 moduleTypeId, address module); - - /** - * @dev Installs a Module of a certain type on the smart account - * @param moduleTypeId the module type ID according to the ERC-7579 spec - * @param module the module address - * @param initData arbitrary data that may be required on the module during `onInstall` - * initialization. - * - * MUST implement authorization control - * MUST call `onInstall` on the module with the `initData` parameter if provided - * MUST emit ModuleInstalled event - * MUST revert if the module is already installed or the initialization on the module failed - */ - function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; - - /** - * @dev Uninstalls a Module of a certain type on the smart account - * @param moduleTypeId the module type ID according the ERC-7579 spec - * @param module the module address - * @param deInitData arbitrary data that may be required on the module during `onInstall` - * initialization. - * - * MUST implement authorization control - * MUST call `onUninstall` on the module with the `deInitData` parameter if provided - * MUST emit ModuleUninstalled event - * MUST revert if the module is not installed or the deInitialization on the module failed - */ - function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; - - /** - * @dev Returns whether a module is installed on the smart account - * @param moduleTypeId the module type ID according the ERC-7579 spec - * @param module the module address - * @param additionalContext arbitrary data that may be required to determine if the module is installed - * - * MUST return true if the module is installed and false otherwise - */ - function isModuleInstalled( - uint256 moduleTypeId, - address module, - bytes calldata additionalContext - ) external view returns (bool); -} diff --git a/contracts/interfaces/IERC7579Module.sol b/contracts/interfaces/IERC7579Module.sol deleted file mode 100644 index 1aee412c053..00000000000 --- a/contracts/interfaces/IERC7579Module.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {PackedUserOperation} from "./IERC4337.sol"; - -uint256 constant VALIDATION_SUCCESS = 0; -uint256 constant VALIDATION_FAILED = 1; -uint256 constant MODULE_TYPE_SIGNER = 0; -uint256 constant MODULE_TYPE_VALIDATOR = 1; -uint256 constant MODULE_TYPE_EXECUTOR = 2; -uint256 constant MODULE_TYPE_FALLBACK = 3; -uint256 constant MODULE_TYPE_HOOK = 4; - -interface IERC7579Module { - /** - * @dev This function is called by the smart account during installation of the module - * @param data arbitrary data that may be required on the module during `onInstall` initialization - * - * MUST revert on error (e.g. if module is already enabled) - */ - function onInstall(bytes calldata data) external; - - /** - * @dev This function is called by the smart account during uninstallation of the module - * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization - * - * MUST revert on error - */ - function onUninstall(bytes calldata data) external; - - /** - * @dev Returns boolean value if module is a certain type - * @param moduleTypeId the module type ID according the ERC-7579 spec - * - * MUST return true if the module is of the given type and false otherwise - */ - function isModuleType(uint256 moduleTypeId) external view returns (bool); -} - -interface IERC7579Validator is IERC7579Module { - /** - * @dev Validates a UserOperation - * @param userOp the ERC-4337 PackedUserOperation - * @param userOpHash the hash of the ERC-4337 PackedUserOperation - * - * MUST validate that the signature is a valid signature of the userOpHash - * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch - */ - function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); - - /** - * @dev Validates a signature using ERC-1271 - * @param sender the address that sent the ERC-1271 request to the smart account - * @param hash the hash of the ERC-1271 request - * @param signature the signature of the ERC-1271 request - * - * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid - * MUST NOT modify state - */ - function isValidSignatureWithSender( - address sender, - bytes32 hash, - bytes calldata signature - ) external view returns (bytes4); -} - -interface IERC7579Hook is IERC7579Module { - /** - * @dev Called by the smart account before execution - * @param msgSender the address that called the smart account - * @param value the value that was sent to the smart account - * @param msgData the data that was sent to the smart account - * - * MAY return arbitrary data in the `hookData` return value - */ - function preCheck( - address msgSender, - uint256 value, - bytes calldata msgData - ) external returns (bytes memory hookData); - - /** - * @dev Called by the smart account after execution - * @param hookData the data that was returned by the `preCheck` function - * - * MAY validate the `hookData` to validate transaction context of the `preCheck` function - */ - function postCheck(bytes calldata hookData) external; -} From 77f98212769fe84a3293e44f40f3ad34b26761d2 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 16:01:58 +0200 Subject: [PATCH 23/41] refactor mock --- contracts/mocks/account/utils/ERC7579UtilsMock.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/mocks/account/utils/ERC7579UtilsMock.sol b/contracts/mocks/account/utils/ERC7579UtilsMock.sol index b64a1d53fa6..e0a1e1a50ea 100644 --- a/contracts/mocks/account/utils/ERC7579UtilsMock.sol +++ b/contracts/mocks/account/utils/ERC7579UtilsMock.sol @@ -2,22 +2,22 @@ pragma solidity ^0.8.20; -import {_eqCallTypeGlobal, _eqExecTypeGlobal, _eqModeSelectorGlobal, _eqModePayloadGlobal, CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; +import {CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; contract ERC7579UtilsGlobalMock { function eqCallTypeGlobal(CallType callType1, CallType callType2) internal pure returns (bool) { - return _eqCallTypeGlobal(callType1, callType2); + return callType1 == callType2; } function eqExecTypeGlobal(ExecType execType1, ExecType execType2) internal pure returns (bool) { - return _eqExecTypeGlobal(execType1, execType2); + return execType1 == execType2; } function eqModeSelectorGlobal(ModeSelector modeSelector1, ModeSelector modeSelector2) internal pure returns (bool) { - return _eqModeSelectorGlobal(modeSelector1, modeSelector2); + return modeSelector1 == modeSelector2; } function eqModePayloadGlobal(ModePayload modePayload1, ModePayload modePayload2) internal pure returns (bool) { - return _eqModePayloadGlobal(modePayload1, modePayload2); + return modePayload1 == modePayload2; } } From a183548b068bbfcd8f616525f7deb3ca0b5f23bf Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 20:37:57 +0200 Subject: [PATCH 24/41] don't include vendored erc4337-entrypoint in the nopm package. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a82ea47dd08..9075a50ad22 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "private": true, "files": [ "/contracts/**/*.sol", - "!/contracts/mocks/**/*" + "!/contracts/mocks/**/*", + "!/contracts/vendor/erc4337-entrypoint" ], "scripts": { "compile": "hardhat compile", From 176f151b209c1f97a7ae2f3578f1d184eaf3037c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 20:39:05 +0200 Subject: [PATCH 25/41] change imports to aliased in vendor/erc4337-entrypoint for upgradeable testing --- .../erc4337-entrypoint/core/EntryPoint.sol | 32 +++++++++---------- .../erc4337-entrypoint/core/NonceManager.sol | 2 +- .../erc4337-entrypoint/core/StakeManager.sol | 2 +- .../core/UserOperationLib.sol | 2 +- .../interfaces/IAccount.sol | 2 +- .../interfaces/IAccountExecute.sol | 2 +- .../interfaces/IAggregator.sol | 2 +- .../interfaces/IEntryPoint.sol | 8 ++--- .../interfaces/IPaymaster.sol | 2 +- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 778115b1637..bdca0e5d410 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -3,22 +3,22 @@ pragma solidity ^0.8.23; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-inline-assembly */ -import "../interfaces/IAccount.sol"; -import "../interfaces/IAccountExecute.sol"; -import "../interfaces/IPaymaster.sol"; -import "../interfaces/IEntryPoint.sol"; - -import "../utils/Exec.sol"; -import "./StakeManager.sol"; -import "./SenderCreator.sol"; -import "./Helpers.sol"; -import "./NonceManager.sol"; -import "./UserOperationLib.sol"; - -// import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "../../../utils/introspection/ERC165.sol"; // OZ edit -import "../../../utils/ReentrancyGuard.sol"; // OZ edit +import {IAccount} from "../interfaces/IAccount.sol"; // OZ edit +import {IAccountExecute} from "../interfaces/IAccountExecute.sol"; // OZ edit +import {IPaymaster} from "../interfaces/IPaymaster.sol"; // OZ edit +import {IEntryPoint} from "../interfaces/IEntryPoint.sol"; // OZ edit + +import {Exec} from "../utils/Exec.sol"; // OZ edit +import {IStakeManager, StakeManager} from "./StakeManager.sol"; // OZ edit +import {SenderCreator} from "./SenderCreator.sol"; // OZ edit +import {ValidationData, _parseValidationData, min} from "./Helpers.sol"; // OZ edit +import {INonceManager, NonceManager} from "./NonceManager.sol"; // OZ edit +import {UserOperationLib, PackedUserOperation} from "./UserOperationLib.sol"; // OZ edit + +import {IAggregator} from "../interfaces/IAggregator.sol"; // OZ edit + +import {ERC165} from "../../../utils/introspection/ERC165.sol"; // OZ edit +import {ReentrancyGuard} from "../../../utils/ReentrancyGuard.sol"; // OZ edit /* * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. diff --git a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol index 7bef62e99dd..3bd86f21694 100644 --- a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; -import "../interfaces/INonceManager.sol"; +import {INonceManager} from "../interfaces/INonceManager.sol"; // OZ edit /** * nonce management functionality diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index f90210b7e38..98914a39f52 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.23; -import "../interfaces/IStakeManager.sol"; +import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit /* solhint-disable avoid-low-level-calls */ /* solhint-disable not-rely-on-time */ diff --git a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol index dcf5740cc28..7cbb55a2729 100644 --- a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol +++ b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; /* solhint-disable no-inline-assembly */ -import "../interfaces/PackedUserOperation.sol"; +import {PackedUserOperation} from "../interfaces/PackedUserOperation.sol"; // OZ edit import {calldataKeccak, min} from "./Helpers.sol"; /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol index e3b355fbc27..03c11aaa2ae 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit interface IAccount { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol index 4433c80ce3e..aed4a4f0e4c 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit interface IAccountExecute { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol index 070d8f27a2c..e315abd6b9d 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit /** * Aggregated Signatures validator. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol index 28c26f98e6c..5a78a428143 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -9,10 +9,10 @@ pragma solidity >=0.7.5; /* solhint-disable no-inline-assembly */ /* solhint-disable reason-string */ -import "./PackedUserOperation.sol"; -import "./IStakeManager.sol"; -import "./IAggregator.sol"; -import "./INonceManager.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import {IStakeManager} from "./IStakeManager.sol"; // OZ Edit +import {IAggregator} from "./IAggregator.sol"; // OZ Edit +import {INonceManager} from "./INonceManager.sol"; // OZ Edit interface IEntryPoint is IStakeManager, INonceManager { /*** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol index 9176a0b242a..d39a60068fb 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import "./PackedUserOperation.sol"; +import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit /** * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. From 21e950b1ff47a8c394a7dd5d7fdbc2d42e975ac5 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 20:46:31 +0200 Subject: [PATCH 26/41] fix --- contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index bdca0e5d410..82f6c3877e7 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -17,7 +17,7 @@ import {UserOperationLib, PackedUserOperation} from "./UserOperationLib.sol"; // import {IAggregator} from "../interfaces/IAggregator.sol"; // OZ edit -import {ERC165} from "../../../utils/introspection/ERC165.sol"; // OZ edit +import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; // OZ edit import {ReentrancyGuard} from "../../../utils/ReentrancyGuard.sol"; // OZ edit /* From 6a8cfde6075421f899700034f7fc1c14e0275e30 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 20:50:26 +0200 Subject: [PATCH 27/41] fix --- contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 82f6c3877e7..2195671b623 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -32,10 +32,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, SenderCreator private immutable _senderCreator = new SenderCreator(); - function senderCreator() internal view virtual returns (SenderCreator) { - return _senderCreator; - } - //compensate for innerHandleOps' emit message and deposit refund. // allow some slack for future gas price changes. uint256 private constant INNER_GAS_OVERHEAD = 10000; @@ -47,6 +43,11 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 private constant REVERT_REASON_MAX_LEN = 2048; uint256 private constant PENALTY_PERCENT = 10; + // OZ edit: reorder + function senderCreator() internal view virtual returns (SenderCreator) { + return _senderCreator; + } + /// @inheritdoc IERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything From 71f8ec8790e7303fa736dc50c5cbcca2924ba83b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 9 Oct 2024 20:53:22 +0200 Subject: [PATCH 28/41] codespell --- contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol index 5a78a428143..7af7d8e5159 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -118,7 +118,7 @@ interface IEntryPoint is IStakeManager, INonceManager { * A custom revert error of handleOps, to report a revert by account or paymaster. * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). * @param reason - Revert reason. see FailedOp(uint256,string), above - * @param inner - data from inner cought revert reason + * @param inner - data from inner caught revert reason * @dev note that inner is truncated to 2048 bytes */ error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); From 7ac541a860ab072e916969851725bc9a3eba5100 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 09:44:09 +0200 Subject: [PATCH 29/41] exlude vendor contract from namespace transformation --- scripts/upgradeable/transpile.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh index f7c848c1320..ae081640482 100644 --- a/scripts/upgradeable/transpile.sh +++ b/scripts/upgradeable/transpile.sh @@ -44,6 +44,7 @@ npx @openzeppelin/upgrade-safe-transpiler -D \ -p 'contracts/metatx/ERC2771Forwarder.sol' \ -n \ -N 'contracts/mocks/**/*' \ + -N 'contracts/vendor/**/*' \ -q '@openzeppelin/' # delete compilation artifacts of vanilla code From 8341a4f825151890db8d7f42cce8c2ad0d8ee6a0 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 09:48:41 +0200 Subject: [PATCH 30/41] test --- .../erc4337-entrypoint/core/EntryPoint.sol | 41 +++++++++---------- .../erc4337-entrypoint/core/NonceManager.sol | 2 +- .../erc4337-entrypoint/core/StakeManager.sol | 2 +- .../core/UserOperationLib.sol | 2 +- .../interfaces/IAccount.sol | 2 +- .../interfaces/IAccountExecute.sol | 2 +- .../interfaces/IAggregator.sol | 2 +- .../interfaces/IEntryPoint.sol | 10 ++--- .../interfaces/IPaymaster.sol | 2 +- scripts/upgradeable/transpile.sh | 5 ++- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol index 2195671b623..778115b1637 100644 --- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol @@ -3,22 +3,22 @@ pragma solidity ^0.8.23; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-inline-assembly */ -import {IAccount} from "../interfaces/IAccount.sol"; // OZ edit -import {IAccountExecute} from "../interfaces/IAccountExecute.sol"; // OZ edit -import {IPaymaster} from "../interfaces/IPaymaster.sol"; // OZ edit -import {IEntryPoint} from "../interfaces/IEntryPoint.sol"; // OZ edit - -import {Exec} from "../utils/Exec.sol"; // OZ edit -import {IStakeManager, StakeManager} from "./StakeManager.sol"; // OZ edit -import {SenderCreator} from "./SenderCreator.sol"; // OZ edit -import {ValidationData, _parseValidationData, min} from "./Helpers.sol"; // OZ edit -import {INonceManager, NonceManager} from "./NonceManager.sol"; // OZ edit -import {UserOperationLib, PackedUserOperation} from "./UserOperationLib.sol"; // OZ edit - -import {IAggregator} from "../interfaces/IAggregator.sol"; // OZ edit - -import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; // OZ edit -import {ReentrancyGuard} from "../../../utils/ReentrancyGuard.sol"; // OZ edit +import "../interfaces/IAccount.sol"; +import "../interfaces/IAccountExecute.sol"; +import "../interfaces/IPaymaster.sol"; +import "../interfaces/IEntryPoint.sol"; + +import "../utils/Exec.sol"; +import "./StakeManager.sol"; +import "./SenderCreator.sol"; +import "./Helpers.sol"; +import "./NonceManager.sol"; +import "./UserOperationLib.sol"; + +// import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "../../../utils/introspection/ERC165.sol"; // OZ edit +import "../../../utils/ReentrancyGuard.sol"; // OZ edit /* * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. @@ -32,6 +32,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, SenderCreator private immutable _senderCreator = new SenderCreator(); + function senderCreator() internal view virtual returns (SenderCreator) { + return _senderCreator; + } + //compensate for innerHandleOps' emit message and deposit refund. // allow some slack for future gas price changes. uint256 private constant INNER_GAS_OVERHEAD = 10000; @@ -43,11 +47,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, uint256 private constant REVERT_REASON_MAX_LEN = 2048; uint256 private constant PENALTY_PERCENT = 10; - // OZ edit: reorder - function senderCreator() internal view virtual returns (SenderCreator) { - return _senderCreator; - } - /// @inheritdoc IERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything diff --git a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol index 3bd86f21694..7bef62e99dd 100644 --- a/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/NonceManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; -import {INonceManager} from "../interfaces/INonceManager.sol"; // OZ edit +import "../interfaces/INonceManager.sol"; /** * nonce management functionality diff --git a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol index 98914a39f52..f90210b7e38 100644 --- a/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol +++ b/contracts/vendor/erc4337-entrypoint/core/StakeManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.23; -import {IStakeManager} from "../interfaces/IStakeManager.sol"; // OZ edit +import "../interfaces/IStakeManager.sol"; /* solhint-disable avoid-low-level-calls */ /* solhint-disable not-rely-on-time */ diff --git a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol index 7cbb55a2729..dcf5740cc28 100644 --- a/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol +++ b/contracts/vendor/erc4337-entrypoint/core/UserOperationLib.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; /* solhint-disable no-inline-assembly */ -import {PackedUserOperation} from "../interfaces/PackedUserOperation.sol"; // OZ edit +import "../interfaces/PackedUserOperation.sol"; import {calldataKeccak, min} from "./Helpers.sol"; /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol index 03c11aaa2ae..e3b355fbc27 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccount.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; interface IAccount { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol index aed4a4f0e4c..4433c80ce3e 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAccountExecute.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; interface IAccountExecute { /** diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol index e315abd6b9d..070d8f27a2c 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IAggregator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; /** * Aggregated Signatures validator. diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol index 7af7d8e5159..28c26f98e6c 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IEntryPoint.sol @@ -9,10 +9,10 @@ pragma solidity >=0.7.5; /* solhint-disable no-inline-assembly */ /* solhint-disable reason-string */ -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit -import {IStakeManager} from "./IStakeManager.sol"; // OZ Edit -import {IAggregator} from "./IAggregator.sol"; // OZ Edit -import {INonceManager} from "./INonceManager.sol"; // OZ Edit +import "./PackedUserOperation.sol"; +import "./IStakeManager.sol"; +import "./IAggregator.sol"; +import "./INonceManager.sol"; interface IEntryPoint is IStakeManager, INonceManager { /*** @@ -118,7 +118,7 @@ interface IEntryPoint is IStakeManager, INonceManager { * A custom revert error of handleOps, to report a revert by account or paymaster. * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). * @param reason - Revert reason. see FailedOp(uint256,string), above - * @param inner - data from inner caught revert reason + * @param inner - data from inner cought revert reason * @dev note that inner is truncated to 2048 bytes */ error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); diff --git a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol index d39a60068fb..9176a0b242a 100644 --- a/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol +++ b/contracts/vendor/erc4337-entrypoint/interfaces/IPaymaster.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.5; -import {PackedUserOperation} from "./PackedUserOperation.sol"; // OZ Edit +import "./PackedUserOperation.sol"; /** * The interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh index ae081640482..b25284a1cac 100644 --- a/scripts/upgradeable/transpile.sh +++ b/scripts/upgradeable/transpile.sh @@ -23,7 +23,7 @@ fi # -D: delete original and excluded files # -b: use this build info file # -i: use included Initializable -# -x: exclude proxy-related contracts with a few exceptions +# -x: exclude vendored and proxy-related contracts with a few exceptions # -p: emit public initializer # -n: use namespaces # -N: exclude from namespaces transformation @@ -31,6 +31,8 @@ fi npx @openzeppelin/upgrade-safe-transpiler -D \ -b "$build_info" \ -i contracts/proxy/utils/Initializable.sol \ + -x 'contracts/vendor/**/*' \ + -x '!contracts/vendor/compound/ICompoundTimelock.sol' \ -x 'contracts-exposed/**/*' \ -x 'contracts/proxy/**/*' \ -x '!contracts/proxy/Clones.sol' \ @@ -44,7 +46,6 @@ npx @openzeppelin/upgrade-safe-transpiler -D \ -p 'contracts/metatx/ERC2771Forwarder.sol' \ -n \ -N 'contracts/mocks/**/*' \ - -N 'contracts/vendor/**/*' \ -q '@openzeppelin/' # delete compilation artifacts of vanilla code From 8bac09cf509409d1b90c4cd8e3a24f846877af40 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 11:19:49 +0200 Subject: [PATCH 31/41] Fix transpilation and testing of upgradeable contracts --- .github/workflows/checks.yml | 2 +- contracts/mocks/Stateless.sol | 2 ++ scripts/upgradeable/patch-apply.sh | 2 +- scripts/upgradeable/patch-save.sh | 2 +- scripts/upgradeable/transpile.sh | 10 ++++++++-- scripts/upgradeable/upgradeable.excluded.patch | 17 +++++++++++++++++ 6 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 scripts/upgradeable/upgradeable.excluded.patch diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2f1d5aa687e..128b31346d0 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -130,4 +130,4 @@ jobs: with: check_hidden: true check_filenames: true - skip: package-lock.json,*.pdf + skip: package-lock.json,*.pdf,contracts/vendor/** diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 846c77d98e8..97924bc7d50 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -22,6 +22,8 @@ import {ERC165} from "../utils/introspection/ERC165.sol"; import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol"; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol"; import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; diff --git a/scripts/upgradeable/patch-apply.sh b/scripts/upgradeable/patch-apply.sh index d9e17589b05..e16cf76a4c2 100755 --- a/scripts/upgradeable/patch-apply.sh +++ b/scripts/upgradeable/patch-apply.sh @@ -3,7 +3,7 @@ set -euo pipefail DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" -PATCH="$DIRNAME/upgradeable.patch" +PATCH="$DIRNAME/${1:-upgradeable.patch}" error() { echo Error: "$*" >&2 diff --git a/scripts/upgradeable/patch-save.sh b/scripts/upgradeable/patch-save.sh index 111e6f1572a..de0cec6a5ef 100755 --- a/scripts/upgradeable/patch-save.sh +++ b/scripts/upgradeable/patch-save.sh @@ -3,7 +3,7 @@ set -euo pipefail DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" -PATCH="$DIRNAME/upgradeable.patch" +PATCH="$DIRNAME/${1:-upgradeable.patch}" error() { echo Error: "$*" >&2 diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh index b25284a1cac..d31172ddf4e 100644 --- a/scripts/upgradeable/transpile.sh +++ b/scripts/upgradeable/transpile.sh @@ -5,13 +5,16 @@ set -euo pipefail -x VERSION="$(jq -r .version contracts/package.json)" DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" +# Apply patch to contracts that are transpiled bash "$DIRNAME/patch-apply.sh" sed -i'' -e "s//$VERSION/g" "contracts/package.json" git add contracts/package.json +# Build artifacts npm run clean npm run compile +# Check artifacts are correctly built build_info=($(jq -r '.input.sources | keys | if any(test("^contracts/mocks/.*\\bunreachable\\b")) then empty else input_filename end' artifacts/build-info/*)) build_info_num=${#build_info[@]} @@ -20,6 +23,9 @@ if [ $build_info_num -ne 1 ]; then exit 1 fi +# Apply changes to the excluded contracts (these don't need to in the artifact and may prevent compilation) +git apply -3 "$DIRNAME/upgradeable.excluded.patch" + # -D: delete original and excluded files # -b: use this build info file # -i: use included Initializable @@ -31,8 +37,6 @@ fi npx @openzeppelin/upgrade-safe-transpiler -D \ -b "$build_info" \ -i contracts/proxy/utils/Initializable.sol \ - -x 'contracts/vendor/**/*' \ - -x '!contracts/vendor/compound/ICompoundTimelock.sol' \ -x 'contracts-exposed/**/*' \ -x 'contracts/proxy/**/*' \ -x '!contracts/proxy/Clones.sol' \ @@ -40,6 +44,8 @@ npx @openzeppelin/upgrade-safe-transpiler -D \ -x '!contracts/proxy/ERC1967/ERC1967Utils.sol' \ -x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ -x '!contracts/proxy/beacon/IBeacon.sol' \ + -x 'contracts/vendor/**/*' \ + -x '!contracts/vendor/compound/ICompoundTimelock.sol' \ -p 'contracts/access/manager/AccessManager.sol' \ -p 'contracts/finance/VestingWallet.sol' \ -p 'contracts/governance/TimelockController.sol' \ diff --git a/scripts/upgradeable/upgradeable.excluded.patch b/scripts/upgradeable/upgradeable.excluded.patch new file mode 100644 index 00000000000..7c49e21e123 --- /dev/null +++ b/scripts/upgradeable/upgradeable.excluded.patch @@ -0,0 +1,17 @@ +diff --git a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +index 778115b1..44501524 100644 +--- a/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol ++++ b/contracts/vendor/erc4337-entrypoint/core/EntryPoint.sol +@@ -15,10 +15,8 @@ import "./Helpers.sol"; + import "./NonceManager.sol"; + import "./UserOperationLib.sol"; + +-// import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +-// import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +-import "../../../utils/introspection/ERC165.sol"; // OZ edit +-import "../../../utils/ReentrancyGuard.sol"; // OZ edit ++import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; ++import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + + /* + * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. From c2e98394af6a254f4604fe7d6de36441d0ecbc26 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 12:49:55 +0200 Subject: [PATCH 32/41] update --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 128b31346d0..64229e697f8 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -130,4 +130,4 @@ jobs: with: check_hidden: true check_filenames: true - skip: package-lock.json,*.pdf,contracts/vendor/** + skip: package-lock.json,*.pdf,contracts/vendor/**/* From 09a569196be6b48db4406671b8292542751820de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 10 Oct 2024 09:38:01 -0600 Subject: [PATCH 33/41] Applying review --- .../account/utils/draft-ERC7579Utils.sol | 126 +++++------------- 1 file changed, 35 insertions(+), 91 deletions(-) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index 48b065cb705..8c5abd852cf 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -17,77 +17,47 @@ type ModePayload is bytes22; * * See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579]. */ +// slither-disable-next-line unused-state library ERC7579Utils { using Packing for *; - /** - * @dev A single `call` execution. - */ - // slither-disable-next-line unused-state + /// @dev A single `call` execution. CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); - /** - * @dev A batch of `call` executions. - */ - // slither-disable-next-line unused-state + /// @dev A batch of `call` executions. CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); - /** - * @dev A `delegatecall` execution. - */ - // slither-disable-next-line unused-state + /// @dev A `delegatecall` execution. CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); - /** - * @dev Default execution type that reverts on failure. - */ - // slither-disable-next-line unused-state + /// @dev Default execution type that reverts on failure. ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); - /** - * @dev Execution type that does not revert on failure. - */ - // slither-disable-next-line unused-state + /// @dev Execution type that does not revert on failure. ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); - /** - * @dev Emits when an {EXECTYPE_TRY} execution fails. - */ + /// @dev Emits when an {EXECTYPE_TRY} execution fails. event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); - /** - * @dev The provided {CallType} is not supported. - */ + /// @dev The provided {CallType} is not supported. error ERC7579UnsupportedCallType(CallType callType); - /** - * @dev The provided {ExecType} is not supported. - */ + /// @dev The provided {ExecType} is not supported. error ERC7579UnsupportedExecType(ExecType execType); - /** - * @dev The provided module doesn't match the provided module type. - */ + /// @dev The provided module doesn't match the provided module type. error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module); - /** - * @dev The module is not installed. - */ + /// @dev The module is not installed. error ERC7579UninstalledModule(uint256 moduleTypeId, address module); - /** - * @dev The module is already installed. - */ + /// @dev The module is already installed. error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module); - /** - * @dev The module type is not supported. - */ + /// @dev The module type is not supported. error ERC7579UnsupportedModuleType(uint256 moduleTypeId); - /** - * @dev Executes a single call. - */ + /// @dev Executes a single call. function execSingle( ExecType execType, bytes calldata executionCalldata @@ -97,9 +67,7 @@ library ERC7579Utils { returnData[0] = _call(0, execType, target, value, callData); } - /** - * @dev Executes a batch of calls. - */ + /// @dev Executes a batch of calls. function execBatch( ExecType execType, bytes calldata executionCalldata @@ -117,9 +85,7 @@ library ERC7579Utils { } } - /** - * @dev Executes a delegate call. - */ + /// @dev Executes a delegate call. function execDelegateCall( ExecType execType, bytes calldata executionCalldata @@ -131,9 +97,7 @@ library ERC7579Utils { _validateExecutionMode(0, execType, success, returndata); } - /** - * @dev Encodes the mode with the provided parameters. See {decodeMode}. - */ + /// @dev Encodes the mode with the provided parameters. See {decodeMode}. function encodeMode( CallType callType, ExecType execType, @@ -151,9 +115,7 @@ library ERC7579Utils { ); } - /** - * @dev Decodes the mode into its parameters. See {encodeMode}. - */ + /// @dev Decodes the mode into its parameters. See {encodeMode}. function decodeMode( Mode mode ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) { @@ -165,9 +127,7 @@ library ERC7579Utils { ); } - /** - * @dev Encodes a single call execution. See {decodeSingle}. - */ + /// @dev Encodes a single call execution. See {decodeSingle}. function encodeSingle( address target, uint256 value, @@ -176,22 +136,16 @@ library ERC7579Utils { return abi.encodePacked(target, value, callData); } - /** - * @dev Decodes a single call execution. See {encodeSingle}. - */ + /// @dev Decodes a single call execution. See {encodeSingle}. function decodeSingle( bytes calldata executionCalldata ) internal pure returns (address target, uint256 value, bytes calldata callData) { - assembly ("memory-safe") { - target := shr(96, calldataload(executionCalldata.offset)) - value := calldataload(add(executionCalldata.offset, 20)) - } + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); callData = executionCalldata[52:]; } - /** - * @dev Encodes a delegate call execution. See {decodeDelegate}. - */ + /// @dev Encodes a delegate call execution. See {decodeDelegate}. function encodeDelegate( address target, bytes calldata callData @@ -199,28 +153,20 @@ library ERC7579Utils { return abi.encodePacked(target, callData); } - /** - * @dev Decodes a delegate call execution. See {encodeDelegate}. - */ + /// @dev Decodes a delegate call execution. See {encodeDelegate}. function decodeDelegate( bytes calldata executionCalldata ) internal pure returns (address target, bytes calldata callData) { - assembly ("memory-safe") { - target := shr(96, calldataload(executionCalldata.offset)) - } + target = address(bytes20(executionCalldata[0:20])); callData = executionCalldata[20:]; } - /** - * @dev Encodes a batch of executions. See {decodeBatch}. - */ + /// @dev Encodes a batch of executions. See {decodeBatch}. function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) { return abi.encode(executionBatch); } - /** - * @dev Decodes a batch of executions. See {encodeBatch}. - */ + /// @dev Decodes a batch of executions. See {encodeBatch}. function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { assembly ("memory-safe") { let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) @@ -230,9 +176,7 @@ library ERC7579Utils { } } - /** - * @dev Executes a `call` to the target with the provided {ExecType}. - */ + /// @dev Executes a `call` to the target with the provided {ExecType}. function _call( uint256 index, ExecType execType, @@ -244,21 +188,21 @@ library ERC7579Utils { return _validateExecutionMode(index, execType, success, returndata); } - /** - * @dev Validates the execution mode and returns the returndata. - */ + /// @dev Validates the execution mode and returns the returndata. function _validateExecutionMode( uint256 index, ExecType execType, bool success, bytes memory returndata ) private returns (bytes memory) { - if (execType == ERC7579Utils.EXECTYPE_DEFAULT) return Address.verifyCallResult(success, returndata); - if (execType == ERC7579Utils.EXECTYPE_TRY) { + if (execType == ERC7579Utils.EXECTYPE_DEFAULT) { + Address.verifyCallResult(success, returndata); + } else if (execType == ERC7579Utils.EXECTYPE_TRY) { if (!success) emit ERC7579TryExecuteFail(index, returndata); - return returndata; + } else { + revert ERC7579UnsupportedExecType(execType); } - revert ERC7579UnsupportedExecType(execType); + return returndata; } } From 6216b5c0941dbf01c02642f4d3c8c479b5b95d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 10 Oct 2024 09:43:49 -0600 Subject: [PATCH 34/41] Update contracts/interfaces/draft-IERC4337.sol Co-authored-by: Hadrien Croubois --- contracts/interfaces/draft-IERC4337.sol | 36 ++++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol index fcc8d95a693..2cc09bcb926 100644 --- a/contracts/interfaces/draft-IERC4337.sol +++ b/contracts/interfaces/draft-IERC4337.sol @@ -3,17 +3,33 @@ pragma solidity ^0.8.20; /** - * @dev An user operation that can be executed by an account on its packed version. + * @dev A https://github.com/ethereum/ercs/blob/master/ERCS/erc-4337.md#useroperation[user operation] is composed of the following elements: + * - `sender` (`address`): The account making the operation + * - `nonce` (`uint256`): Anti-replay parameter (see “Semi-abstracted Nonce Support” ) + * - `factory` (`address`): account factory, only for new accounts + * - `factoryData` (`bytes`): data for account factory (only if account factory exists) + * - `callData` (`bytes`): The data to pass to the sender during the main execution call + * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call + * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step + * - `preVerificationGas` (`uint256`): Extra gas to pay the bunder + * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) + * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) + * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself) + * - `paymasterVerificationGasLimit` (`uint256`): The amount of gas to allocate for the paymaster validation code + * - `paymasterPostOpGasLimit` (`uint256`): The amount of gas to allocate for the paymaster post-operation code + * - `paymasterData` (`bytes`): Data for paymaster (only if paymaster exists) + * - `signature` (`bytes`): Data passed into the account to verify authorization * - * - `sender`: The account making the operation (might not be deployed yet). - * - `nonce`: Anti-replay parameter handled by the entrypoint. - * - `initCode`: Concatenation of the address of the factory and the factory calldata. - * - `callData`: The `data` the sender will be called with. - * - `accountGasLimits`: Concatenation of the verification step gas limit and the main call gas limit. - * - `preVerificationGas`: Extra gas to pay the bundler before the verification step. - * - `gasFees`: Maximum fee and priority fee the sender is willing to pay. - * - `paymasterAndData`: Address of paymaster contract and calldata, (or empty, if account pays for itself). - * - `signature`: Data passed into the account to verify authorization. + * When passed to on-chain contacts, the following packed version is used. + * - `sender` (`address`) + * - `nonce` (`uint256`) + * - `initCode` (`bytes`): concatenation of factory address and factoryData (or empty) + * - `callData` (`bytes`) + * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes) + * - `preVerificationGas` (`uint256`) + * - `gasFees` (`bytes32`): concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) + * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty) + * - `signature` (`bytes`) */ struct PackedUserOperation { address sender; From afbb6e48190ddd6c2f804586ca833664279206c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 10 Oct 2024 09:44:05 -0600 Subject: [PATCH 35/41] Remove IEntryPointSimulation --- contracts/interfaces/draft-IERC4337.sol | 69 ------------------------- 1 file changed, 69 deletions(-) diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol index 2cc09bcb926..9b1af56b6e5 100644 --- a/contracts/interfaces/draft-IERC4337.sol +++ b/contracts/interfaces/draft-IERC4337.sol @@ -155,75 +155,6 @@ interface IEntryPoint is IEntryPointNonces, IEntryPointStake { ) external; } -/** - * @dev Handle the simulation of user operations. - */ -interface IEntryPointSimulation { - /** - * @dev Result of executing a user operation. - */ - struct ExecutionResult { - uint256 preOpGas; - uint256 paid; - uint256 accountValidationData; - uint256 paymasterValidationData; - bool targetSuccess; - bytes targetResult; - } - - /** - * @dev Result of validating a user operation. - */ - struct ReturnInfo { - uint256 preOpGas; - uint256 prefund; - uint256 accountValidationData; - uint256 paymasterValidationData; - bytes paymasterContext; - } - - /** - * @dev Information about the stake of an account. - */ - struct StakeInfo { - uint256 stake; - uint256 unstakeDelaySec; - } - - /** - * @dev Information about the stake of an aggregator. - */ - struct AggregatorStakeInfo { - address aggregator; - StakeInfo stakeInfo; - } - - /** - * @dev Result of simulating the validation of a user operation. - */ - struct ValidationResult { - ReturnInfo returnInfo; - StakeInfo senderInfo; - StakeInfo factoryInfo; - StakeInfo paymasterInfo; - AggregatorStakeInfo aggregatorInfo; - } - - /** - * @dev Simulates the validation of a user operation (i.e. {IAccount-validateUserOp} and {IPaymaster-validatePaymasterUserOp}). - */ - function simulateValidation(PackedUserOperation calldata userOp) external returns (ValidationResult memory); - - /** - * @dev Simulates the full execution of a user operation. - */ - function simulateHandleOp( - PackedUserOperation calldata op, - address target, - bytes calldata targetCallData - ) external returns (ExecutionResult memory); -} - /** * @dev Base interface for an account. */ From 5bb9757bca2ca47c069ad25a20e4ed5bb376f168 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 21:29:50 +0200 Subject: [PATCH 36/41] /// comments --- .../account/utils/draft-ERC4337Utils.sol | 67 +++++-------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index ee2925f9023..ddb02b45000 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -14,21 +14,13 @@ import {Packing} from "../../utils/Packing.sol"; library ERC4337Utils { using Packing for *; - /** - * @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) - * return this value on success. - */ + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. uint256 internal constant SIG_VALIDATION_SUCCESS = 0; - /** - * @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) - * must return this value in case of signature failure, instead of revert. - */ + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert. uint256 internal constant SIG_VALIDATION_FAILED = 1; - /** - * @dev Parses the validation data into its components. See {packValidationData}. - */ + /// @dev Parses the validation data into its components. See {packValidationData}. function parseValidationData( uint256 validationData ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { @@ -38,9 +30,7 @@ library ERC4337Utils { if (validUntil == 0) validUntil = type(uint48).max; } - /** - * @dev Packs the validation data into a single uint256. See {parseValidationData}. - */ + /// @dev Packs the validation data into a single uint256. See {parseValidationData}. function packValidationData( address aggregator, uint48 validAfter, @@ -49,9 +39,7 @@ library ERC4337Utils { return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); } - /** - * @dev Same as {packValidationData}, but with a boolean signature success flag. - */ + /// @dev Same as {packValidationData}, but with a boolean signature success flag. function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { return uint256( @@ -77,9 +65,7 @@ library ERC4337Utils { return packValidationData(success, validAfter, validUntil); } - /** - * @dev Returns the aggregator of the `validationData` and whether it is out of time range. - */ + /// @dev Returns the aggregator of the `validationData` and whether it is out of time range. function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { if (validationData == 0) { return (address(0), false); @@ -89,16 +75,12 @@ library ERC4337Utils { } } - /** - * @dev Computes the hash of a user operation with the current entrypoint and chainid. - */ + /// @dev Computes the hash of a user operation with the current entrypoint and chainid. function hash(PackedUserOperation calldata self) internal view returns (bytes32) { return hash(self, address(this), block.chainid); } - /** - * @dev Sames as {hash}, but with a custom entrypoint and chainid. - */ + /// @dev Sames as {hash}, but with a custom entrypoint and chainid. function hash( PackedUserOperation calldata self, address entrypoint, @@ -125,38 +107,27 @@ library ERC4337Utils { return result; } - /** - * @dev Returns `verificationGasLimit` from the {PackedUserOperation}. - */ + /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(self.accountGasLimits.extract_32_16(0x00)); } - /** - * @dev Returns `accountGasLimits` from the {PackedUserOperation}. - */ + /// @dev Returns `accountGasLimits` from the {PackedUserOperation}. function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(self.accountGasLimits.extract_32_16(0x10)); } - /** - * @dev Returns the first section of `gasFees` from the {PackedUserOperation}. - */ + /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}. function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(self.gasFees.extract_32_16(0x00)); } - /** - * @dev Returns the second section of `gasFees` from the {PackedUserOperation}. - */ + /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}. function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(self.gasFees.extract_32_16(0x10)); } - /** - * @dev Returns the total gas price for the {PackedUserOperation} - * (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). - */ + /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { unchecked { // Following values are "per gas" @@ -166,23 +137,17 @@ library ERC4337Utils { } } - /** - * @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. - */ + /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. function paymaster(PackedUserOperation calldata self) internal pure returns (address) { return address(bytes20(self.paymasterAndData[0:20])); } - /** - * @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. - */ + /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(bytes16(self.paymasterAndData[20:36])); } - /** - * @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. - */ + /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(bytes16(self.paymasterAndData[36:52])); } From cd3e38880c1406518a995a20418449f53fa9915d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 21:35:02 +0200 Subject: [PATCH 37/41] minor refactor --- contracts/account/utils/draft-ERC4337Utils.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index ddb02b45000..a66dd0f9c4b 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -42,10 +42,10 @@ library ERC4337Utils { /// @dev Same as {packValidationData}, but with a boolean signature success flag. function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { return - uint256( - bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20( - bytes20(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))) - ) + packValidationData( + address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))), + validAfter, + validUntil ); } From bdf55bb80e48d6019a4b673c9bc0b19e97c6b746 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 21:40:02 +0200 Subject: [PATCH 38/41] update --- contracts/account/utils/draft-ERC7579Utils.sol | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index 8c5abd852cf..21d36e620db 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -92,9 +92,7 @@ library ERC7579Utils { ) internal returns (bytes[] memory returnData) { (address target, bytes calldata callData) = decodeDelegate(executionCalldata); returnData = new bytes[](1); - (bool success, bytes memory returndata) = target.delegatecall(callData); - returnData[0] = returndata; - _validateExecutionMode(0, execType, success, returndata); + returnData[0] = _delegatecall(0, execType, target, callData); } /// @dev Encodes the mode with the provided parameters. See {decodeMode}. @@ -188,6 +186,17 @@ library ERC7579Utils { return _validateExecutionMode(index, execType, success, returndata); } + /// @dev Executes a `delegatecall` to the target with the provided {ExecType}. + function _delegatecall( + uint256 index, + ExecType execType, + address target, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return _validateExecutionMode(index, execType, success, returndata); + } + /// @dev Validates the execution mode and returns the returndata. function _validateExecutionMode( uint256 index, From b72e3b1e1668973b17f91086ddd759d42f4f635c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 10 Oct 2024 13:41:36 -0600 Subject: [PATCH 39/41] Update .changeset/small-seahorses-bathe.md Co-authored-by: Hadrien Croubois --- .changeset/small-seahorses-bathe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/small-seahorses-bathe.md b/.changeset/small-seahorses-bathe.md index d88c1807d57..7b5ec794f38 100644 --- a/.changeset/small-seahorses-bathe.md +++ b/.changeset/small-seahorses-bathe.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -`ERC7579Utils`: Add a reusable library to interact with ERC7579 modular accounts +`ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts From 0a2fb4833f9f24b3f1019d5685a97d18ac29e3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 10 Oct 2024 13:41:42 -0600 Subject: [PATCH 40/41] Update .codecov.yml Co-authored-by: Hadrien Croubois --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index cb718b2fc21..4cec4ef7d5f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,4 +13,4 @@ coverage: ignore: - "test" - "contracts/mocks" - - "vendor" + - "contracts/vendor" From 207379973db23e14ddfd96ee030fca617bb81897 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 10 Oct 2024 21:50:54 +0200 Subject: [PATCH 41/41] rename global comparators --- contracts/account/utils/draft-ERC7579Utils.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index 21d36e620db..de6bb6509ec 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -216,27 +216,27 @@ library ERC7579Utils { } // Operators -using {_eqCallTypeGlobal as ==} for CallType global; -using {_eqExecTypeGlobal as ==} for ExecType global; -using {_eqModeSelectorGlobal as ==} for ModeSelector global; -using {_eqModePayloadGlobal as ==} for ModePayload global; +using {eqCallType as ==} for CallType global; +using {eqExecType as ==} for ExecType global; +using {eqModeSelector as ==} for ModeSelector global; +using {eqModePayload as ==} for ModePayload global; /// @dev Compares two `CallType` values for equality. -function _eqCallTypeGlobal(CallType a, CallType b) pure returns (bool) { +function eqCallType(CallType a, CallType b) pure returns (bool) { return CallType.unwrap(a) == CallType.unwrap(b); } /// @dev Compares two `ExecType` values for equality. -function _eqExecTypeGlobal(ExecType a, ExecType b) pure returns (bool) { +function eqExecType(ExecType a, ExecType b) pure returns (bool) { return ExecType.unwrap(a) == ExecType.unwrap(b); } /// @dev Compares two `ModeSelector` values for equality. -function _eqModeSelectorGlobal(ModeSelector a, ModeSelector b) pure returns (bool) { +function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); } /// @dev Compares two `ModePayload` values for equality. -function _eqModePayloadGlobal(ModePayload a, ModePayload b) pure returns (bool) { +function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) { return ModePayload.unwrap(a) == ModePayload.unwrap(b); }