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/.github/workflows/checks.yml b/.github/workflows/checks.yml index a4d08c1da51..18a38b3c5c0 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -133,4 +133,4 @@ jobs: with: check_hidden: true check_filenames: true - skip: package-lock.json,*.pdf + skip: package-lock.json,*.pdf,vendor 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/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol new file mode 100644 index 00000000000..bf559b86d6e --- /dev/null +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Packing} from "../../utils/Packing.sol"; + +/** + * @dev Library with common ERC-4337 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337]. + */ +library ERC4337Utils { + using Packing for *; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. + uint256 internal constant SIG_VALIDATION_SUCCESS = 0; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert. + uint256 internal constant SIG_VALIDATION_FAILED = 1; + + /// @dev Parses the validation data into its components. See {packValidationData}. + function parseValidationData( + uint256 validationData + ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { + validAfter = uint48(bytes32(validationData).extract_32_6(0x00)); + validUntil = uint48(bytes32(validationData).extract_32_6(0x06)); + aggregator = address(bytes32(validationData).extract_32_20(0x0c)); + if (validUntil == 0) validUntil = type(uint48).max; + } + + /// @dev Packs the validation data into a single uint256. See {parseValidationData}. + function packValidationData( + address aggregator, + uint48 validAfter, + uint48 validUntil + ) internal pure returns (uint256) { + return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); + } + + /// @dev Same as {packValidationData}, but with a boolean signature success flag. + function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { + return + 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); + + bool success = aggregator1 == address(0) && aggregator2 == address(0); + uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); + uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); + return packValidationData(success, validAfter, validUntil); + } + + /// @dev Returns the aggregator of the `validationData` and whether it is out of time range. + function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { + (address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData); + return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp); + } + + /// @dev Computes the hash of a user operation with the current entrypoint and chainid. + function hash(PackedUserOperation calldata self) internal view returns (bytes32) { + return hash(self, address(this), block.chainid); + } + + /// @dev Sames as {hash}, but with a custom entrypoint and chainid. + function hash( + PackedUserOperation calldata self, + address entrypoint, + uint256 chainid + ) internal pure returns (bytes32) { + bytes32 result = keccak256( + abi.encode( + keccak256( + abi.encode( + self.sender, + self.nonce, + keccak256(self.initCode), + keccak256(self.callData), + self.accountGasLimits, + self.preVerificationGas, + self.gasFees, + keccak256(self.paymasterAndData) + ) + ), + entrypoint, + chainid + ) + ); + return result; + } + + /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. + function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x00)); + } + + /// @dev Returns `accountGasLimits` from the {PackedUserOperation}. + function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x10)); + } + + /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}. + function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x00)); + } + + /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}. + function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x10)); + } + + /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { + unchecked { + // Following values are "per gas" + uint256 maxPriorityFee = maxPriorityFeePerGas(self); + uint256 maxFee = maxFeePerGas(self); + return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); + } + } + + /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. + function paymaster(PackedUserOperation calldata self) internal pure returns (address) { + return address(bytes20(self.paymasterAndData[0:20])); + } + + /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[20:36])); + } + + /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[36:52])); + } +} 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/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/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol new file mode 100644 index 00000000000..47f1627f682 --- /dev/null +++ b/contracts/interfaces/draft-IERC7579.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "./draft-IERC4337.sol"; + +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; +uint256 constant MODULE_TYPE_VALIDATOR = 1; +uint256 constant MODULE_TYPE_EXECUTOR = 2; +uint256 constant MODULE_TYPE_FALLBACK = 3; +uint256 constant MODULE_TYPE_HOOK = 4; + +interface IERC7579Module { + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` initialization + * + * MUST revert on error (e.g. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579Validator is IERC7579Module { + /** + * @dev Validates a UserOperation + * @param userOp the ERC-4337 PackedUserOperation + * @param userOpHash the hash of the ERC-4337 PackedUserOperation + * + * MUST validate that the signature is a valid signature of the userOpHash + * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /** + * @dev Validates a signature using ERC-1271 + * @param sender the address that sent the ERC-1271 request to the smart account + * @param hash the hash of the ERC-1271 request + * @param signature the signature of the ERC-1271 request + * + * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid + * MUST NOT modify state + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} + +interface IERC7579Hook is IERC7579Module { + /** + * @dev Called by the smart account before execution + * @param msgSender the address that called the smart account + * @param value the value that was sent to the smart account + * @param msgData the data that was sent to the smart account + * + * MAY return arbitrary data in the `hookData` return value + */ + function preCheck( + address msgSender, + uint256 value, + bytes calldata msgData + ) external returns (bytes memory hookData); + + /** + * @dev Called by the smart account after execution + * @param hookData the data that was returned by the `preCheck` function + * + * MAY validate the `hookData` to validate transaction context of the `preCheck` function + */ + function postCheck(bytes calldata hookData) external; +} + +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IERC7579Execution { + /** + * @dev Executes a transaction on behalf of the account. + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function execute(bytes32 mode, bytes calldata executionCalldata) external; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: i.e. onlyExecutorModule + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) external returns (bytes[] memory returnData); +} + +interface IERC7579AccountConfig { + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * + * MUST return a non-empty string + * The accountId SHOULD be structured like so: + * "vendorname.accountname.semver" + * The id SHOULD be unique across all smart accounts + */ + function accountId() external view returns (string memory accountImplementationId); + + /** + * @dev Function to check if the account supports a certain execution mode (see above) + * @param encodedMode the encoded mode + * + * MUST return true if the account supports the mode and false otherwise + */ + function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); + + /** + * @dev Function to check if the account supports a certain module typeId + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * + * MUST return true if the account supports the module type and false otherwise + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579ModuleConfig { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Installs a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onInstall` on the module with the `initData` parameter if provided + * MUST emit ModuleInstalled event + * MUST revert if the module is already installed or the initialization on the module failed + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; + + /** + * @dev Uninstalls a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onUninstall` on the module with the `deInitData` parameter if provided + * MUST emit ModuleUninstalled event + * MUST revert if the module is not installed or the deInitialization on the module failed + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; + + /** + * @dev Returns whether a module is installed on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param additionalContext arbitrary data that may be required to determine if the module is installed + * + * MUST return true if the module is installed and false otherwise + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) external view returns (bool); +} diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 3040ddcdf8e..0bc89e4ceb7 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -25,6 +25,8 @@ import {ERC165} from "../utils/introspection/ERC165.sol"; import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol"; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol"; import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; diff --git a/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/utils/Packing.sol b/contracts/utils/Packing.sol index 069153bef4a..f38e64a3bf6 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -68,6 +68,38 @@ library Packing { } } + function pack_2_8(bytes2 left, bytes8 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(192, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_10(bytes2 left, bytes10 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_20(bytes2 left, bytes20 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(96, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_22(bytes2 left, bytes22 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(16, right)) + } + } + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -84,6 +116,14 @@ library Packing { } } + function pack_4_6(bytes4 left, bytes6 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(32, right)) + } + } + function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -140,6 +180,14 @@ library Packing { } } + function pack_6_4(bytes6 left, bytes4 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(224, not(0))) + result := or(left, shr(48, right)) + } + } + function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(208, not(0))) @@ -148,6 +196,38 @@ library Packing { } } + function pack_6_10(bytes6 left, bytes10 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_16(bytes6 left, bytes16 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(128, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_22(bytes6 left, bytes22 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_8_2(bytes8 left, bytes2 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(64, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(192, not(0))) @@ -196,6 +276,46 @@ library Packing { } } + function pack_10_2(bytes10 left, bytes2 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_6(bytes10 left, bytes6 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_10(bytes10 left, bytes10 right) internal pure returns (bytes20 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_12(bytes10 left, bytes12 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(160, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_22(bytes10 left, bytes22 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(80, right)) + } + } + function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -212,6 +332,14 @@ library Packing { } } + function pack_12_10(bytes12 left, bytes10 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(96, right)) + } + } + function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -244,6 +372,14 @@ library Packing { } } + function pack_16_6(bytes16 left, bytes6 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(128, right)) + } + } + function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(128, not(0))) @@ -268,6 +404,14 @@ library Packing { } } + function pack_20_2(bytes20 left, bytes2 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(160, right)) + } + } + function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(96, not(0))) @@ -292,6 +436,30 @@ library Packing { } } + function pack_22_2(bytes22 left, bytes2 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_6(bytes22 left, bytes6 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_10(bytes22 left, bytes10 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(176, right)) + } + } + function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { left := and(left, shl(64, not(0))) @@ -466,6 +634,81 @@ library Packing { } } + function extract_10_1(bytes10 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 9) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_10_1(bytes10 self, bytes1 value, uint8 offset) internal pure returns (bytes10 result) { + bytes1 oldValue = extract_10_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_2(bytes10 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 8) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_10_2(bytes10 self, bytes2 value, uint8 offset) internal pure returns (bytes10 result) { + bytes2 oldValue = extract_10_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_4(bytes10 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_10_4(bytes10 self, bytes4 value, uint8 offset) internal pure returns (bytes10 result) { + bytes4 oldValue = extract_10_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_6(bytes10 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 4) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_10_6(bytes10 self, bytes6 value, uint8 offset) internal pure returns (bytes10 result) { + bytes6 oldValue = extract_10_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_8(bytes10 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_10_8(bytes10 self, bytes8 value, uint8 offset) internal pure returns (bytes10 result) { + bytes8 oldValue = extract_10_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_12_1(bytes12 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 11) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -541,6 +784,21 @@ library Packing { } } + function extract_12_10(bytes12 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_12_10(bytes12 self, bytes10 value, uint8 offset) internal pure returns (bytes12 result) { + bytes10 oldValue = extract_12_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_1(bytes16 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 15) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -616,6 +874,21 @@ library Packing { } } + function extract_16_10(bytes16 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_16_10(bytes16 self, bytes10 value, uint8 offset) internal pure returns (bytes16 result) { + bytes10 oldValue = extract_16_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_12(bytes16 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -706,6 +979,21 @@ library Packing { } } + function extract_20_10(bytes20 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_20_10(bytes20 self, bytes10 value, uint8 offset) internal pure returns (bytes20 result) { + bytes10 oldValue = extract_20_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_20_12(bytes20 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -736,6 +1024,141 @@ library Packing { } } + function extract_22_1(bytes22 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 21) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_22_1(bytes22 self, bytes1 value, uint8 offset) internal pure returns (bytes22 result) { + bytes1 oldValue = extract_22_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_2(bytes22 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 20) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_22_2(bytes22 self, bytes2 value, uint8 offset) internal pure returns (bytes22 result) { + bytes2 oldValue = extract_22_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_4(bytes22 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_22_4(bytes22 self, bytes4 value, uint8 offset) internal pure returns (bytes22 result) { + bytes4 oldValue = extract_22_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_6(bytes22 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 16) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_22_6(bytes22 self, bytes6 value, uint8 offset) internal pure returns (bytes22 result) { + bytes6 oldValue = extract_22_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_8(bytes22 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_22_8(bytes22 self, bytes8 value, uint8 offset) internal pure returns (bytes22 result) { + bytes8 oldValue = extract_22_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_10(bytes22 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 12) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_22_10(bytes22 self, bytes10 value, uint8 offset) internal pure returns (bytes22 result) { + bytes10 oldValue = extract_22_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_12(bytes22 self, uint8 offset) internal pure returns (bytes12 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(160, not(0))) + } + } + + function replace_22_12(bytes22 self, bytes12 value, uint8 offset) internal pure returns (bytes22 result) { + bytes12 oldValue = extract_22_12(self, offset); + assembly ("memory-safe") { + value := and(value, shl(160, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_16(bytes22 self, uint8 offset) internal pure returns (bytes16 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(128, not(0))) + } + } + + function replace_22_16(bytes22 self, bytes16 value, uint8 offset) internal pure returns (bytes22 result) { + bytes16 oldValue = extract_22_16(self, offset); + assembly ("memory-safe") { + value := and(value, shl(128, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_20(bytes22 self, uint8 offset) internal pure returns (bytes20 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(96, not(0))) + } + } + + function replace_22_20(bytes22 self, bytes20 value, uint8 offset) internal pure returns (bytes22 result) { + bytes20 oldValue = extract_22_20(self, offset); + assembly ("memory-safe") { + value := and(value, shl(96, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_1(bytes24 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 23) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -811,6 +1234,21 @@ library Packing { } } + function extract_24_10(bytes24 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_24_10(bytes24 self, bytes10 value, uint8 offset) internal pure returns (bytes24 result) { + bytes10 oldValue = extract_24_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_12(bytes24 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 12) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -856,6 +1294,21 @@ library Packing { } } + function extract_24_22(bytes24 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_24_22(bytes24 self, bytes22 value, uint8 offset) internal pure returns (bytes24 result) { + bytes22 oldValue = extract_24_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_1(bytes28 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 27) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -931,6 +1384,21 @@ library Packing { } } + function extract_28_10(bytes28 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_28_10(bytes28 self, bytes10 value, uint8 offset) internal pure returns (bytes28 result) { + bytes10 oldValue = extract_28_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_12(bytes28 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 16) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -976,6 +1444,21 @@ library Packing { } } + function extract_28_22(bytes28 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_28_22(bytes28 self, bytes22 value, uint8 offset) internal pure returns (bytes28 result) { + bytes22 oldValue = extract_28_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_24(bytes28 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1066,6 +1549,21 @@ library Packing { } } + function extract_32_10(bytes32 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 22) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_32_10(bytes32 self, bytes10 value, uint8 offset) internal pure returns (bytes32 result) { + bytes10 oldValue = extract_32_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_12(bytes32 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 20) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1111,6 +1609,21 @@ library Packing { } } + function extract_32_22(bytes32 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_32_22(bytes32 self, bytes22 value, uint8 offset) internal pure returns (bytes32 result) { + bytes22 oldValue = extract_32_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_24(bytes32 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { diff --git a/scripts/generate/templates/Packing.opts.js b/scripts/generate/templates/Packing.opts.js index de9ab77ff53..893ad6297cf 100644 --- a/scripts/generate/templates/Packing.opts.js +++ b/scripts/generate/templates/Packing.opts.js @@ -1,3 +1,3 @@ module.exports = { - SIZES: [1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], + SIZES: [1, 2, 4, 6, 8, 10, 12, 16, 20, 22, 24, 28, 32], }; diff --git a/scripts/generate/templates/Packing.t.js b/scripts/generate/templates/Packing.t.js index 56e9c0cc7c4..1feec28f5a5 100644 --- a/scripts/generate/templates/Packing.t.js +++ b/scripts/generate/templates/Packing.t.js @@ -11,14 +11,14 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; `; const testPack = (left, right) => `\ -function testPack(bytes${left} left, bytes${right} right) external { +function testPack(bytes${left} left, bytes${right} right) external pure { assertEq(left, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${left}(0)); assertEq(right, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${right}(${left})); } `; const testReplace = (outer, inner) => `\ -function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external { +function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, ${outer - inner})); bytes${inner} oldValue = container.extract_${outer}_${inner}(offset); diff --git a/slither.config.json b/slither.config.json index 069da1f3a21..fa52f4dd1dd 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +1,5 @@ { "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-state,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,cyclomatic-complexity,deprecated-standards,erc20-indexed,function-init-state,pragma,unused-state,reentrancy-unlimited-gas,constable-states,immutable-states,var-read-using-this", - "filter_paths": "contracts/mocks,contracts-exposed", + "filter_paths": "contracts/mocks,contracts/vendor,contracts-exposed", "compile_force_framework": "hardhat" } diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js new file mode 100644 index 00000000000..8374bc745c9 --- /dev/null +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -0,0 +1,211 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337'); +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; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + validUntil, + ]); + }); + + it('returns an type(uint48).max if until is 0', async function () { + const authorizer = this.authorizer; + const validAfter = 0x12345678n; + const validationData = packValidationData(validAfter, 0, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + MAX_UINT48, + ]); + }); + }); + + describe('packValidationData', function () { + it('packs the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect( + this.utils.$packValidationData(ethers.Typed.address(authorizer), validAfter, validUntil), + ).to.eventually.equal(validationData); + }); + + it('packs the validation data (bool)', async function () { + const success = false; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, false); + + expect(this.utils.$packValidationData(ethers.Typed.bool(success), validAfter, validUntil)).to.eventually.equal( + validationData, + ); + }); + }); + + describe('combineValidationData', function () { + const validUntil1 = 0x12345678n; + const validAfter1 = 0x9abcdef0n; + const validUntil2 = 0x87654321n; + const validAfter2 = 0xabcdef90n; + + it('combines the validation data', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, ethers.ZeroAddress); + const validationData2 = packValidationData(validAfter2, validUntil2, ethers.ZeroAddress); + const expected = packValidationData(validAfter2, validUntil1, true); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + + for (const [authorizer1, authorizer2] of [ + [ethers.ZeroAddress, '0xbf023313b891fd6000544b79e353323aa94a4f29'], + ['0xbf023313b891fd6000544b79e353323aa94a4f29', ethers.ZeroAddress], + ]) { + it('returns SIG_VALIDATION_FAILURE if one of the authorizers is not address(0)', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, authorizer1); + const validationData2 = packValidationData(validAfter2, validUntil2, authorizer2); + const expected = packValidationData(validAfter2, validUntil1, false); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + } + }); + + describe('getValidationData', function () { + it('returns the validation data with valid validity range', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, false]); + }); + + it('returns the validation data with invalid validity range (expired)', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = 1; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns the validation data with invalid validity range (not yet valid)', async function () { + const aggregator = this.authorizer; + const validAfter = MAX_UINT48; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns address(0) and false for validationData = 0', function () { + expect(this.utils.$getValidationData(0n)).to.eventually.deep.equal([ethers.ZeroAddress, false]); + }); + }); + + describe('hash', function () { + it('returns the user operation hash', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + + expect(this.utils.$hash(userOp.packed)).to.eventually.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, nonce: 1 }); + const chainId = 0xdeadbeef; + + expect(this.utils.$hash(userOp.packed, this.entrypoint, chainId)).to.eventually.equal( + userOp.hash(this.entrypoint, chainId), + ); + }); + }); + + describe('userOp values', function () { + it('returns verificationGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n }); + expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas); + }); + + it('returns callGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, callGas: 0x12345678n }); + expect(this.utils.$callGasLimit(userOp.packed)).to.eventually.equal(userOp.callGas); + }); + + it('returns maxPriorityFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxPriorityFee: 0x12345678n }); + expect(this.utils.$maxPriorityFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + it('returns maxFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxFeePerGas: 0x12345678n }); + expect(this.utils.$maxFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxFeePerGas); + }); + + it('returns gasPrice', async function () { + const userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + maxPriorityFee: 0x12345678n, + maxFeePerGas: 0x87654321n, + }); + expect(this.utils.$gasPrice(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + describe('paymasterAndData', function () { + beforeEach(async function () { + this.verificationGasLimit = 0x12345678n; + this.postOpGasLimit = 0x87654321n; + this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit); + this.userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + paymasterAndData: this.paymasterAndData, + }); + }); + + it('returns paymaster', async function () { + expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster); + }); + + it('returns verificationGasLimit', async function () { + expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal( + this.verificationGasLimit, + ); + }); + + it('returns postOpGasLimit', async function () { + expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.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..cc3b70425dd --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -0,0 +1,354 @@ +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(ethers.provider.getBalance(this.target)).to.eventually.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(ethers.provider.getBalance(this.target)).to.eventually.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(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.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(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.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(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.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(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash); + await this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data); + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.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); + + expect(this.utils.$encodeMode(callType, execType, selector, payload)).to.eventually.equal( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ); + }); + + it('decodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect( + this.utils.$decodeMode( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ), + ).to.eventually.deep.equal([callType, execType, selector, payload]); + }); + + it('encodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$encodeSingle(target, value, data)).to.eventually.equal(encodeSingle(target, value, data)); + }); + + it('decodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$decodeSingle(encodeSingle(target, value, data))).to.eventually.deep.equal([ + target.target, + value, + data, + ]); + }); + + it('encodes batch', async function () { + const entries = [ + [this.target, 0x123, '0x12345678'], + [this.anotherTarget, 0x456, '0x12345678'], + ]; + + expect(this.utils.$encodeBatch(entries)).to.eventually.equal(encodeBatch(...entries)); + }); + + it('decodes batch', async function () { + const entries = [ + [this.target.target, 0x123, '0x12345678'], + [this.anotherTarget.target, 0x456, '0x12345678'], + ]; + + expect(this.utils.$decodeBatch(encodeBatch(...entries))).to.eventually.deep.equal(entries); + }); + + it('encodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$encodeDelegate(target, data)).to.eventually.equal(encodeDelegate(target, data)); + }); + + it('decodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$decodeDelegate(encodeDelegate(target, data))).to.eventually.deep.equal([target.target, data]); + }); + + describe('global', function () { + describe('eqCallTypeGlobal', function () { + it('returns true if both call types are equal', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_BATCH, CALL_TYPE_BATCH)).to.eventually.be.true; + }); + + it('returns false if both call types are different', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_CALL, CALL_TYPE_BATCH)).to.eventually.be.false; + }); + }); + + describe('eqExecTypeGlobal', function () { + it('returns true if both exec types are equal', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_TRY, EXEC_TYPE_TRY)).to.eventually.be.true; + }); + + it('returns false if both exec types are different', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY)).to.eventually.be.false; + }); + }); + + describe('eqModeSelectorGlobal', function () { + it('returns true if both selectors are equal', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x12345678')).to.eventually.be.true; + }); + + it('returns false if both selectors are different', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x87654321')).to.eventually.be.false; + }); + }); + + describe('eqModePayloadGlobal', function () { + it('returns true if both payloads are equal', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(0, 22))).to.eventually.be + .true; + }); + + it('returns false if both payloads are different', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(1, 22))).to.eventually.be + .false; + }); + }); + }); +}); diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js new file mode 100644 index 00000000000..5901375b174 --- /dev/null +++ b/test/helpers/erc4337.js @@ -0,0 +1,95 @@ +const { ethers } = require('hardhat'); + +const SIG_VALIDATION_SUCCESS = '0x0000000000000000000000000000000000000000'; +const SIG_VALIDATION_FAILURE = '0x0000000000000000000000000000000000000001'; + +function getAddress(account) { + return account.target ?? account.address ?? account; +} + +function pack(left, right) { + return ethers.solidityPacked(['uint128', 'uint128'], [left, right]); +} + +function packValidationData(validAfter, validUntil, authorizer) { + return ethers.solidityPacked( + ['uint48', 'uint48', 'address'], + [ + validAfter, + validUntil, + typeof authorizer == 'boolean' + ? authorizer + ? SIG_VALIDATION_SUCCESS + : SIG_VALIDATION_FAILURE + : getAddress(authorizer), + ], + ); +} + +function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) { + return ethers.solidityPacked( + ['address', 'uint128', 'uint128'], + [getAddress(paymaster), verificationGasLimit, postOpGasLimit], + ); +} + +/// Represent one user operation +class UserOperation { + constructor(params) { + this.sender = getAddress(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, getAddress(entrypoint), chainId]), + ); + } +} + +module.exports = { + SIG_VALIDATION_SUCCESS, + SIG_VALIDATION_FAILURE, + 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 9531f1bffbb..40f052c80ff 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -9,182 +9,287 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; contract PackingTest is Test { using Packing for *; - function testPack(bytes1 left, bytes1 right) external { + function testPack(bytes1 left, bytes1 right) external pure { assertEq(left, Packing.pack_1_1(left, right).extract_2_1(0)); assertEq(right, Packing.pack_1_1(left, right).extract_2_1(1)); } - function testPack(bytes2 left, bytes2 right) external { + function testPack(bytes2 left, bytes2 right) external pure { assertEq(left, Packing.pack_2_2(left, right).extract_4_2(0)); assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2)); } - function testPack(bytes2 left, bytes4 right) external { + function testPack(bytes2 left, bytes4 right) external pure { assertEq(left, Packing.pack_2_4(left, right).extract_6_2(0)); assertEq(right, Packing.pack_2_4(left, right).extract_6_4(2)); } - function testPack(bytes2 left, bytes6 right) external { + function testPack(bytes2 left, bytes6 right) external pure { assertEq(left, Packing.pack_2_6(left, right).extract_8_2(0)); assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); } - function testPack(bytes4 left, bytes2 right) external { + function testPack(bytes2 left, bytes8 right) external pure { + assertEq(left, Packing.pack_2_8(left, right).extract_10_2(0)); + assertEq(right, Packing.pack_2_8(left, right).extract_10_8(2)); + } + + function testPack(bytes2 left, bytes10 right) external pure { + assertEq(left, Packing.pack_2_10(left, right).extract_12_2(0)); + assertEq(right, Packing.pack_2_10(left, right).extract_12_10(2)); + } + + function testPack(bytes2 left, bytes20 right) external pure { + assertEq(left, Packing.pack_2_20(left, right).extract_22_2(0)); + assertEq(right, Packing.pack_2_20(left, right).extract_22_20(2)); + } + + function testPack(bytes2 left, bytes22 right) external pure { + assertEq(left, Packing.pack_2_22(left, right).extract_24_2(0)); + assertEq(right, Packing.pack_2_22(left, right).extract_24_22(2)); + } + + function testPack(bytes4 left, bytes2 right) external pure { assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); } - function testPack(bytes4 left, bytes4 right) external { + function testPack(bytes4 left, bytes4 right) external pure { assertEq(left, Packing.pack_4_4(left, right).extract_8_4(0)); assertEq(right, Packing.pack_4_4(left, right).extract_8_4(4)); } - function testPack(bytes4 left, bytes8 right) external { + function testPack(bytes4 left, bytes6 right) external pure { + assertEq(left, Packing.pack_4_6(left, right).extract_10_4(0)); + assertEq(right, Packing.pack_4_6(left, right).extract_10_6(4)); + } + + function testPack(bytes4 left, bytes8 right) external pure { assertEq(left, Packing.pack_4_8(left, right).extract_12_4(0)); assertEq(right, Packing.pack_4_8(left, right).extract_12_8(4)); } - function testPack(bytes4 left, bytes12 right) external { + function testPack(bytes4 left, bytes12 right) external pure { assertEq(left, Packing.pack_4_12(left, right).extract_16_4(0)); assertEq(right, Packing.pack_4_12(left, right).extract_16_12(4)); } - function testPack(bytes4 left, bytes16 right) external { + function testPack(bytes4 left, bytes16 right) external pure { assertEq(left, Packing.pack_4_16(left, right).extract_20_4(0)); assertEq(right, Packing.pack_4_16(left, right).extract_20_16(4)); } - function testPack(bytes4 left, bytes20 right) external { + function testPack(bytes4 left, bytes20 right) external pure { assertEq(left, Packing.pack_4_20(left, right).extract_24_4(0)); assertEq(right, Packing.pack_4_20(left, right).extract_24_20(4)); } - function testPack(bytes4 left, bytes24 right) external { + function testPack(bytes4 left, bytes24 right) external pure { assertEq(left, Packing.pack_4_24(left, right).extract_28_4(0)); assertEq(right, Packing.pack_4_24(left, right).extract_28_24(4)); } - function testPack(bytes4 left, bytes28 right) external { + function testPack(bytes4 left, bytes28 right) external pure { assertEq(left, Packing.pack_4_28(left, right).extract_32_4(0)); assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4)); } - function testPack(bytes6 left, bytes2 right) external { + function testPack(bytes6 left, bytes2 right) external pure { assertEq(left, Packing.pack_6_2(left, right).extract_8_6(0)); assertEq(right, Packing.pack_6_2(left, right).extract_8_2(6)); } - function testPack(bytes6 left, bytes6 right) external { + function testPack(bytes6 left, bytes4 right) external pure { + assertEq(left, Packing.pack_6_4(left, right).extract_10_6(0)); + assertEq(right, Packing.pack_6_4(left, right).extract_10_4(6)); + } + + function testPack(bytes6 left, bytes6 right) external pure { assertEq(left, Packing.pack_6_6(left, right).extract_12_6(0)); assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); } - function testPack(bytes8 left, bytes4 right) external { + function testPack(bytes6 left, bytes10 right) external pure { + assertEq(left, Packing.pack_6_10(left, right).extract_16_6(0)); + assertEq(right, Packing.pack_6_10(left, right).extract_16_10(6)); + } + + function testPack(bytes6 left, bytes16 right) external pure { + assertEq(left, Packing.pack_6_16(left, right).extract_22_6(0)); + assertEq(right, Packing.pack_6_16(left, right).extract_22_16(6)); + } + + function testPack(bytes6 left, bytes22 right) external pure { + assertEq(left, Packing.pack_6_22(left, right).extract_28_6(0)); + assertEq(right, Packing.pack_6_22(left, right).extract_28_22(6)); + } + + function testPack(bytes8 left, bytes2 right) external pure { + assertEq(left, Packing.pack_8_2(left, right).extract_10_8(0)); + assertEq(right, Packing.pack_8_2(left, right).extract_10_2(8)); + } + + function testPack(bytes8 left, bytes4 right) external pure { assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0)); assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8)); } - function testPack(bytes8 left, bytes8 right) external { + function testPack(bytes8 left, bytes8 right) external pure { assertEq(left, Packing.pack_8_8(left, right).extract_16_8(0)); assertEq(right, Packing.pack_8_8(left, right).extract_16_8(8)); } - function testPack(bytes8 left, bytes12 right) external { + function testPack(bytes8 left, bytes12 right) external pure { assertEq(left, Packing.pack_8_12(left, right).extract_20_8(0)); assertEq(right, Packing.pack_8_12(left, right).extract_20_12(8)); } - function testPack(bytes8 left, bytes16 right) external { + function testPack(bytes8 left, bytes16 right) external pure { assertEq(left, Packing.pack_8_16(left, right).extract_24_8(0)); assertEq(right, Packing.pack_8_16(left, right).extract_24_16(8)); } - function testPack(bytes8 left, bytes20 right) external { + function testPack(bytes8 left, bytes20 right) external pure { assertEq(left, Packing.pack_8_20(left, right).extract_28_8(0)); assertEq(right, Packing.pack_8_20(left, right).extract_28_20(8)); } - function testPack(bytes8 left, bytes24 right) external { + function testPack(bytes8 left, bytes24 right) external pure { assertEq(left, Packing.pack_8_24(left, right).extract_32_8(0)); assertEq(right, Packing.pack_8_24(left, right).extract_32_24(8)); } - function testPack(bytes12 left, bytes4 right) external { + function testPack(bytes10 left, bytes2 right) external pure { + assertEq(left, Packing.pack_10_2(left, right).extract_12_10(0)); + assertEq(right, Packing.pack_10_2(left, right).extract_12_2(10)); + } + + function testPack(bytes10 left, bytes6 right) external pure { + assertEq(left, Packing.pack_10_6(left, right).extract_16_10(0)); + assertEq(right, Packing.pack_10_6(left, right).extract_16_6(10)); + } + + function testPack(bytes10 left, bytes10 right) external pure { + assertEq(left, Packing.pack_10_10(left, right).extract_20_10(0)); + assertEq(right, Packing.pack_10_10(left, right).extract_20_10(10)); + } + + function testPack(bytes10 left, bytes12 right) external pure { + assertEq(left, Packing.pack_10_12(left, right).extract_22_10(0)); + assertEq(right, Packing.pack_10_12(left, right).extract_22_12(10)); + } + + function testPack(bytes10 left, bytes22 right) external pure { + assertEq(left, Packing.pack_10_22(left, right).extract_32_10(0)); + assertEq(right, Packing.pack_10_22(left, right).extract_32_22(10)); + } + + function testPack(bytes12 left, bytes4 right) external pure { assertEq(left, Packing.pack_12_4(left, right).extract_16_12(0)); assertEq(right, Packing.pack_12_4(left, right).extract_16_4(12)); } - function testPack(bytes12 left, bytes8 right) external { + function testPack(bytes12 left, bytes8 right) external pure { assertEq(left, Packing.pack_12_8(left, right).extract_20_12(0)); assertEq(right, Packing.pack_12_8(left, right).extract_20_8(12)); } - function testPack(bytes12 left, bytes12 right) external { + function testPack(bytes12 left, bytes10 right) external pure { + assertEq(left, Packing.pack_12_10(left, right).extract_22_12(0)); + assertEq(right, Packing.pack_12_10(left, right).extract_22_10(12)); + } + + function testPack(bytes12 left, bytes12 right) external pure { assertEq(left, Packing.pack_12_12(left, right).extract_24_12(0)); assertEq(right, Packing.pack_12_12(left, right).extract_24_12(12)); } - function testPack(bytes12 left, bytes16 right) external { + function testPack(bytes12 left, bytes16 right) external pure { assertEq(left, Packing.pack_12_16(left, right).extract_28_12(0)); assertEq(right, Packing.pack_12_16(left, right).extract_28_16(12)); } - function testPack(bytes12 left, bytes20 right) external { + function testPack(bytes12 left, bytes20 right) external pure { assertEq(left, Packing.pack_12_20(left, right).extract_32_12(0)); assertEq(right, Packing.pack_12_20(left, right).extract_32_20(12)); } - function testPack(bytes16 left, bytes4 right) external { + function testPack(bytes16 left, bytes4 right) external pure { assertEq(left, Packing.pack_16_4(left, right).extract_20_16(0)); assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16)); } - function testPack(bytes16 left, bytes8 right) external { + function testPack(bytes16 left, bytes6 right) external pure { + assertEq(left, Packing.pack_16_6(left, right).extract_22_16(0)); + assertEq(right, Packing.pack_16_6(left, right).extract_22_6(16)); + } + + function testPack(bytes16 left, bytes8 right) external pure { assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0)); assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16)); } - function testPack(bytes16 left, bytes12 right) external { + function testPack(bytes16 left, bytes12 right) external pure { assertEq(left, Packing.pack_16_12(left, right).extract_28_16(0)); assertEq(right, Packing.pack_16_12(left, right).extract_28_12(16)); } - function testPack(bytes16 left, bytes16 right) external { + function testPack(bytes16 left, bytes16 right) external pure { assertEq(left, Packing.pack_16_16(left, right).extract_32_16(0)); assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16)); } - function testPack(bytes20 left, bytes4 right) external { + function testPack(bytes20 left, bytes2 right) external pure { + assertEq(left, Packing.pack_20_2(left, right).extract_22_20(0)); + assertEq(right, Packing.pack_20_2(left, right).extract_22_2(20)); + } + + function testPack(bytes20 left, bytes4 right) external pure { assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0)); assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20)); } - function testPack(bytes20 left, bytes8 right) external { + function testPack(bytes20 left, bytes8 right) external pure { assertEq(left, Packing.pack_20_8(left, right).extract_28_20(0)); assertEq(right, Packing.pack_20_8(left, right).extract_28_8(20)); } - function testPack(bytes20 left, bytes12 right) external { + function testPack(bytes20 left, bytes12 right) external pure { assertEq(left, Packing.pack_20_12(left, right).extract_32_20(0)); assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20)); } - function testPack(bytes24 left, bytes4 right) external { + function testPack(bytes22 left, bytes2 right) external pure { + assertEq(left, Packing.pack_22_2(left, right).extract_24_22(0)); + assertEq(right, Packing.pack_22_2(left, right).extract_24_2(22)); + } + + function testPack(bytes22 left, bytes6 right) external pure { + assertEq(left, Packing.pack_22_6(left, right).extract_28_22(0)); + assertEq(right, Packing.pack_22_6(left, right).extract_28_6(22)); + } + + function testPack(bytes22 left, bytes10 right) external pure { + assertEq(left, Packing.pack_22_10(left, right).extract_32_22(0)); + assertEq(right, Packing.pack_22_10(left, right).extract_32_10(22)); + } + + function testPack(bytes24 left, bytes4 right) external pure { assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0)); assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24)); } - function testPack(bytes24 left, bytes8 right) external { + function testPack(bytes24 left, bytes8 right) external pure { assertEq(left, Packing.pack_24_8(left, right).extract_32_24(0)); assertEq(right, Packing.pack_24_8(left, right).extract_32_8(24)); } - function testPack(bytes28 left, bytes4 right) external { + function testPack(bytes28 left, bytes4 right) external pure { assertEq(left, Packing.pack_28_4(left, right).extract_32_28(0)); assertEq(right, Packing.pack_28_4(left, right).extract_32_4(28)); } - function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 1)); bytes1 oldValue = container.extract_2_1(offset); @@ -193,7 +298,7 @@ contract PackingTest is Test { assertEq(container, container.replace_2_1(newValue, offset).replace_2_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 3)); bytes1 oldValue = container.extract_4_1(offset); @@ -202,7 +307,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_1(newValue, offset).replace_4_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes2 oldValue = container.extract_4_2(offset); @@ -211,7 +316,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 5)); bytes1 oldValue = container.extract_6_1(offset); @@ -220,7 +325,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_1(newValue, offset).replace_6_1(oldValue, offset)); } - function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes2 oldValue = container.extract_6_2(offset); @@ -229,7 +334,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_2(newValue, offset).replace_6_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes4 oldValue = container.extract_6_4(offset); @@ -238,7 +343,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_4(newValue, offset).replace_6_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 7)); bytes1 oldValue = container.extract_8_1(offset); @@ -247,7 +352,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_1(newValue, offset).replace_8_1(oldValue, offset)); } - function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes2 oldValue = container.extract_8_2(offset); @@ -256,7 +361,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_2(newValue, offset).replace_8_2(oldValue, offset)); } - function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes4 oldValue = container.extract_8_4(offset); @@ -265,7 +370,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes6 oldValue = container.extract_8_6(offset); @@ -274,7 +379,52 @@ contract PackingTest is Test { assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 9)); + + bytes1 oldValue = container.extract_10_1(offset); + + assertEq(newValue, container.replace_10_1(newValue, offset).extract_10_1(offset)); + assertEq(container, container.replace_10_1(newValue, offset).replace_10_1(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 8)); + + bytes2 oldValue = container.extract_10_2(offset); + + assertEq(newValue, container.replace_10_2(newValue, offset).extract_10_2(offset)); + assertEq(container, container.replace_10_2(newValue, offset).replace_10_2(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes4 oldValue = container.extract_10_4(offset); + + assertEq(newValue, container.replace_10_4(newValue, offset).extract_10_4(offset)); + assertEq(container, container.replace_10_4(newValue, offset).replace_10_4(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 4)); + + bytes6 oldValue = container.extract_10_6(offset); + + assertEq(newValue, container.replace_10_6(newValue, offset).extract_10_6(offset)); + assertEq(container, container.replace_10_6(newValue, offset).replace_10_6(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes8 oldValue = container.extract_10_8(offset); + + assertEq(newValue, container.replace_10_8(newValue, offset).extract_10_8(offset)); + assertEq(container, container.replace_10_8(newValue, offset).replace_10_8(oldValue, offset)); + } + + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 11)); bytes1 oldValue = container.extract_12_1(offset); @@ -283,7 +433,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_1(newValue, offset).replace_12_1(oldValue, offset)); } - function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes2 oldValue = container.extract_12_2(offset); @@ -292,7 +442,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_2(newValue, offset).replace_12_2(oldValue, offset)); } - function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes4 oldValue = container.extract_12_4(offset); @@ -301,7 +451,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset)); } - function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes6 oldValue = container.extract_12_6(offset); @@ -310,7 +460,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_6(newValue, offset).replace_12_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes8 oldValue = container.extract_12_8(offset); @@ -319,7 +469,16 @@ contract PackingTest is Test { assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes10 oldValue = container.extract_12_10(offset); + + assertEq(newValue, container.replace_12_10(newValue, offset).extract_12_10(offset)); + assertEq(container, container.replace_12_10(newValue, offset).replace_12_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 15)); bytes1 oldValue = container.extract_16_1(offset); @@ -328,7 +487,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_1(newValue, offset).replace_16_1(oldValue, offset)); } - function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes2 oldValue = container.extract_16_2(offset); @@ -337,7 +496,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_2(newValue, offset).replace_16_2(oldValue, offset)); } - function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes4 oldValue = container.extract_16_4(offset); @@ -346,7 +505,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset)); } - function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes6 oldValue = container.extract_16_6(offset); @@ -355,7 +514,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_6(newValue, offset).replace_16_6(oldValue, offset)); } - function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes8 oldValue = container.extract_16_8(offset); @@ -364,7 +523,16 @@ contract PackingTest is Test { assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes10 oldValue = container.extract_16_10(offset); + + assertEq(newValue, container.replace_16_10(newValue, offset).extract_16_10(offset)); + assertEq(container, container.replace_16_10(newValue, offset).replace_16_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes12 oldValue = container.extract_16_12(offset); @@ -373,7 +541,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_12(newValue, offset).replace_16_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 19)); bytes1 oldValue = container.extract_20_1(offset); @@ -382,7 +550,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_1(newValue, offset).replace_20_1(oldValue, offset)); } - function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes2 oldValue = container.extract_20_2(offset); @@ -391,7 +559,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_2(newValue, offset).replace_20_2(oldValue, offset)); } - function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes4 oldValue = container.extract_20_4(offset); @@ -400,7 +568,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset)); } - function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes6 oldValue = container.extract_20_6(offset); @@ -409,7 +577,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_6(newValue, offset).replace_20_6(oldValue, offset)); } - function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes8 oldValue = container.extract_20_8(offset); @@ -418,7 +586,16 @@ contract PackingTest is Test { assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset)); } - function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes10 oldValue = container.extract_20_10(offset); + + assertEq(newValue, container.replace_20_10(newValue, offset).extract_20_10(offset)); + assertEq(container, container.replace_20_10(newValue, offset).replace_20_10(oldValue, offset)); + } + + function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes12 oldValue = container.extract_20_12(offset); @@ -427,7 +604,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_12(newValue, offset).replace_20_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes16 oldValue = container.extract_20_16(offset); @@ -436,7 +613,88 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 21)); + + bytes1 oldValue = container.extract_22_1(offset); + + assertEq(newValue, container.replace_22_1(newValue, offset).extract_22_1(offset)); + assertEq(container, container.replace_22_1(newValue, offset).replace_22_1(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 20)); + + bytes2 oldValue = container.extract_22_2(offset); + + assertEq(newValue, container.replace_22_2(newValue, offset).extract_22_2(offset)); + assertEq(container, container.replace_22_2(newValue, offset).replace_22_2(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes4 oldValue = container.extract_22_4(offset); + + assertEq(newValue, container.replace_22_4(newValue, offset).extract_22_4(offset)); + assertEq(container, container.replace_22_4(newValue, offset).replace_22_4(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 16)); + + bytes6 oldValue = container.extract_22_6(offset); + + assertEq(newValue, container.replace_22_6(newValue, offset).extract_22_6(offset)); + assertEq(container, container.replace_22_6(newValue, offset).replace_22_6(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes8 oldValue = container.extract_22_8(offset); + + assertEq(newValue, container.replace_22_8(newValue, offset).extract_22_8(offset)); + assertEq(container, container.replace_22_8(newValue, offset).replace_22_8(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 12)); + + bytes10 oldValue = container.extract_22_10(offset); + + assertEq(newValue, container.replace_22_10(newValue, offset).extract_22_10(offset)); + assertEq(container, container.replace_22_10(newValue, offset).replace_22_10(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes12 oldValue = container.extract_22_12(offset); + + assertEq(newValue, container.replace_22_12(newValue, offset).extract_22_12(offset)); + assertEq(container, container.replace_22_12(newValue, offset).replace_22_12(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes16 oldValue = container.extract_22_16(offset); + + assertEq(newValue, container.replace_22_16(newValue, offset).extract_22_16(offset)); + assertEq(container, container.replace_22_16(newValue, offset).replace_22_16(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes20 oldValue = container.extract_22_20(offset); + + assertEq(newValue, container.replace_22_20(newValue, offset).extract_22_20(offset)); + assertEq(container, container.replace_22_20(newValue, offset).replace_22_20(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 23)); bytes1 oldValue = container.extract_24_1(offset); @@ -445,7 +703,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_1(newValue, offset).replace_24_1(oldValue, offset)); } - function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes2 oldValue = container.extract_24_2(offset); @@ -454,7 +712,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_2(newValue, offset).replace_24_2(oldValue, offset)); } - function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes4 oldValue = container.extract_24_4(offset); @@ -463,7 +721,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset)); } - function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes6 oldValue = container.extract_24_6(offset); @@ -472,7 +730,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_6(newValue, offset).replace_24_6(oldValue, offset)); } - function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes8 oldValue = container.extract_24_8(offset); @@ -481,7 +739,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset)); } - function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes10 oldValue = container.extract_24_10(offset); + + assertEq(newValue, container.replace_24_10(newValue, offset).extract_24_10(offset)); + assertEq(container, container.replace_24_10(newValue, offset).replace_24_10(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes12 oldValue = container.extract_24_12(offset); @@ -490,7 +757,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_12(newValue, offset).replace_24_12(oldValue, offset)); } - function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes16 oldValue = container.extract_24_16(offset); @@ -499,7 +766,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_16(newValue, offset).replace_24_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes20 oldValue = container.extract_24_20(offset); @@ -508,7 +775,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes22 oldValue = container.extract_24_22(offset); + + assertEq(newValue, container.replace_24_22(newValue, offset).extract_24_22(offset)); + assertEq(container, container.replace_24_22(newValue, offset).replace_24_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 27)); bytes1 oldValue = container.extract_28_1(offset); @@ -517,7 +793,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_1(newValue, offset).replace_28_1(oldValue, offset)); } - function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes2 oldValue = container.extract_28_2(offset); @@ -526,7 +802,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_2(newValue, offset).replace_28_2(oldValue, offset)); } - function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes4 oldValue = container.extract_28_4(offset); @@ -535,7 +811,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset)); } - function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes6 oldValue = container.extract_28_6(offset); @@ -544,7 +820,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_6(newValue, offset).replace_28_6(oldValue, offset)); } - function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes8 oldValue = container.extract_28_8(offset); @@ -553,7 +829,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset)); } - function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes10 oldValue = container.extract_28_10(offset); + + assertEq(newValue, container.replace_28_10(newValue, offset).extract_28_10(offset)); + assertEq(container, container.replace_28_10(newValue, offset).replace_28_10(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes12 oldValue = container.extract_28_12(offset); @@ -562,7 +847,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_12(newValue, offset).replace_28_12(oldValue, offset)); } - function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes16 oldValue = container.extract_28_16(offset); @@ -571,7 +856,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_16(newValue, offset).replace_28_16(oldValue, offset)); } - function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes20 oldValue = container.extract_28_20(offset); @@ -580,7 +865,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes22 oldValue = container.extract_28_22(offset); + + assertEq(newValue, container.replace_28_22(newValue, offset).extract_28_22(offset)); + assertEq(container, container.replace_28_22(newValue, offset).replace_28_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes24 oldValue = container.extract_28_24(offset); @@ -589,7 +883,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_24(newValue, offset).replace_28_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 31)); bytes1 oldValue = container.extract_32_1(offset); @@ -598,7 +892,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_1(newValue, offset).replace_32_1(oldValue, offset)); } - function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 30)); bytes2 oldValue = container.extract_32_2(offset); @@ -607,7 +901,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_2(newValue, offset).replace_32_2(oldValue, offset)); } - function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 28)); bytes4 oldValue = container.extract_32_4(offset); @@ -616,7 +910,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset)); } - function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes6 oldValue = container.extract_32_6(offset); @@ -625,7 +919,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_6(newValue, offset).replace_32_6(oldValue, offset)); } - function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes8 oldValue = container.extract_32_8(offset); @@ -634,7 +928,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset)); } - function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 22)); + + bytes10 oldValue = container.extract_32_10(offset); + + assertEq(newValue, container.replace_32_10(newValue, offset).extract_32_10(offset)); + assertEq(container, container.replace_32_10(newValue, offset).replace_32_10(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes12 oldValue = container.extract_32_12(offset); @@ -643,7 +946,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_12(newValue, offset).replace_32_12(oldValue, offset)); } - function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes16 oldValue = container.extract_32_16(offset); @@ -652,7 +955,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_16(newValue, offset).replace_32_16(oldValue, offset)); } - function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes20 oldValue = container.extract_32_20(offset); @@ -661,7 +964,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } - function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes22 oldValue = container.extract_32_22(offset); + + assertEq(newValue, container.replace_32_22(newValue, offset).extract_32_22(offset)); + assertEq(container, container.replace_32_22(newValue, offset).replace_32_22(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes24 oldValue = container.extract_32_24(offset); @@ -670,7 +982,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_24(newValue, offset).replace_32_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes28 oldValue = container.extract_32_28(offset);