diff --git a/contracts/abstraction/account/Account.sol b/contracts/abstraction/account/Account.sol index 38e8f9a6274..42bd0bba599 100644 --- a/contracts/abstraction/account/Account.sol +++ b/contracts/abstraction/account/Account.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.20; -import {PackedUserOperation, IAccount, IEntryPoint} from "../../interfaces/IERC4337.sol"; -import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; +import {PackedUserOperation, IAccount, IAccountExecute, IEntryPoint} from "../../interfaces/IERC4337.sol"; import {ERC4337Utils} from "./../utils/ERC4337Utils.sol"; +import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; +import {Address} from "../../utils/Address.sol"; -abstract contract Account is IAccount { +abstract contract Account is IAccount, IAccountExecute { error AccountEntryPointRestricted(); - error AccountInvalidBatchLength(); /**************************************************************************************************************** * Modifiers * @@ -39,7 +39,7 @@ abstract contract Account is IAccount { * * Subclass must implement this using their own access control mechanism. */ - function _isAuthorized(address) internal virtual returns (bool); + function _isAuthorized(address) internal view virtual returns (bool); /** * @dev Recover the signer for a given signature and user operation hash. This function does not need to verify @@ -47,7 +47,7 @@ abstract contract Account is IAccount { * * Subclass must implement this using their own choice of cryptography. */ - function _recoverSigner(bytes32 userOpHash, bytes calldata signature) internal virtual returns (address); + function _recoverSigner(bytes32 userOpHash, bytes calldata signature) internal view virtual returns (address); /**************************************************************************************************************** * Public interface * @@ -72,13 +72,18 @@ abstract contract Account is IAccount { PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds - ) public virtual override onlyEntryPoint returns (uint256 validationData) { + ) public virtual onlyEntryPoint returns (uint256 validationData) { (bool valid, , uint48 validAfter, uint48 validUntil) = _processSignature(userOpHash, userOp.signature); _validateNonce(userOp.nonce); _payPrefund(missingAccountFunds); return ERC4337Utils.packValidationData(valid, validAfter, validUntil); } + /// @inheritdoc IAccountExecute + function executeUserOp(PackedUserOperation calldata userOp, bytes32 /*userOpHash*/) public virtual onlyEntryPoint { + Address.functionDelegateCall(address(this), userOp.callData[4:]); + } + /**************************************************************************************************************** * Internal mechanisms * ****************************************************************************************************************/ @@ -95,7 +100,7 @@ abstract contract Account is IAccount { function _processSignature( bytes32 userOpHash, bytes calldata signature - ) internal virtual returns (bool valid, address signer, uint48 validAfter, uint48 validUntil) { + ) internal view virtual returns (bool valid, address signer, uint48 validAfter, uint48 validUntil) { address recovered = _recoverSigner(userOpHash, signature); return (recovered != address(0) && _isAuthorized(recovered), recovered, 0, 0); } diff --git a/contracts/abstraction/account/AccountCommon.sol b/contracts/abstraction/account/AccountCommon.sol deleted file mode 100644 index 3c29ef4ef1b..00000000000 --- a/contracts/abstraction/account/AccountCommon.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {IEntryPoint} from "../../interfaces/IERC4337.sol"; -import {ERC721Holder} from "../../token/ERC721/utils/ERC721Holder.sol"; -import {ERC1155Holder} from "../../token/ERC1155/utils/ERC1155Holder.sol"; -import {Address} from "../../utils/Address.sol"; -import {Account} from "./Account.sol"; - -abstract contract AccountCommon is Account, ERC721Holder, ERC1155Holder { - IEntryPoint private immutable _entryPoint; - - constructor(IEntryPoint entryPoint_) { - _entryPoint = entryPoint_; - } - - receive() external payable {} - - function entryPoint() public view virtual override returns (IEntryPoint) { - return _entryPoint; - } - - function execute(address target, uint256 value, bytes calldata data) public virtual onlyEntryPoint { - _call(target, value, data); - } - - function executeBatch( - address[] calldata targets, - uint256[] calldata values, - bytes[] calldata calldatas - ) public virtual onlyEntryPoint { - if (targets.length != calldatas.length || (values.length != 0 && values.length != targets.length)) { - revert AccountInvalidBatchLength(); - } - - for (uint256 i = 0; i < targets.length; ++i) { - _call(targets[i], (values.length == 0 ? 0 : values[i]), calldatas[i]); - } - } - - function _call(address target, uint256 value, bytes memory data) internal { - (bool success, bytes memory returndata) = target.call{value: value}(data); - Address.verifyCallResult(success, returndata); - } -} diff --git a/contracts/abstraction/account/ERC7579Account.sol b/contracts/abstraction/account/ERC7579Account.sol new file mode 100644 index 00000000000..c5f627eeab8 --- /dev/null +++ b/contracts/abstraction/account/ERC7579Account.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Account} from "./Account.sol"; +import {Address} from "../../utils/Address.sol"; +import {ERC1155Holder} from "../../token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC721Holder} from "../../token/ERC721/utils/ERC721Holder.sol"; +import {ERC7579Utils} from "../utils/ERC7579Utils.sol"; +import {IEntryPoint} from "../../interfaces/IERC4337.sol"; +import {IERC1271} from "../../interfaces/IERC1271.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; +import {IERC7579Account, Execution} from "../../interfaces/IERC7579Account.sol"; + +abstract contract ERC7579Account is IERC7579Account, Account, ERC165, ERC721Holder, ERC1155Holder { + using ERC7579Utils for *; + + IEntryPoint private immutable _entryPoint; + + error UnsupportedCallType(bytes1); + + modifier onlyExecutorModule() { + // TODO + _; + } + + constructor(IEntryPoint entryPoint_) { + _entryPoint = entryPoint_; + } + + receive() external payable {} + + function entryPoint() public view virtual override returns (IEntryPoint) { + return _entryPoint; + } + + /// IERC165 + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, ERC165, ERC1155Holder) returns (bool) { + // TODO: more? + return super.supportsInterface(interfaceId); + } + + /// IERC1271 + function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue) { + (bool valid, , uint48 validAfter, uint48 validUntil) = _processSignature(hash, signature); + return + (valid && validAfter < block.timestamp && (validUntil == 0 || validUntil > block.timestamp)) + ? IERC1271.isValidSignature.selector + : bytes4(0); + } + + /// IERC7579Execution + function execute(bytes32 mode, bytes calldata executionCalldata) public virtual onlyEntryPoint { + // TODO: support execType ? + (bytes1 callType, , , ) = mode.parseMode(); + + if (callType == ERC7579Utils.CALLTYPE_SINGLE) { + (address target, uint256 value, bytes calldata callData) = executionCalldata.parseSingle(); + _execute(target, value, callData); + } else if (callType == ERC7579Utils.CALLTYPE_BATCH) { + Execution[] calldata executionBatch = executionCalldata.parseBatch(); + for (uint256 i = 0; i < executionBatch.length; ++i) { + _execute(executionBatch[i].target, executionBatch[i].value, executionBatch[i].callData); + } + } else { + revert UnsupportedCallType(callType); + } + } + + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) public virtual onlyExecutorModule returns (bytes[] memory returnData) { + // TODO: support execType ? + (bytes1 callType, , , ) = mode.parseMode(); + + if (callType == ERC7579Utils.CALLTYPE_SINGLE) { + (address target, uint256 value, bytes calldata callData) = executionCalldata.parseSingle(); + returnData = new bytes[](1); + returnData[0] = _execute(target, value, callData); + } else if (callType == ERC7579Utils.CALLTYPE_BATCH) { + Execution[] calldata executionBatch = executionCalldata.parseBatch(); + returnData = new bytes[](executionBatch.length); + for (uint256 i = 0; i < executionBatch.length; ++i) { + returnData[i] = _execute(executionBatch[i].target, executionBatch[i].value, executionBatch[i].callData); + } + } else { + revert UnsupportedCallType(callType); + } + } + + function _execute(address target, uint256 value, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.call{value: value}(data); + Address.verifyCallResult(success, returndata); + return returndata; + } + + /// IERC7579AccountConfig + function accountId() external pure returns (/*view*/ string memory /*accountImplementationId*/) { + revert("not-implemented-yet"); + } + + function supportsExecutionMode(bytes32 /*encodedMode*/ /*view*/) external pure returns (bool) { + revert("not-implemented-yet"); + } + + function supportsModule(uint256 /*moduleTypeId*/ /*view*/) external pure returns (bool) { + revert("not-implemented-yet"); + } + + /// IERC7579ModuleConfig + function installModule(uint256 /*moduleTypeId*/, address /*module*/, bytes calldata /*initData*/) external pure { + revert("not-implemented-yet"); + } + + function uninstallModule( + uint256 /*moduleTypeId*/, + address /*module*/, + bytes calldata /*deInitData*/ + ) external pure { + revert("not-implemented-yet"); + } + + function isModuleInstalled( + uint256 /*moduleTypeId*/, + address /*module*/, + bytes calldata /*additionalContext*/ /*view*/ + ) external pure returns (bool) { + revert("not-implemented-yet"); + } +} diff --git a/contracts/abstraction/account/modules/AccountMultisig.sol b/contracts/abstraction/account/modules/AccountMultisig.sol index 67498695148..c6c6f8f6caa 100644 --- a/contracts/abstraction/account/modules/AccountMultisig.sol +++ b/contracts/abstraction/account/modules/AccountMultisig.sol @@ -13,7 +13,7 @@ abstract contract AccountMultisig is Account { function _processSignature( bytes32 userOpHash, bytes calldata signatures - ) internal virtual override returns (bool, address, uint48 validAfter, uint48 validUntil) { + ) internal view virtual override returns (bool, address, uint48 validAfter, uint48 validUntil) { bytes[] calldata signatureArray = _decodeBytesArray(signatures); if (signatureArray.length < requiredSignatures()) { diff --git a/contracts/abstraction/account/modules/recovery/AccountAllSignatures.sol b/contracts/abstraction/account/modules/recovery/AccountAllSignatures.sol index 97f87448dd7..a10aa0cdff4 100644 --- a/contracts/abstraction/account/modules/recovery/AccountAllSignatures.sol +++ b/contracts/abstraction/account/modules/recovery/AccountAllSignatures.sol @@ -15,7 +15,7 @@ abstract contract AccountAllSignatures is AccountECDSA, AccountERC1271 { function _recoverSigner( bytes32 userOpHash, bytes calldata signature - ) internal virtual override(AccountECDSA, AccountERC1271) returns (address) { + ) internal view virtual override(AccountECDSA, AccountERC1271) returns (address) { SignatureType sigType = SignatureType(uint8(bytes1(signature))); if (sigType == SignatureType.ECDSA) { diff --git a/contracts/abstraction/account/modules/recovery/AccountECDSA.sol b/contracts/abstraction/account/modules/recovery/AccountECDSA.sol index 206d65f0f9d..51cc214f079 100644 --- a/contracts/abstraction/account/modules/recovery/AccountECDSA.sol +++ b/contracts/abstraction/account/modules/recovery/AccountECDSA.sol @@ -11,7 +11,7 @@ abstract contract AccountECDSA is Account { function _recoverSigner( bytes32 userOpHash, bytes calldata signature - ) internal virtual override returns (address signer) { + ) internal view virtual override returns (address signer) { bytes32 msgHash = MessageHashUtils.toEthSignedMessageHash(userOpHash); // This implementation support both "normal" and short signature formats: diff --git a/contracts/abstraction/account/modules/recovery/AccountERC1271.sol b/contracts/abstraction/account/modules/recovery/AccountERC1271.sol index 43423c3a7a5..c92f323f4aa 100644 --- a/contracts/abstraction/account/modules/recovery/AccountERC1271.sol +++ b/contracts/abstraction/account/modules/recovery/AccountERC1271.sol @@ -10,7 +10,10 @@ import {Account} from "../../Account.sol"; abstract contract AccountERC1271 is Account { error P256InvalidSignatureLength(uint256 length); - function _recoverSigner(bytes32 userOpHash, bytes calldata signature) internal virtual override returns (address) { + function _recoverSigner( + bytes32 userOpHash, + bytes calldata signature + ) internal view virtual override returns (address) { bytes32 msgHash = MessageHashUtils.toEthSignedMessageHash(userOpHash); address signer = address(bytes20(signature[0x00:0x14])); diff --git a/contracts/abstraction/mocks/AdvancedAccount.sol b/contracts/abstraction/mocks/AdvancedAccount.sol index f60d9f7bd98..9475a768104 100644 --- a/contracts/abstraction/mocks/AdvancedAccount.sol +++ b/contracts/abstraction/mocks/AdvancedAccount.sol @@ -4,13 +4,12 @@ pragma solidity ^0.8.20; import {IEntryPoint} from "../../interfaces/IERC4337.sol"; import {AccessControl} from "../../access/AccessControl.sol"; -import {ERC1155Holder} from "../../token/ERC1155/utils/ERC1155Holder.sol"; import {Account} from "../account/Account.sol"; -import {AccountCommon} from "../account/AccountCommon.sol"; +import {ERC7579Account} from "../account/ERC7579Account.sol"; import {AccountMultisig} from "../account/modules/AccountMultisig.sol"; import {AccountAllSignatures} from "../account/modules/recovery/AccountAllSignatures.sol"; -contract AdvancedAccount is AccessControl, AccountCommon, AccountAllSignatures, AccountMultisig { +contract AdvancedAccount is AccessControl, ERC7579Account, AccountAllSignatures, AccountMultisig { bytes32 public constant SIGNER_ROLE = keccak256("SIGNER_ROLE"); uint256 private _requiredSignatures; @@ -19,7 +18,7 @@ contract AdvancedAccount is AccessControl, AccountCommon, AccountAllSignatures, address admin_, address[] memory signers_, uint256 requiredSignatures_ - ) AccountCommon(entryPoint_) { + ) ERC7579Account(entryPoint_) { _grantRole(DEFAULT_ADMIN_ROLE, admin_); for (uint256 i = 0; i < signers_.length; ++i) { _grantRole(SIGNER_ROLE, signers_[i]); @@ -29,7 +28,7 @@ contract AdvancedAccount is AccessControl, AccountCommon, AccountAllSignatures, function supportsInterface( bytes4 interfaceId - ) public view virtual override(AccessControl, ERC1155Holder) returns (bool) { + ) public view virtual override(ERC7579Account, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } @@ -44,7 +43,7 @@ contract AdvancedAccount is AccessControl, AccountCommon, AccountAllSignatures, function _processSignature( bytes32 userOpHash, bytes calldata signature - ) internal virtual override(Account, AccountMultisig) returns (bool, address, uint48, uint48) { + ) internal view virtual override(Account, AccountMultisig) returns (bool, address, uint48, uint48) { return super._processSignature(userOpHash, signature); } } diff --git a/contracts/abstraction/mocks/SimpleAccount.sol b/contracts/abstraction/mocks/SimpleAccount.sol index 34bd27fdf96..9c23cd07ea3 100644 --- a/contracts/abstraction/mocks/SimpleAccount.sol +++ b/contracts/abstraction/mocks/SimpleAccount.sol @@ -4,20 +4,20 @@ pragma solidity ^0.8.20; import {IEntryPoint} from "../../interfaces/IERC4337.sol"; import {Ownable} from "../../access/Ownable.sol"; -import {AccountCommon} from "../account/AccountCommon.sol"; +import {ERC7579Account} from "../account/ERC7579Account.sol"; import {AccountECDSA} from "../account/modules/recovery/AccountECDSA.sol"; import {AccountERC1271} from "../account/modules/recovery/AccountERC1271.sol"; -contract SimpleAccountECDSA is Ownable, AccountCommon, AccountECDSA { - constructor(IEntryPoint entryPoint_, address owner_) AccountCommon(entryPoint_) Ownable(owner_) {} +contract SimpleAccountECDSA is Ownable, ERC7579Account, AccountECDSA { + constructor(IEntryPoint entryPoint_, address owner_) ERC7579Account(entryPoint_) Ownable(owner_) {} function _isAuthorized(address user) internal view virtual override returns (bool) { return user == owner(); } } -contract SimpleAccountERC1271 is Ownable, AccountCommon, AccountERC1271 { - constructor(IEntryPoint entryPoint_, address owner_) AccountCommon(entryPoint_) Ownable(owner_) {} +contract SimpleAccountERC1271 is Ownable, ERC7579Account, AccountERC1271 { + constructor(IEntryPoint entryPoint_, address owner_) ERC7579Account(entryPoint_) Ownable(owner_) {} function _isAuthorized(address user) internal view virtual override returns (bool) { return user == owner(); diff --git a/contracts/abstraction/utils/ERC7579Utils.sol b/contracts/abstraction/utils/ERC7579Utils.sol new file mode 100644 index 00000000000..1cd03ca7549 --- /dev/null +++ b/contracts/abstraction/utils/ERC7579Utils.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Execution} from "../../interfaces/IERC7579Account.sol"; +import {Packing} from "../../utils/Packing.sol"; + +library ERC7579Utils { + using Packing for *; + + bytes1 constant CALLTYPE_SINGLE = bytes1(0x00); + bytes1 constant CALLTYPE_BATCH = bytes1(0x01); + bytes1 constant CALLTYPE_DELEGATECALL = bytes1(0xFF); + + bytes1 constant EXECTYPE_DEFAULT = bytes1(0x00); + bytes1 constant EXECTYPE_TRY = bytes1(0x01); + + function parseMode( + bytes32 mode + ) internal pure returns (bytes1 callType, bytes1 execType, bytes4 selector, bytes22 payload) { + return ( + Packing.extract_32_1(mode, 0), + Packing.extract_32_1(mode, 1), + Packing.extract_32_4(mode, 6), + Packing.extract_32_22(mode, 10) + ); + } + + function parseSingle( + bytes calldata executionCalldata + ) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + function parseDelegate( + bytes calldata executionCalldata + ) internal pure returns (address target, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + callData = executionCalldata[20:]; + } + + function parseBatch(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) + } + } +} diff --git a/contracts/interfaces/IERC7579Account.sol b/contracts/interfaces/IERC7579Account.sol new file mode 100644 index 00000000000..12740028a41 --- /dev/null +++ b/contracts/interfaces/IERC7579Account.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +// import { CallType, ExecType, ModeCode } from "../lib/ModeLib.sol"; +import {IERC165} from "./IERC165.sol"; +import {IERC1271} from "./IERC1271.sol"; + +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IERC7579Execution { + /** + * @dev Executes a transaction on behalf of the account. + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function execute(bytes32 mode, bytes calldata executionCalldata) external; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: i.e. onlyExecutorModule + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) external returns (bytes[] memory returnData); +} + +interface IERC7579AccountConfig { + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * + * MUST return a non-empty string + * The accountId SHOULD be structured like so: + * "vendorname.accountname.semver" + * The id SHOULD be unique across all smart accounts + */ + function accountId() external view returns (string memory accountImplementationId); + + /** + * @dev Function to check if the account supports a certain execution mode (see above) + * @param encodedMode the encoded mode + * + * MUST return true if the account supports the mode and false otherwise + */ + function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); + + /** + * @dev Function to check if the account supports a certain module typeId + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * + * MUST return true if the account supports the module type and false otherwise + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579ModuleConfig { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Installs a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onInstall` on the module with the `initData` parameter if provided + * MUST emit ModuleInstalled event + * MUST revert if the module is already installed or the initialization on the module failed + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; + + /** + * @dev Uninstalls a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onUninstall` on the module with the `deInitData` parameter if provided + * MUST emit ModuleUninstalled event + * MUST revert if the module is not installed or the deInitialization on the module failed + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; + + /** + * @dev Returns whether a module is installed on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param additionalContext arbitrary data that may be required to determine if the module is installed + * + * MUST return true if the module is installed and false otherwise + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) external view returns (bool); +} + +interface IERC7579Account is IERC165, IERC1271, IERC7579Execution, IERC7579AccountConfig, IERC7579ModuleConfig {} diff --git a/contracts/interfaces/IERC7579Module.sol b/contracts/interfaces/IERC7579Module.sol new file mode 100644 index 00000000000..0350e7e8853 --- /dev/null +++ b/contracts/interfaces/IERC7579Module.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "./IERC4337.sol"; + +interface IERC7579Module { + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` initialization + * + * MUST revert on error (e.g. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579Validator is IERC7579Module { + /** + * @dev Validates a UserOperation + * @param userOp the ERC-4337 PackedUserOperation + * @param userOpHash the hash of the ERC-4337 PackedUserOperation + * + * MUST validate that the signature is a valid signature of the userOpHash + * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /** + * @dev Validates a signature using ERC-1271 + * @param sender the address that sent the ERC-1271 request to the smart account + * @param hash the hash of the ERC-1271 request + * @param signature the signature of the ERC-1271 request + * + * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid + * MUST NOT modify state + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} + +interface IERC7579Hook is IERC7579Module { + /** + * @dev Called by the smart account before execution + * @param msgSender the address that called the smart account + * @param value the value that was sent to the smart account + * @param msgData the data that was sent to the smart account + * + * MAY return arbitrary data in the `hookData` return value + */ + function preCheck( + address msgSender, + uint256 value, + bytes calldata msgData + ) external returns (bytes memory hookData); + + /** + * @dev Called by the smart account after execution + * @param hookData the data that was returned by the `preCheck` function + * + * MAY validate the `hookData` to validate transaction context of the `preCheck` function + */ + function postCheck(bytes calldata hookData) external; +} diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index a5a38b50397..502fa7eab51 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -57,6 +57,18 @@ library Packing { } } + function pack_2_20(bytes2 left, bytes20 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + result := or(left, shr(16, right)) + } + } + + function pack_2_22(bytes2 left, bytes22 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + result := or(left, shr(16, right)) + } + } + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { result := or(left, shr(32, right)) @@ -117,6 +129,18 @@ library Packing { } } + function pack_6_16(bytes6 left, bytes16 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + result := or(left, shr(48, right)) + } + } + + function pack_6_22(bytes6 left, bytes22 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + result := or(left, shr(48, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { result := or(left, shr(64, right)) @@ -189,6 +213,12 @@ library Packing { } } + function pack_16_6(bytes16 left, bytes6 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + result := or(left, shr(128, right)) + } + } + function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { result := or(left, shr(128, right)) @@ -207,6 +237,12 @@ library Packing { } } + function pack_20_2(bytes20 left, bytes2 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + result := or(left, shr(160, right)) + } + } + function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { result := or(left, shr(160, right)) @@ -225,6 +261,18 @@ library Packing { } } + function pack_22_2(bytes22 left, bytes2 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + result := or(left, shr(176, right)) + } + } + + function pack_22_6(bytes22 left, bytes6 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + result := or(left, shr(176, right)) + } + } + function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { result := or(left, shr(192, right)) @@ -635,6 +683,118 @@ 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") { + 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") { + 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") { + 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") { + 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") { + 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") { + 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") { + 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") { + 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") { @@ -747,6 +907,20 @@ 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") { + 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") { @@ -859,6 +1033,20 @@ 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") { + 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") { @@ -985,6 +1173,20 @@ 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") { + 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..c981385bd72 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, 12, 16, 20, 22, 24, 28, 32], }; diff --git a/test/abstraction/TODO.md b/test/abstraction/TODO.md new file mode 100644 index 00000000000..1cb0be092ae --- /dev/null +++ b/test/abstraction/TODO.md @@ -0,0 +1,5 @@ +- [ ] test ERC1271 +- [ ] test batch exec mode +- [ ] implement and test delegate exec mode ? +- [ ] implement module support +- [ ] implement ECDSA, 1271, multisig as modules ? \ No newline at end of file diff --git a/test/abstraction/accountECDSA.test.js b/test/abstraction/accountECDSA.test.js index 190bf70763c..5bb9727ec01 100644 --- a/test/abstraction/accountECDSA.test.js +++ b/test/abstraction/accountECDSA.test.js @@ -2,8 +2,9 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { ERC4337Helper } = require('../helpers/erc4337'); const { IdentityHelper } = require('../helpers/identity'); +const { ERC4337Helper } = require('../helpers/erc4337'); +const { encodeMode, encodeSingle } = require('../helpers/erc7579'); async function fixture() { const accounts = await ethers.getSigners(); @@ -46,9 +47,8 @@ describe('AccountECDSA', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 17, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 17, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.addInitCode()) @@ -71,9 +71,8 @@ describe('AccountECDSA', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign()); @@ -87,9 +86,8 @@ describe('AccountECDSA', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign()); diff --git a/test/abstraction/accountERC1271.test.js b/test/abstraction/accountERC1271.test.js index 14dfbea0845..a12788d6312 100644 --- a/test/abstraction/accountERC1271.test.js +++ b/test/abstraction/accountERC1271.test.js @@ -2,8 +2,9 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { ERC4337Helper } = require('../helpers/erc4337'); const { IdentityHelper } = require('../helpers/identity'); +const { ERC4337Helper } = require('../helpers/erc4337'); +const { encodeMode, encodeSingle } = require('../helpers/erc7579'); async function fixture() { const accounts = await ethers.getSigners(); @@ -47,9 +48,8 @@ describe('AccountERC1271', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 17, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 17, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.addInitCode()) @@ -72,9 +72,8 @@ describe('AccountERC1271', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign()); diff --git a/test/abstraction/accountMultisig.test.js b/test/abstraction/accountMultisig.test.js index b8d65061ec6..bbe5924b366 100644 --- a/test/abstraction/accountMultisig.test.js +++ b/test/abstraction/accountMultisig.test.js @@ -2,8 +2,9 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { ERC4337Helper } = require('../helpers/erc4337'); const { IdentityHelper } = require('../helpers/identity'); +const { ERC4337Helper } = require('../helpers/erc4337'); +const { encodeMode, encodeSingle } = require('../helpers/erc7579'); async function fixture() { const accounts = await ethers.getSigners(); @@ -52,9 +53,8 @@ describe('AccountMultisig', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 17, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 17, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.addInitCode()) @@ -77,9 +77,8 @@ describe('AccountMultisig', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign(this.signers)); @@ -93,9 +92,8 @@ describe('AccountMultisig', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign([this.signers[0], this.signers[2]])); @@ -109,9 +107,8 @@ describe('AccountMultisig', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign([this.signers[2]])); @@ -125,9 +122,8 @@ describe('AccountMultisig', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign([this.accounts.relayer, this.signers[2]])); diff --git a/test/abstraction/entrypoint.test.js b/test/abstraction/entrypoint.test.js index df73f560c8b..fab14678411 100644 --- a/test/abstraction/entrypoint.test.js +++ b/test/abstraction/entrypoint.test.js @@ -4,6 +4,7 @@ const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); const { ERC4337Helper } = require('../helpers/erc4337'); +const { encodeMode, encodeSingle } = require('../helpers/erc7579'); async function fixture() { const accounts = await ethers.getSigners(); @@ -143,9 +144,8 @@ describe('EntryPoint', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 17, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 17, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.addInitCode()) @@ -168,9 +168,8 @@ describe('EntryPoint', function () { const operation = await this.sender .createOp({ callData: this.sender.interface.encodeFunctionData('execute', [ - this.target.target, - 42, - this.target.interface.encodeFunctionData('mockFunctionExtra'), + encodeMode(), + encodeSingle(this.target, 42, this.target.interface.encodeFunctionData('mockFunctionExtra')), ]), }) .then(op => op.sign()); diff --git a/test/helpers/erc7579.js b/test/helpers/erc7579.js new file mode 100644 index 00000000000..ac0e7127a25 --- /dev/null +++ b/test/helpers/erc7579.js @@ -0,0 +1,23 @@ +const { ethers } = require('hardhat'); + +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]); + +/// TODO +// const encodeBatch = + +module.exports = { + encodeMode, + encodeSingle, +}; diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index 9531f1bffbb..dfa5638b34c 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -29,6 +29,16 @@ contract PackingTest is Test { assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); } + function testPack(bytes2 left, bytes20 right) external { + 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 { + 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 { assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); @@ -79,6 +89,16 @@ contract PackingTest is Test { assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); } + function testPack(bytes6 left, bytes16 right) external { + 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 { + 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, bytes4 right) external { assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0)); assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8)); @@ -139,6 +159,11 @@ contract PackingTest is Test { assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16)); } + function testPack(bytes16 left, bytes6 right) external { + 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 { assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0)); assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16)); @@ -154,6 +179,11 @@ contract PackingTest is Test { assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16)); } + function testPack(bytes20 left, bytes2 right) external { + 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 { assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0)); assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20)); @@ -169,6 +199,16 @@ contract PackingTest is Test { assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20)); } + function testPack(bytes22 left, bytes2 right) external { + 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 { + 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(bytes24 left, bytes4 right) external { assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0)); assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24)); @@ -436,6 +476,78 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } + function testReplace(bytes22 container, bytes1 newValue, uint8 offset) external { + 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 { + 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 { + 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 { + 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 { + 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, bytes12 newValue, uint8 offset) external { + 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 { + 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 { + 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 { offset = uint8(bound(offset, 0, 23)); @@ -508,6 +620,15 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } + function testReplace(bytes24 container, bytes22 newValue, uint8 offset) external { + 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 { offset = uint8(bound(offset, 0, 27)); @@ -580,6 +701,15 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } + function testReplace(bytes28 container, bytes22 newValue, uint8 offset) external { + 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 { offset = uint8(bound(offset, 0, 4)); @@ -661,6 +791,15 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } + function testReplace(bytes32 container, bytes22 newValue, uint8 offset) external { + 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 { offset = uint8(bound(offset, 0, 8));