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..7b5ec794f38 --- /dev/null +++ b/.changeset/small-seahorses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC7579Utils`: Add a reusable library to interact with ERC-7579 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 diff --git a/.codecov.yml b/.codecov.yml index 5bee9146ab9..4cec4ef7d5f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,3 +13,4 @@ coverage: ignore: - "test" - "contracts/mocks" + - "contracts/vendor" diff --git a/contracts/abstraction/utils/ERC7579Utils.sol b/contracts/abstraction/utils/ERC7579Utils.sol deleted file mode 100644 index 4a014d3e3e5..00000000000 --- a/contracts/abstraction/utils/ERC7579Utils.sol +++ /dev/null @@ -1,116 +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; - -// slither-disable-next-line unused-state -library ERC7579Utils { - using Packing for *; - - CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00); - CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01); - CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); - ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); - ExecType internal 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); -} 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}} diff --git a/contracts/abstraction/utils/ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol similarity index 61% rename from contracts/abstraction/utils/ERC4337Utils.sol rename to contracts/account/utils/draft-ERC4337Utils.sol index f3f049d14cc..a66dd0f9c4b 100644 --- a/contracts/abstraction/utils/ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -2,26 +2,25 @@ pragma solidity ^0.8.20; -import {IEntryPoint, PackedUserOperation} from "../../interfaces/IERC4337.sol"; +import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; import {Math} from "../../utils/math/Math.sol"; -// import {Memory} from "../../utils/Memory.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 *; - /* - * 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; - /* - * 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; - // Validation data + /// @dev Parses the validation data into its components. See {packValidationData}. function parseValidationData( uint256 validationData ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { @@ -31,6 +30,7 @@ library ERC4337Utils { if (validUntil == 0) validUntil = type(uint48).max; } + /// @dev Packs the validation data into a single uint256. See {parseValidationData}. function packValidationData( address aggregator, uint48 validAfter, @@ -39,15 +39,22 @@ 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. 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 ); } + /** + * @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); @@ -58,6 +65,7 @@ library ERC4337Utils { 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); @@ -67,17 +75,17 @@ library ERC4337Utils { } } - // Packed user operation + /// @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) { - // Memory.FreePtr ptr = Memory.save(); bytes32 result = keccak256( abi.encode( keccak256( @@ -96,26 +104,30 @@ library ERC4337Utils { chainid ) ); - // Memory.load(ptr); 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" @@ -125,74 +137,18 @@ library ERC4337Utils { } } + /// @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 { - 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/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol new file mode 100644 index 00000000000..de6bb6509ec --- /dev/null +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -0,0 +1,242 @@ +// 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]. + */ +// slither-disable-next-line unused-state +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); + returnData[0] = _delegatecall(0, execType, target, callData); + } + + /// @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 = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + 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 = address(bytes20(executionCalldata[0:20])); + 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 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, + ExecType execType, + bool success, + bytes memory returndata + ) private returns (bytes memory) { + if (execType == ERC7579Utils.EXECTYPE_DEFAULT) { + Address.verifyCallResult(success, returndata); + } else if (execType == ERC7579Utils.EXECTYPE_TRY) { + if (!success) emit ERC7579TryExecuteFail(index, returndata); + } else { + revert ERC7579UnsupportedExecType(execType); + } + return returndata; + } +} + +// 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; + +/// @dev Compares two `CallType` values for equality. +function eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +/// @dev Compares two `ExecType` values for equality. +function eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +/// @dev Compares two `ModeSelector` values for equality. +function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} + +/// @dev Compares two `ModePayload` values for equality. +function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) { + return ModePayload.unwrap(a) == ModePayload.unwrap(b); +} 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/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; -} diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol new file mode 100644 index 00000000000..9b1af56b6e5 --- /dev/null +++ b/contracts/interfaces/draft-IERC4337.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @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 + * + * 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; + 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 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/IERC7579Account.sol b/contracts/interfaces/draft-IERC7579.sol similarity index 58% rename from contracts/interfaces/IERC7579Account.sol rename to contracts/interfaces/draft-IERC7579.sol index 0be805f5b1f..47f1627f682 100644 --- a/contracts/interfaces/IERC7579Account.sol +++ b/contracts/interfaces/draft-IERC7579.sol @@ -1,9 +1,91 @@ // 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"; +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; diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index e0193a91442..97924bc7d50 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -22,8 +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 "../abstraction/utils/ERC4337Utils.sol"; -import {ERC7579Utils} from "../abstraction/utils/ERC7579Utils.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/contracts/mocks/account/utils/ERC7579UtilsMock.sol b/contracts/mocks/account/utils/ERC7579UtilsMock.sol new file mode 100644 index 00000000000..e0a1e1a50ea --- /dev/null +++ b/contracts/mocks/account/utils/ERC7579UtilsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; + +contract ERC7579UtilsGlobalMock { + function eqCallTypeGlobal(CallType callType1, CallType callType2) internal pure returns (bool) { + return callType1 == callType2; + } + + function eqExecTypeGlobal(ExecType execType1, ExecType execType2) internal pure returns (bool) { + return execType1 == execType2; + } + + function eqModeSelectorGlobal(ModeSelector modeSelector1, ModeSelector modeSelector2) internal pure returns (bool) { + return modeSelector1 == modeSelector2; + } + + function eqModePayloadGlobal(ModePayload modePayload1, ModePayload modePayload2) internal pure returns (bool) { + return modePayload1 == modePayload2; + } +} 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/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/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js new file mode 100644 index 00000000000..c76f89845ac --- /dev/null +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -0,0 +1,207 @@ +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]); + }); + + 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 () { + 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 new file mode 100644 index 00000000000..9d6154f78bd --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -0,0 +1,346 @@ +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'])], + ), + ); + }); + + 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 () { + 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); + }); + + 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 () { + 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('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 () { + 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, +}; diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index e8adda48920..40f052c80ff 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -9,287 +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(bytes2 left, bytes8 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 { + 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 { + 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 { + 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 { + 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, bytes6 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 { + 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, bytes4 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 { + 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(bytes6 left, bytes10 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 { + 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 { + 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 { + 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 { + 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(bytes10 left, bytes2 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 { + 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 { + 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 { + 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 { + 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 { + 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, bytes10 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 { + 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, bytes6 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 { + 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, bytes2 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 { + 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(bytes22 left, bytes2 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 { + 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 { + 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 { + 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); @@ -298,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); @@ -307,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); @@ -316,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); @@ -325,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); @@ -334,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); @@ -343,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); @@ -352,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); @@ -361,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); @@ -370,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); @@ -379,7 +379,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); } - function testReplace(bytes10 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); @@ -388,7 +388,7 @@ contract PackingTest is Test { assertEq(container, container.replace_10_1(newValue, offset).replace_10_1(oldValue, offset)); } - function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes2 oldValue = container.extract_10_2(offset); @@ -397,7 +397,7 @@ contract PackingTest is Test { assertEq(container, container.replace_10_2(newValue, offset).replace_10_2(oldValue, offset)); } - function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes4 oldValue = container.extract_10_4(offset); @@ -406,7 +406,7 @@ contract PackingTest is Test { assertEq(container, container.replace_10_4(newValue, offset).replace_10_4(oldValue, offset)); } - function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes6 oldValue = container.extract_10_6(offset); @@ -415,7 +415,7 @@ contract PackingTest is Test { assertEq(container, container.replace_10_6(newValue, offset).replace_10_6(oldValue, offset)); } - function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes8 oldValue = container.extract_10_8(offset); @@ -424,7 +424,7 @@ contract PackingTest is Test { assertEq(container, container.replace_10_8(newValue, offset).replace_10_8(oldValue, offset)); } - function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 11)); bytes1 oldValue = container.extract_12_1(offset); @@ -433,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); @@ -442,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); @@ -451,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); @@ -460,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); @@ -469,7 +469,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset)); } - function testReplace(bytes12 container, bytes10 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); @@ -478,7 +478,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_10(newValue, offset).replace_12_10(oldValue, offset)); } - function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 15)); bytes1 oldValue = container.extract_16_1(offset); @@ -487,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); @@ -496,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); @@ -505,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); @@ -514,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); @@ -523,7 +523,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes10 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); @@ -532,7 +532,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_10(newValue, offset).replace_16_10(oldValue, offset)); } - function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes12 oldValue = container.extract_16_12(offset); @@ -541,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); @@ -550,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); @@ -559,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); @@ -568,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); @@ -577,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); @@ -586,7 +586,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset)); } - function testReplace(bytes20 container, bytes10 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); @@ -595,7 +595,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_10(newValue, offset).replace_20_10(oldValue, offset)); } - function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes12 oldValue = container.extract_20_12(offset); @@ -604,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); @@ -613,7 +613,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } - function testReplace(bytes22 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); @@ -622,7 +622,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_1(newValue, offset).replace_22_1(oldValue, offset)); } - function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes2 oldValue = container.extract_22_2(offset); @@ -631,7 +631,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_2(newValue, offset).replace_22_2(oldValue, offset)); } - function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes4 oldValue = container.extract_22_4(offset); @@ -640,7 +640,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_4(newValue, offset).replace_22_4(oldValue, offset)); } - function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes6 oldValue = container.extract_22_6(offset); @@ -649,7 +649,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_6(newValue, offset).replace_22_6(oldValue, offset)); } - function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes8 oldValue = container.extract_22_8(offset); @@ -658,7 +658,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_8(newValue, offset).replace_22_8(oldValue, offset)); } - function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes10 oldValue = container.extract_22_10(offset); @@ -667,7 +667,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_10(newValue, offset).replace_22_10(oldValue, offset)); } - function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes12 oldValue = container.extract_22_12(offset); @@ -676,7 +676,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_12(newValue, offset).replace_22_12(oldValue, offset)); } - function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes16 oldValue = container.extract_22_16(offset); @@ -685,7 +685,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_16(newValue, offset).replace_22_16(oldValue, offset)); } - function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes20 oldValue = container.extract_22_20(offset); @@ -694,7 +694,7 @@ contract PackingTest is Test { assertEq(container, container.replace_22_20(newValue, offset).replace_22_20(oldValue, offset)); } - function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 23)); bytes1 oldValue = container.extract_24_1(offset); @@ -703,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); @@ -712,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); @@ -721,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); @@ -730,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); @@ -739,7 +739,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset)); } - function testReplace(bytes24 container, bytes10 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); @@ -748,7 +748,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_10(newValue, offset).replace_24_10(oldValue, offset)); } - function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes12 oldValue = container.extract_24_12(offset); @@ -757,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); @@ -766,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); @@ -775,7 +775,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } - function testReplace(bytes24 container, bytes22 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); @@ -784,7 +784,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_22(newValue, offset).replace_24_22(oldValue, offset)); } - function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 27)); bytes1 oldValue = container.extract_28_1(offset); @@ -793,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); @@ -802,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); @@ -811,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); @@ -820,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); @@ -829,7 +829,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset)); } - function testReplace(bytes28 container, bytes10 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); @@ -838,7 +838,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_10(newValue, offset).replace_28_10(oldValue, offset)); } - function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes12 oldValue = container.extract_28_12(offset); @@ -847,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); @@ -856,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); @@ -865,7 +865,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes22 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); @@ -874,7 +874,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_22(newValue, offset).replace_28_22(oldValue, offset)); } - function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes24 oldValue = container.extract_28_24(offset); @@ -883,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); @@ -892,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); @@ -901,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); @@ -910,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); @@ -919,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); @@ -928,7 +928,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset)); } - function testReplace(bytes32 container, bytes10 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); @@ -937,7 +937,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_10(newValue, offset).replace_32_10(oldValue, offset)); } - function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes12 oldValue = container.extract_32_12(offset); @@ -946,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); @@ -955,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); @@ -964,7 +964,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } - function testReplace(bytes32 container, bytes22 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); @@ -973,7 +973,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_22(newValue, offset).replace_32_22(oldValue, offset)); } - function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes24 oldValue = container.extract_32_24(offset); @@ -982,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);