From aa88f8905688b41912c8167eae9ec5ddf280fa38 Mon Sep 17 00:00:00 2001 From: taek Date: Wed, 5 Aug 2026 19:18:04 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix:=20audit=20batch=20=E2=80=94=20enable-m?= =?UTF-8?q?ode=20install=20bypass=20(H),=20factory-nonce=20replay=20(M),?= =?UTF-8?q?=20validity-format=20ordering=20(M)=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: gate enable-mode install on root signature success * test: enable-mode install rejected on failed root signature * fix: advance factory nonce on direct approval changes * test: direct factory approval advances nonce * fix: normalize validity ranges before format classification * test: validity-format ordering regressions * fix: RP-01 checkValidation EntryPoint v0.9 parity (#59) * fix: checkValidation block-mode and exclusive validAfter per EntryPoint v0.9 * test: cover checkValidation EntryPoint v0.9 boundaries * fix: vendor canonical EntryPoint v0.9 release initcode and address --- CHANGELOG_AUDIT.md | 2 +- src/Kernel.sol | 9 +++ src/Staker.sol | 4 ++ src/lib/Lib4337.sol | 75 ++++++++++++------- test/CheckValidation.t.sol | 112 +++++++++++++++++++++++++++++ test/IntersectValidationData.t.sol | 62 ++++++++++++++++ test/KernelUserOpTest.sol | 45 ++++++++++++ test/Staker.t.sol | 58 +++++++++++++++ test/btt/Lib4337.t.sol | 4 +- test/btt/Lib4337.tree | 2 +- test/utils/EntryPointLib.sol | 4 +- 11 files changed, 345 insertions(+), 32 deletions(-) create mode 100644 test/CheckValidation.t.sol diff --git a/CHANGELOG_AUDIT.md b/CHANGELOG_AUDIT.md index fc320fc1..3953754e 100644 --- a/CHANGELOG_AUDIT.md +++ b/CHANGELOG_AUDIT.md @@ -17,7 +17,7 @@ Added support for ERC-4337 EntryPoint version 0.9. - Gas snapshot updates reflecting v0.9 optimizations (reduced gas costs across all test scenarios) - **Breaking Change:** UserOperation hash calculation has been changed in EntryPoint v0.9 - **Files:** `foundry.toml`, `remappings.txt`, `soldeer.lock`, `test/utils/EntryPointLib.sol`, `test/KernelUserOpTest.sol`, `test/KernelValidatorTest.sol` -- **EntryPoint Address:** `0x43370900c8de573dB349BEd8DD53b4Ebd3Cce709` +- **EntryPoint Address:** `0x433709009B8330FDa32311DF1C2AFA402eD8D009` - **Commits:** 977ca07, aa91ef1, 110c7af, 3e72921 - **Note:** The module type ID was updated from 8 to 10 for `MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER` as part of this upgrade - you can find the release docs in [here](https://docs.google.com/document/d/1RKkKZsP1eYkOoBEkzJ1vWRK_bcWXaewoGPzawMjsleM/edit?usp=drivesdk), please do note that this document is not in public yet diff --git a/src/Kernel.sol b/src/Kernel.sol index a8b87cf1..65a23cdd 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -146,6 +146,15 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { sig := signature.offset } validationData = _verifyInstallSignatureRaw(enableReplayable, sig.nonce, sig.packages, sig.enableSignature); + // Root did not authorize this install -> surface the failure and install nothing. + // Compare only the failure field: a valid enable signature may carry nonzero + // validity bounds, so the full packed word must not be compared against 1. + // Without this guard, a call to validateUserOp outside the EntryPoint validation + // phase (where the returned validationData is ignored) would install modules and + // advance the nonce despite a failed root signature. + if (uint160(validationData) == 1) { + return validationData; + } _checkAndIncrementNonce(sig.nonce); _install(sig.packages); signature = sig.userOpSignature; diff --git a/src/Staker.sol b/src/Staker.sol index 1313e455..a542e907 100644 --- a/src/Staker.sol +++ b/src/Staker.sol @@ -40,6 +40,10 @@ contract Staker is Ownable, EIP712 { /// @param _factory The factory address to approve or revoke. /// @param approval True to approve, false to revoke. function approveFactory(address _factory, bool approval) external payable onlyOwner { + // Advance the per-factory nonce so any outstanding signed approval is invalidated, + // even when `approval` already matches the stored value. Otherwise a revoked factory + // could be re-approved by replaying a previously signed, never-consumed approval. + nonces[_factory]++; approved[_factory] = approval; emit FactoryApprovalChanged(_factory, approval); } diff --git a/src/lib/Lib4337.sol b/src/lib/Lib4337.sol index db96230d..5e0203b1 100644 --- a/src/lib/Lib4337.sol +++ b/src/lib/Lib4337.sol @@ -32,11 +32,20 @@ library Lib4337 { } function checkValidation(uint256 validationData) internal view returns (bool) { + if (validationData == 0) { + return true; + } (uint48 vAfter, uint48 vUntil, address res) = Lib4337.parseValidationData(validationData); - if (vAfter > block.timestamp || vUntil < block.timestamp) { - return false; + uint256 current; + if (_usesBlockNumberFormat(vAfter, vUntil)) { + vAfter &= MODE_BIT - 1; + vUntil &= MODE_BIT - 1; + current = block.number; + } else { + current = block.timestamp; } - return res == address(0); + // Canonical EntryPoint v0.9 interval: (validAfter, validUntil]. + return res == address(0) && current > vAfter && current <= vUntil; } /// @dev Variant of `_hashTypedData` that excludes the chain ID. @@ -78,25 +87,8 @@ library Lib4337 { return preValidationData | validationRes; } - // Extract raw time bounds - uint48 validUntil1 = uint48(preValidationData >> 160); - uint48 validUntil2 = uint48(validationRes >> 160); - uint48 validAfter1 = uint48(preValidationData >> 208); - uint48 validAfter2 = uint48(validationRes >> 208); - - // Check for validity format mismatch (EP v0.9: block number vs timestamp) - // Block number format: both validAfter and validUntil have highest bit set - bool preUsesBlock = _usesBlockNumberFormat(validAfter1, validUntil1); - bool resUsesBlock = _usesBlockNumberFormat(validAfter2, validUntil2); - require(preUsesBlock == resUsesBlock, ValidityFormatMismatch()); - - // Convert validUntil=0 to max (no expiry) - if (validUntil1 == 0) validUntil1 = type(uint48).max; - if (validUntil2 == 0) validUntil2 = type(uint48).max; - - resValidationData = uint256(validUntil1 > validUntil2 ? validUntil2 : validUntil1) << 160; - resValidationData |= uint256(validAfter1 < validAfter2 ? validAfter2 : validAfter1) << 208; - + // Aggregator FIRST: resolve success / failure / conflict before touching the ranges. + // // Aggregator values: 0 = success, 1 = failure, >1 = aggregator address // // Rules (in precedence order): @@ -109,20 +101,51 @@ library Lib4337 { uint160 preAgg = uint160(preValidationData); uint160 resAgg = uint160(validationRes); - uint160 finalAgg; - - finalAgg = (preAgg == 1 || resAgg == 1) + uint160 finalAgg = (preAgg == 1 || resAgg == 1) ? 1 // Any failure : (preAgg == 0 && resAgg == 0) ? 0 // Both success : (preAgg > 1 && resAgg == 0) - ? preAgg // Preserve aggregator (FIX) + ? preAgg // Preserve aggregator : (preAgg == 0 && resAgg > 1) ? resAgg // Use new aggregator : (preAgg == resAgg) ? preAgg // Same aggregator : 1; // Conflict or unknown + // Extract raw time bounds + uint48 validUntil1 = uint48(preValidationData >> 160); + uint48 validUntil2 = uint48(validationRes >> 160); + uint48 validAfter1 = uint48(preValidationData >> 208); + uint48 validAfter2 = uint48(validationRes >> 208); + + // Normalize validUntil=0 to max (no expiry) BEFORE classifying the format. Doing this + // after the format check misclassifies an unbounded block range (validUntil=0) as a + // timestamp range, letting a mixed intersection drop a future timestamp start. + if (validUntil1 == 0) validUntil1 = type(uint48).max; + if (validUntil2 == 0) validUntil2 = type(uint48).max; + + // Only enforce format compatibility for a usable (non-failure) result. When either side + // reports signature failure (finalAgg == 1) the op is rejected regardless of its time + // bounds, and a failed operand carries zeroed bounds that must not trigger a spurious + // ValidityFormatMismatch revert — so the check is skipped. A neutral [0, max] range + // carries no restriction and no format and is likewise exempt (its normalized validUntil + // has MODE_BIT set, which would otherwise misclassify it as a block range). + if (finalAgg != 1) { + bool preNeutral = validAfter1 == 0 && validUntil1 == type(uint48).max; + bool resNeutral = validAfter2 == 0 && validUntil2 == type(uint48).max; + // Block number format: both validAfter and validUntil have the highest bit set. + if (!preNeutral && !resNeutral) { + require( + _usesBlockNumberFormat(validAfter1, validUntil1) + == _usesBlockNumberFormat(validAfter2, validUntil2), + ValidityFormatMismatch() + ); + } + } + + resValidationData = uint256(validUntil1 > validUntil2 ? validUntil2 : validUntil1) << 160; + resValidationData |= uint256(validAfter1 < validAfter2 ? validAfter2 : validAfter1) << 208; resValidationData |= finalAgg; } } diff --git a/test/CheckValidation.t.sol b/test/CheckValidation.t.sol new file mode 100644 index 00000000..9d10a14b --- /dev/null +++ b/test/CheckValidation.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "src/lib/Lib4337.sol"; + +/// @dev RP-01: `Lib4337.checkValidation` must match canonical EntryPoint v0.9 semantics — +/// exact-zero success, block-number vs timestamp mode selection, and the exclusive +/// interval `(validAfter, validUntil]`. +contract CheckValidationTest is Test { + uint48 constant MODE_BIT = 0x800000000000; + + function _pack(uint48 validAfter, uint48 validUntil, address res) internal pure returns (uint256) { + return uint256(validAfter) << 208 | uint256(validUntil) << 160 | uint160(res); + } + + // --- exact zero --------------------------------------------------------- + + function test_ExactZero_IsUnconditionalSuccess() public { + // Even at block.timestamp == 0, canonical EntryPoint treats packed-zero as success. + vm.warp(0); + assertTrue(Lib4337.checkValidation(0)); + } + + // --- result field ------------------------------------------------------- + + function test_NonzeroResult_Failure_IsInvalid() public { + vm.warp(1000); + assertFalse(Lib4337.checkValidation(_pack(100, 2000, address(1)))); + } + + function test_NonzeroResult_Aggregator_IsInvalid() public { + vm.warp(1000); + assertFalse(Lib4337.checkValidation(_pack(100, 2000, address(0x1234)))); + } + + // --- timestamp mode boundaries: valid iff current in (validAfter, validUntil] --- + + function test_Timestamp_CurrentEqualsValidAfter_IsInvalid() public { + vm.warp(100); + assertFalse(Lib4337.checkValidation(_pack(100, 200, address(0)))); + } + + function test_Timestamp_CurrentOneAboveValidAfter_IsValid() public { + vm.warp(101); + assertTrue(Lib4337.checkValidation(_pack(100, 200, address(0)))); + } + + function test_Timestamp_CurrentEqualsValidUntil_IsValid() public { + vm.warp(200); + assertTrue(Lib4337.checkValidation(_pack(100, 200, address(0)))); + } + + function test_Timestamp_CurrentOneAboveValidUntil_IsInvalid() public { + vm.warp(201); + assertFalse(Lib4337.checkValidation(_pack(100, 200, address(0)))); + } + + function test_Timestamp_ZeroValidUntil_IsUnbounded() public { + // Raw validUntil == 0 normalizes to type(uint48).max (no expiry). + vm.warp(uint256(type(uint48).max) - 1); + assertTrue(Lib4337.checkValidation(_pack(100, 0, address(0)))); + } + + // --- block-number mode boundaries: compared against block.number --------- + + function test_Block_UsesBlockNumberNotTimestamp() public { + // A deliberately incompatible timestamp proves block mode ignores block.timestamp. + vm.warp(uint256(type(uint48).max) + 1); + vm.roll(150); + assertTrue(Lib4337.checkValidation(_pack(MODE_BIT | 100, MODE_BIT | 200, address(0)))); + } + + function test_Block_CurrentEqualsValidAfter_IsInvalid() public { + vm.roll(100); + assertFalse(Lib4337.checkValidation(_pack(MODE_BIT | 100, MODE_BIT | 200, address(0)))); + } + + function test_Block_CurrentOneAboveValidAfter_IsValid() public { + vm.roll(101); + assertTrue(Lib4337.checkValidation(_pack(MODE_BIT | 100, MODE_BIT | 200, address(0)))); + } + + function test_Block_CurrentEqualsValidUntil_IsValid() public { + vm.roll(200); + assertTrue(Lib4337.checkValidation(_pack(MODE_BIT | 100, MODE_BIT | 200, address(0)))); + } + + function test_Block_CurrentOneAboveValidUntil_IsInvalid() public { + vm.roll(201); + assertFalse(Lib4337.checkValidation(_pack(MODE_BIT | 100, MODE_BIT | 200, address(0)))); + } + + function test_Block_ZeroValidUntil_IsUnbounded() public { + // MODE_BIT validAfter with raw zero validUntil stays block mode, unbounded upper. + vm.roll(uint256(uint48(MODE_BIT | 100) & (MODE_BIT - 1)) + 1); + assertTrue(Lib4337.checkValidation(_pack(MODE_BIT | 100, 0, address(0)))); + } + + // --- exact MODE_BIT classification (>= MODE_BIT, equality counts) -------- + + function test_ExactModeBitBound_ClassifiesAsBlockMode() public pure { + // RP-01: a bound exactly equal to MODE_BIT counts as block-number format (>= MODE_BIT), + // not just strictly greater. Both bounds must carry the flag. + assertTrue(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT)); + assertTrue(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT | 10)); + // One bound below MODE_BIT → timestamp format. + assertFalse(Lib4337._usesBlockNumberFormat(MODE_BIT - 1, MODE_BIT)); + assertFalse(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT - 1)); + } +} diff --git a/test/IntersectValidationData.t.sol b/test/IntersectValidationData.t.sol index 2f396535..ba9c4bf8 100644 --- a/test/IntersectValidationData.t.sol +++ b/test/IntersectValidationData.t.sol @@ -400,5 +400,67 @@ contract IntersectValidationDataTest is Test { assertTrue(resultAfter & MODE_BIT != 0, "Result validAfter should have MODE_BIT"); assertTrue(resultUntil & MODE_BIT != 0, "Result validUntil should have MODE_BIT"); } + + /** + * TEST CATEGORY 9: Regression — validity-format ordering (audit M-03) + * + * The format check must run AFTER `validUntil == 0 -> max` normalization, the + * aggregator failure/conflict must resolve BEFORE the format check, and a neutral + * `[0, max]` range must be exempt from the format check. + */ + + // An unbounded block range encodes validUntil = 0. Before the fix this was classified + // as timestamp format (raw validUntil = 0 lacks MODE_BIT), so pairing it with a future + // timestamp range passed the format check and then dropped the future timestamp start + // (MODE_BIT >> any real timestamp, so max() kept the block validAfter). After the fix the + // range normalizes to [MODE_BIT|block, max] (block format) and the mismatched intersection + // is rejected instead of silently producing an already-valid result. + function test_Regression_UnboundedBlockRangeVsFutureTimestampReverts() public { + uint256 unboundedBlock = createValidationData(100 | MODE_BIT, 0, address(0)); + uint256 futureTimestamp = createValidationData(2_000_000_000, 0, address(0)); + + try this.callIntersect(unboundedBlock, futureTimestamp) returns (uint256 result) { + // The dangerous pre-fix outcome: no revert AND the future start is dropped. + uint48 resultAfter = uint48(result >> 208); + assertFalse( + resultAfter == (100 | MODE_BIT), + "M-03: future timestamp start dropped by misclassified unbounded block range" + ); + fail("M-03: mismatched block/timestamp intersection must revert, not silently merge"); + } catch (bytes memory reason) { + assertEq(bytes4(reason), bytes4(keccak256("ValidityFormatMismatch()"))); + } + } + + // A signature-failure operand (aggregator == 1) has all-zero raw time bounds. Before the + // fix, pairing it with a block-format range hit the format `revert` before the aggregator + // logic ran. After the fix the failure short-circuits and returns SIG_VALIDATION_FAILED. + function test_Regression_FailureOperandDoesNotRevertOnFormatMismatch() public { + uint256 blockValid = createValidationData(100 | MODE_BIT, 200 | MODE_BIT, address(0)); + uint256 failure = createValidationData(0, 0, address(1)); + + // Must not revert; must return failure. + uint256 result = Lib4337._intersectValidationData(blockValid, failure); + assertEq(uint160(result), 1, "M-03: signature failure must be returned, not reverted"); + + // Symmetric ordering. + uint256 resultRev = Lib4337._intersectValidationData(failure, blockValid); + assertEq(uint160(resultRev), 1, "M-03: signature failure must be returned, not reverted (reverse)"); + } + + // A neutral [0, max] range carries no restriction and no format. It must intersect with a + // block-format range without a spurious ValidityFormatMismatch (its normalized validUntil + // has MODE_BIT set, which would otherwise misclassify it as block/timestamp inconsistently). + function test_Regression_NeutralRangeWithBlockRangeDoesNotRevert() public { + // Neutral range carrying only an aggregator (validAfter = validUntil = 0). + uint256 neutral = createValidationData(0, 0, BLS_AGGREGATOR); + uint256 blockRange = createValidationData(100 | MODE_BIT, 200 | MODE_BIT, address(0)); + + uint256 result = Lib4337._intersectValidationData(neutral, blockRange); + + assertEq(uint160(result), uint160(BLS_AGGREGATOR), "M-03: aggregator must survive neutral intersection"); + assertEq(uint48(result >> 208), 100 | MODE_BIT, "M-03: block validAfter must be preserved"); + assertEq(uint48(result >> 160), 200 | MODE_BIT, "M-03: block validUntil must be preserved"); + } } diff --git a/test/KernelUserOpTest.sol b/test/KernelUserOpTest.sol index d2e42d27..1729e7cc 100644 --- a/test/KernelUserOpTest.sol +++ b/test/KernelUserOpTest.sol @@ -349,6 +349,51 @@ abstract contract KernelUserOpTest is KernelTestBase { vm.stopPrank(); } + // Regression (audit H-01): enable-mode install must NOT persist when the root signature + // fails, even when validateUserOp is invoked outside the EntryPoint validation phase (during + // execution EntryPoint calls the account with arbitrary calldata and ignores the returned + // validationData). Before the fix, _processUserOp installed the package and advanced the + // nonce before the failed validationData was ever checked, so a scoped key could install + // arbitrary modules without root approval. + function test_userop_enable_failed_root_sig_does_not_install() external entryPointTest { + // newValidator is not installed yet. + assertEq(kernel.validationInfo(validatorToIdentifier(newValidator)).hook, address(0)); + + PackedUserOperation memory op = PackedUserOperation({ + sender: address(kernel), + nonce: encodeNonce(false, true, false, bytes1(0x01), bytes20(address(newValidator))), + initCode: hex"", + callData: abi.encodeWithSelector( + Kernel.execute.selector, + bytes32(0), + abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) + ), + accountGasLimits: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))), + preVerificationGas: 1000000, + gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), + paymasterAndData: hex"", + signature: hex"" + }); + // enableSuccess = false -> the root signature over the install digest is invalid. + op.signature = encodeEnableValidatorSignature( + Kernel.execute.selector, 0, false, false, _rootSignHash, _validatorSignUserOp(op, true, false) + ); + + // Simulate the execution-phase re-entry: EntryPoint calls validateUserOp and discards + // the return value. + vm.prank(address(ep)); + uint256 validationData = kernel.validateUserOp(op, bytes32(0), 0); + + // Failed root signature must surface as validation failure... + assertEq(uint160(validationData), 1, "H-01: failed root sig must return SIG_VALIDATION_FAILED"); + // ...and the module must NOT have been installed. + assertEq( + kernel.validationInfo(validatorToIdentifier(newValidator)).hook, + address(0), + "H-01: module installed despite failed root signature" + ); + } + function test_userop_validator_aa24_validation_failed() external entryPointTest { PackedUserOperation[] memory ops = new PackedUserOperation[](1); ops[0] = PackedUserOperation({ diff --git a/test/Staker.t.sol b/test/Staker.t.sol index d1d9697e..69c04b01 100644 --- a/test/Staker.t.sol +++ b/test/Staker.t.sol @@ -350,4 +350,62 @@ contract StakerTest is Test { staker.approveFactoryWithSignature(factory, false, abi.encodePacked(r, s, v)); assertEq(staker.approved(factory), false); } + + function _signApproval(address factory, bool approval, uint256 nonce) internal view returns (bytes memory) { + address addr = address(staker); + bytes32 structHash = EfficientHashLib.hash( + uint256(APPROVE_FACTORY_STRUCT_HASH), uint256(uint160(factory)), approval ? 1 : 0, nonce + ); + bytes32 digest; + string memory name = "Staker"; + string memory version = "0.0.1"; + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(0x00, _DOMAIN_TYPEHASH_SANS_CHAIN_ID) + mstore(0x20, keccak256(add(name, 0x20), mload(name))) + mstore(0x40, keccak256(add(version, 0x20), mload(version))) + mstore(0x60, addr) + mstore(0x20, keccak256(0x00, 0x80)) + mstore(0x00, 0x1901) + mstore(0x40, structHash) + digest := keccak256(0x1e, 0x42) + mstore(0x40, m) + mstore(0x60, 0) + } + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, digest); + return abi.encodePacked(r, s, v); + } + + // Regression (audit M-02): a direct owner change must advance the per-factory nonce so an + // outstanding, never-consumed signed approval cannot resurrect a revoked factory. + function test_direct_revoke_invalidates_signed_approval() external { + address factory = makeAddr("factory"); + + // Owner signs an approval at nonce 0 but never submits it. + bytes memory staleApproval = _signApproval(factory, true, 0); + assertEq(staker.nonces(factory), 0); + + // Owner revokes directly. With the fix this advances the nonce. + vm.prank(owner); + staker.approveFactory(factory, false); + assertEq(staker.approved(factory), false); + assertEq(staker.nonces(factory), 1, "M-02: direct change must advance the nonce"); + + // Replaying the stale nonce-0 signature must now fail instead of re-approving. + vm.expectRevert(InvalidSignature.selector); + staker.approveFactoryWithSignature(factory, true, staleApproval); + assertEq(staker.approved(factory), false, "M-02: revoked factory must stay revoked"); + } + + // The direct approval path must bump the nonce even when the value is unchanged. + function test_direct_approve_advances_nonce_even_when_unchanged() external { + address factory = makeAddr("factory"); + vm.startPrank(owner); + staker.approveFactory(factory, true); + assertEq(staker.nonces(factory), 1); + staker.approveFactory(factory, true); // no-op value, still advances + assertEq(staker.nonces(factory), 2); + vm.stopPrank(); + } } diff --git a/test/btt/Lib4337.t.sol b/test/btt/Lib4337.t.sol index 2ef19579..fb659b83 100644 --- a/test/btt/Lib4337.t.sol +++ b/test/btt/Lib4337.t.sol @@ -129,12 +129,12 @@ abstract contract Lib4337_Test is Test { } function test_GivenValidAfterEqualsCurrentTimestamp() external whenCallingCheckValidation { - // Boundary: validAfter == block.timestamp should pass (not strictly greater than) + // Canonical EntryPoint v0.9: validAfter is exclusive, so current == validAfter is NOT yet valid. uint256 validationData = packValidationData(uint48(_currentTimestamp), 0, address(0)); bool isValid = harness.checkValidation(validationData); - assertTrue(isValid, "should return true when validAfter equals current timestamp"); + assertFalse(isValid, "should return false when validAfter equals current timestamp"); } /*////////////////////////////////////////////////////////////// diff --git a/test/btt/Lib4337.tree b/test/btt/Lib4337.tree index f893054f..18f466fe 100644 --- a/test/btt/Lib4337.tree +++ b/test/btt/Lib4337.tree @@ -20,7 +20,7 @@ Lib4337_Test │ ├── given validation data is zero │ │ └── it should return true │ └── given validAfter equals current timestamp -│ └── it should return true +│ └── it should return false ├── when calling intersectValidationData │ ├── given preValidationData is zero │ │ └── it should return validationRes via short circuit diff --git a/test/utils/EntryPointLib.sol b/test/utils/EntryPointLib.sol index ed87e881..86068e75 100644 --- a/test/utils/EntryPointLib.sol +++ b/test/utils/EntryPointLib.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; bytes constant ENTRYPOINT_0_9_INITCODE = - hex"f62e359b3876ea3aed0db458ca0d376745a17f194044ffb6da0fd0d9ffe0dc586101806040523461019557604051610018604082610199565b600781526020810190664552433433333760c81b82526040519161003d604084610199565b600183526020830191603160f81b8352610056816101bc565b6101205261006384610357565b61014052519020918260e05251902080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526100cc60c082610199565b5190206080523060c0526040516104ee8082016001600160401b03811183821017610181578291615b36833903905ff0801561017657610160526040516156a690816104908239608051816133ce015260a0518161348b015260c0518161339f015260e0518161341d01526101005181613443015261012051816117fc0152610140518161182501526101605181818161164601528181611fdd0152818161521901526154f70152f35b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b601f909101601f19168101906001600160401b0382119082101761018157604052565b908151602081105f14610236575090601f8151116101f65760208151910151602082106101e7571790565b5f198260200360031b1b161790565b604460209160405192839163305a27a960e01b83528160048401528051918291826024860152018484015e5f828201840152601f01601f19168101030190fd5b6001600160401b03811161018157600254600181811c9116801561034d575b602082101461033957601f8111610306575b50602092601f82116001146102a557928192935f9261029a575b50508160011b915f199060031b1c19161760025560ff90565b015190505f80610281565b601f1982169360025f52805f20915f5b8681106102ee57508360019596106102d6575b505050811b0160025560ff90565b01515f1960f88460031b161c191690555f80806102c8565b919260206001819286850151815501940192016102b5565b60025f52601f60205f20910160051c810190601f830160051c015b81811061032e5750610267565b5f8155600101610321565b634e487b7160e01b5f52602260045260245ffd5b90607f1690610255565b908151602081105f14610382575090601f8151116101f65760208151910151602082106101e7571790565b6001600160401b03811161018157600354600181811c91168015610485575b602082101461033957601f8111610452575b50602092601f82116001146103f157928192935f926103e6575b50508160011b915f199060031b1c19161760035560ff90565b015190505f806103cd565b601f1982169360035f52805f20915f5b86811061043a5750836001959610610422575b505050811b0160035560ff90565b01515f1960f88460031b161c191690555f8080610414565b91926020600181928685015181550194019201610401565b60035f52601f60205f20910160051c810190601f830160051c015b81811061047a57506103b3565b5f815560010161046d565b90607f16906103a156fe6101606040526004361015610024575b3615610019575f80fd5b6100223361305c565b005b5f610140525f3560e01c806242dc53146123b257806301ffc9a7146122605780630396cb601461200157806309ccb88014611f905780630bd28e3b14611ef457806313c65a6e14611eb9578063154e58dc14611e5e5780631b2e01b814611dc8578063205c287814611c7957806322cdde4c14611bf557806335567e1a14611b3b5780635287ce1214611a1b57806370a08231146119b0578063765e827f1461190357806384b0196e146117c3578063850aaf62146116fe5780639b249f691461159a578063b0a398d11461155a578063b760faf914611520578063bb9fe6bf146113ca578063c23a5cea146111ea5763dbed18e00361000f5734610f595761012c36612af9565b6101005260e052333214806111e1575b156111b3576101405190815b60e0518110610f92575061015b82612ea2565b61012052610140516080526101405160c0525b60e05160c05110610286577fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9726101405161014051a161014051608081905290815b60e05181106101cc576101c5836101005161487b565b6101405180f35b61022e6101dc8260e05185613124565b73ffffffffffffffffffffffffffffffffffffffff6101fd602083016131b8565b167f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d6101405161014051a280613164565b9061014051915b808310610247575050506001016101af565b90919460019061027461025b888587612f71565b61026a60805161012051612fde565b51906080516141d2565b01958160805101608052019190610235565b61029560c05160e05183613124565b73ffffffffffffffffffffffffffffffffffffffff6102c360206102b98480613164565b60a05293016131b8565b61014051911691905b60a05181106102f05750505060a05160805101608052600160c0510160c05261016e565b610301816080510161012051612fde565b5161030f8260a05185612f71565b61014051915a81519273ffffffffffffffffffffffffffffffffffffffff610336826131b8565b168452602081810135908501526fffffffffffffffffffffffffffffffff6080808301358281166060880152811c604087015260a083013560c0808801919091528301359182166101008701521c61012085015261039760e08201826131d9565b9081610eb8575b5050604051936103ad82612d51565b6020850152846040526040810151946effffffffffffffffffffffffffffff8660c08401511760608401511760808401511760a084015117610100840151176101208401511711610e525750604081015160608201510160808201510160a08201510160c0820151016101008201510294856040860152845173ffffffffffffffffffffffffffffffffffffffff60e08183511692610460898d61045460408b018b6131d9565b9290916080510161515d565b0151169661014051978015610e21575b87516040810151905173ffffffffffffffffffffffffffffffffffffffff169061014051506040519a8b8960208d01519260208301937f19822f7c00000000000000000000000000000000000000000000000000000000855260248401926104d79361561c565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018d52610507908d6129d0565b61014051908c5190846101405190602095f161014051519a3d602003610e16575b60405215610d23575015610ca5575b505073ffffffffffffffffffffffffffffffffffffffff825116602083015190610140515260016020526040610140512077ffffffffffffffffffffffffffffffffffffffffffffffff8260401c165f5260205267ffffffffffffffff60405f20918254926105a584612cad565b90551603610c3c575a840311610bd35760e0015160609073ffffffffffffffffffffffffffffffffffffffff166108f0575b73ffffffffffffffffffffffffffffffffffffffff949260a08593608093606061060c9801520135905a900301910152614ecb565b929091168603610887576107b3575061063973ffffffffffffffffffffffffffffffffffffffff91614ecb565b9290911661074a5761064e57506001016102cc565b6106e15760a490604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608490604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152602060448201527f41413337207061796d617374657220696e76616c20626c6f636b2072616e67656064820152fd5b608483604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b8260849161082157604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601e60448201527f41413237206f7574736964652076616c696420626c6f636b2072616e676500006064820152fd5b608484604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b9897969594505a9883519961092473ffffffffffffffffffffffffffffffffffffffff60e08d01511660408701519061563e565b15610b6a5760807f52b7512c000000000000000000000000000000000000000000000000000000009798999a9b01516040516109a58161097960208a015160408b015190602084019d8e52896024850161561c565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018352826129d0565b8651608073ffffffffffffffffffffffffffffffffffffffff60e08301511691015161014051918b61014051928551926101405191f1983d908161014051843e51948251604084019b8c519015918215610b5e575b508115610b2e575b50610aa95750601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101160191826040525a900311610a435750946105d7565b80887f220266b6000000000000000000000000000000000000000000000000000000006084935260805101600482015260406024820152602060448201527f41413336206f76657220706d566572696669636174696f6e4761734c696d69746064820152fd5b8b610b2a610ab561335b565b6040519384937f65c8fd4d0000000000000000000000000000000000000000000000000000000085526080510160048501526024840152600d60648401527f4141333320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612b8c565b0390fd5b9050601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa084019101105f610a02565b6040141591505f6109fa565b608487604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601e60448201527f41413331207061796d6173746572206465706f73697420746f6f206c6f7700006064820152fd5b608487604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601e60448201527f41413236206f76657220766572696669636174696f6e4761734c696d697400006064820152fd5b608488604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601a60448201527f4141323520696e76616c6964206163636f756e74206e6f6e63650000000000006064820152fd5b610cae9161563e565b15610cba578b80610537565b608488604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152fd5b8b903b610d9357608490604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601960448201527f41413230206163636f756e74206e6f74206465706c6f796564000000000000006064820152fd5b610d9b61335b565b90610b2a6040519283927f65c8fd4d00000000000000000000000000000000000000000000000000000000845260805101600484015260606024840152600d60648401527f4141323320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612b8c565b610140519150610528565b6101408051849052516020819052604090205490985081811115610e4b5750610140515b97610470565b8103610e45565b80887f220266b6000000000000000000000000000000000000000000000000000000006084935260805101600482015260406024820152601860448201527f41413934206761732076616c756573206f766572666c6f7700000000000000006064820152fd5b60348210610f605781601411610f595780359160248110610f5957603411610f59576024810135608090811c60a0880152601490910135811c90860152606081901c15610f0e5760601c60e0850152898061039e565b73ffffffffffffffffffffffffffffffffffffffff907fd8ccb29200000000000000000000000000000000000000000000000000000000610140515260601c16600452602461014051fd5b6101405180fd5b507f120aaab5000000000000000000000000000000000000000000000000000000006101405152600452602461014051fd5b610f9f8160e05184613124565b92610faa8480613164565b919073ffffffffffffffffffffffffffffffffffffffff610fcd602088016131b8565b1695600187146111815786610fea575b5050019250600101610148565b806040610ff89201906131d9565b91873b15610f5957916040519283917f2dd8113300000000000000000000000000000000000000000000000000000000835286604484016040600486015252606483019160648860051b8501019281610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee182360301915b8b8210611127575050505050816110b8917ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc85809503016024850152610140519561301e565b0381610140518a5af1908161110c575b506110ff57847f86a9f750000000000000000000000000000000000000000000000000000000006101405152600452602461014051fd5b929350839260015f610fdd565b61014051611119916129d0565b61014051610f59575f6110c8565b9193967fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c90879294969703018552863584811215610f595760206111706001938583940161327a565b980195019201889695949391611072565b867f86a9f750000000000000000000000000000000000000000000000000000000006101405152600452602461014051fd5b7fab143c06000000000000000000000000000000000000000000000000000000006101405152600461014051fd5b50333b1561013c565b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f5957611221612a81565b33610140515261014051602052600160406101405120019081549165ffffffffffff6dffffffffffffffffffffffffffff8460081c169361127260ff821663ffffffff8360781c16878015156130cf565b60981c16801561139557428111611360575080547fffffffffffffff000000000000000000000000000000000000000000000000ff1690556040805173ffffffffffffffffffffffffffffffffffffffff831681526020810184905233917fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda391a2610140518080808573ffffffffffffffffffffffffffffffffffffffff86165af161131c612ce7565b9015611329576101405180f35b610b2a906040519384937f0dcf087c0000000000000000000000000000000000000000000000000000000085523360048601612d16565b7f561d331200000000000000000000000000000000000000000000000000000000610140515260045242602452604461014051fd5b7ffbd021d600000000000000000000000000000000000000000000000000000000610140515260045242602452604461014051fd5b34610f5957610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59573361014051526101405160205260016040610140512001805461145063ffffffff8260781c16918260ff6dffffffffffffffffffffffffffff8360081c1692169161144a8383838115156130cf565b826130cf565b65ffffffffffff4216019065ffffffffffff82116114ed5780547fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff001678ffffffffffff00000000000000000000000000000000000000609884901b1617905560405165ffffffffffff909116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a26101405180f35b7f4e487b710000000000000000000000000000000000000000000000000000000061014051526011600452602461014051fd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59576101c5611555612a81565b61305c565b34610f5957610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59576020610140515c604051908152f35b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595760043567ffffffffffffffff8111610f595760206115ee611629923690600401612aa4565b60405193849283927f570e1a36000000000000000000000000000000000000000000000000000000008452856004850152602484019161301e565b03816101405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165af180156116f05773ffffffffffffffffffffffffffffffffffffffff9161014051916116c1575b507f6ca7b80600000000000000000000000000000000000000000000000000000000610140515216600452602461014051fd5b6116e3915060203d6020116116e9575b6116db81836129d0565b810190612ff2565b8261168e565b503d6116d1565b6040513d61014051823e3d90fd5b34610f595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f5957611735612a81565b60243567ffffffffffffffff8111610f5957611755903690600401612aa4565b604051929181908437820190610140518252610140519280610140519303915af461177e612ce7565b90610b2a6040519283927f9941055400000000000000000000000000000000000000000000000000000000845215156004840152604060248401526044830190612b8c565b34610f5957610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59576118a16118207f0000000000000000000000000000000000000000000000000000000000000000614bcf565b6118497f0000000000000000000000000000000000000000000000000000000000000000614d45565b604051906020906118af9061185e83856129d0565b6101405184525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e0870190612b8c565b908582036040870152612b8c565b4660608501523060808501526101405160a085015283810360c0850152818084519283815201930191610140515b8281106118ec57505050500390f35b8351855286955093810193928101926001016118dd565b34610f595761191136612af9565b919091333214806119a7575b156111b35761192b83612ea2565b6119368185856135e7565b5061014051927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9728480a161014051915b858310611977576101c5858561487b565b90919360019061199d61198b878987612f71565b6119958886612fde565b5190886141d2565b0194019190611966565b50333b1561191d565b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595773ffffffffffffffffffffffffffffffffffffffff6119fc612a81565b1661014051526101405160205260206040610140512054604051908152f35b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595773ffffffffffffffffffffffffffffffffffffffff611a67612a81565b604051611a738161294e565b6101405181526101405160208201526101405160408201526101405160608201526080610140519101521661014051526101405160205260a06040610140512065ffffffffffff604051611ac68161294e565b63ffffffff60018454948584520154916dffffffffffffffffffffffffffff6020820160ff8516151581526040830190828660081c1682528660806060860195878960781c168752019660981c1686526040519788525115156020880152511660408601525116606084015251166080820152f35b34610f595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59576020611b74612a81565b73ffffffffffffffffffffffffffffffffffffffff611b91612ad2565b91166101405152600182526040610140512077ffffffffffffffffffffffffffffffffffffffffffffffff82165f52825260405f20547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595760043567ffffffffffffffff8111610f59576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610f5957611c71602091600401612d51565b604051908152f35b34610f595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f5957611cb0612a81565b602435903361014051526101405160205260406101405120828154808211611d945790611cdc91612cda565b90556040805173ffffffffffffffffffffffffffffffffffffffff831681526020810184905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2610140518080808573ffffffffffffffffffffffffffffffffffffffff86165af1611d50612ce7565b9015611d5d576101405180f35b610b2a906040519384937f9f3d69330000000000000000000000000000000000000000000000000000000085523360048601612d16565b7f25c3f46e000000000000000000000000000000000000000000000000000000006101405152600452602452604461014051fd5b34610f595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f5957611dff612a81565b73ffffffffffffffffffffffffffffffffffffffff611e1c612ad2565b91166101405152600160205277ffffffffffffffffffffffffffffffffffffffffffffffff6040610140512091165f52602052602060405f2054604051908152f35b34610f5957610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595760206040517f29a0bca4af4be3421398da00295e58e6d7de38cb492214754cb6a47507dd6f8e8152f35b34610f5957610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59576020611c71613388565b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595760043577ffffffffffffffffffffffffffffffffffffffffffffffff81168103610f5957336101405152600160205277ffffffffffffffffffffffffffffffffffffffffffffffff6040610140512091165f5260205260405f20611f878154612cad565b90556101405180f35b34610f5957610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f5957602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f595760043563ffffffff8116808203610f5957336101405152610140516020526122286dffffffffffffffffffffffffffff604061014051209361208d60018601549163ffffffff8360781c16906120848282891515612be9565b81871015612be9565b60081c16926120c561209f3486612c2a565b946120ad8134881515612c64565b346dffffffffffffffffffffffffffff871115612c64565b54604051906120d38261294e565b815265ffffffffffff602082019160018352604081016dffffffffffffffffffffffffffff87168152606082019086825260016080840193610140518552336101405152610140516020526040610140512090518155019451151560ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008754169116178555517fffffffffffffffffffffffffffffffffff0000000000000000000000000000ff6effffffffffffffffffffffffffff008087549360081b16169116178455517fffffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffff72ffffffff0000000000000000000000000000008086549360781b1616911617835551167fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffff78ffffffffffff0000000000000000000000000000000000000083549260981b169116179055565b60405191825260208201527fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c0160403392a26101405180f35b34610f595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610f59576004357fffffffff000000000000000000000000000000000000000000000000000000008116809103610f5957807fd9934b3f0000000000000000000000000000000000000000000000000000000060209214908115612388575b811561235e575b8115612334575b811561230a575b506040519015158152f35b7f01ffc9a700000000000000000000000000000000000000000000000000000000915014826122ff565b7f3e84f02100000000000000000000000000000000000000000000000000000000811491506122f8565b7fcf28ef9700000000000000000000000000000000000000000000000000000000811491506122f1565b7f283f548900000000000000000000000000000000000000000000000000000000811491506122ea565b3461280b576102007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261280b5760043567ffffffffffffffff811161280b573660238201121561280b57612413903690602481600401359101612a4b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101c0811261280b576101406040519161244f8361294e565b1261280b5760405161246081612997565b60243573ffffffffffffffffffffffffffffffffffffffff8116810361280b57815260443560208201526064356040820152608435606082015260a435608082015260c43560a082015260e43560c08201526101043573ffffffffffffffffffffffffffffffffffffffff8116810361280b5760e082015261012435610100820152610144356101208201528152602081019161016435835260408201906101843582526101a435606084015260808301916101c43583526101e43567ffffffffffffffff811161280b57612539903690600401612aa4565b955a90303303612926578651606081015195603f5a0260061c61271060a0840151890101116128fe575f9681519182612844575b505050505090612585915a9003855101963691612a4b565b925a93855161010081015161012082015148018082105f1461283c5750975b6125d173ffffffffffffffffffffffffffffffffffffffff60e08401511694518203606084015190614925565b01925f92816126e75750505173ffffffffffffffffffffffffffffffffffffffff16945b5a900301019485029051928184105f146126935750506003811015612660576002036126325760209281611c71929361262d81614a46565b614944565b7fdeadaa51000000000000000000000000000000000000000000000000000000006101405152602061014051fd5b7f4e487b710000000000000000000000000000000000000000000000000000000061014051526021600452602461014051fd5b816126c9929594969396039073ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081540180915590565b50600384101561266057826126e29260209515906149c5565b611c71565b9096918782516126fa575b5050506125f5565b90919293505a92600388101561280f5760028803612730575b505060a0612727925a900391015190614925565b908880806126f2565b60a083015191803b1561280b578b925f928361278c938c8b88604051998a98899788957f7c627b210000000000000000000000000000000000000000000000000000000087526004870152608060248701526084860190612b8c565b9202604484015260648301520393f190816127f6575b506127ec57610b2a6127b261335b565b6040519182917fad7954bc000000000000000000000000000000000000000000000000000000008352602060048401526024830190612b8c565b60a0612727612713565b5f612800916129d0565b5f610140528a6127a2565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9050976125a4565b915f9291838093602073ffffffffffffffffffffffffffffffffffffffff885116910192f115612877575b80808061256d565b612585939295506040519161288a61335b565b9081516128a3575b50505060405260019390918861286f565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a201905191602073ffffffffffffffffffffffffffffffffffffffff8551169401516128f360405192839283612bcf565b0390a3888080612892565b7fdeaddead000000000000000000000000000000000000000000000000000000005f5260205ffd5b7f9fbdaa09000000000000000000000000000000000000000000000000000000005f5260045ffd5b60a0810190811067ffffffffffffffff82111761296a57604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610140810190811067ffffffffffffffff82111761296a57604052565b6060810190811067ffffffffffffffff82111761296a57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761296a57604052565b67ffffffffffffffff811161296a57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b929192612a5782612a11565b91612a6560405193846129d0565b82948184528183011161280b578281602093845f960137010152565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361280b57565b9181601f8401121561280b5782359167ffffffffffffffff831161280b576020838186019501011161280b57565b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361280b57565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc83011261280b5760043567ffffffffffffffff811161280b5760040182601f8201121561280b5780359267ffffffffffffffff841161280b576020808301928560051b01011161280b57919060243573ffffffffffffffffffffffffffffffffffffffff8116810361280b5790565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b604090612be6939281528160208201520190612b8c565b90565b15612bf2575050565b9063ffffffff80927fe1823bce000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b91908201809211612c3757565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b15612c6d575050565b6dffffffffffffffffffffffffffff92507f0e10009c000000000000000000000000000000000000000000000000000000005f526004521660245260445ffd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114612c375760010190565b91908203918211612c3757565b3d15612d11573d90612cf882612a11565b91612d0660405193846129d0565b82523d5f602084013e565b606090565b909273ffffffffffffffffffffffffffffffffffffffff60809381612be6979616845216602083015260408201528160608201520190612b8c565b604290612d5d816134c9565b612d65613388565b91612d6f816131b8565b918015612e6d57905b60c0612d8760608301836131d9565b90816040519182372091612da7612da160e08301836131d9565b90614e15565b926040519473ffffffffffffffffffffffffffffffffffffffff60208701977f29a0bca4af4be3421398da00295e58e6d7de38cb492214754cb6a47507dd6f8e895216604087015260208301356060870152608086015260a085015260808101358285015260a081013560e085015201356101008301526101208201526101208152612e35610140826129d0565b519020604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b50612e7b60408201826131d9565b90816040519182372090612d78565b67ffffffffffffffff811161296a5760051b60200190565b90612eac82612e8a565b612eb960405191826129d0565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0612ee78294612e8a565b01905f5b828110612ef757505050565b602090604051612f068161294e565b604051612f1281612997565b5f81525f848201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201525f61012082015281525f838201525f60408201525f60608201525f608082015282828501015201612eeb565b9190811015612fb15760051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee18136030182121561280b570190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b8051821015612fb15760209160051b010190565b9081602091031261280b575173ffffffffffffffffffffffffffffffffffffffff8116810361280b5790565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b7f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4602073ffffffffffffffffffffffffffffffffffffffff6130c3348573ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081540180915590565b936040519485521692a2565b156130d957505050565b906dffffffffffffffffffffffffffff63ffffffff927f8421e8e5000000000000000000000000000000000000000000000000000000005f521660045216602452151560445260645ffd5b9190811015612fb15760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18136030182121561280b570190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561280b570180359067ffffffffffffffff821161280b57602001918160051b3603831361280b57565b3573ffffffffffffffffffffffffffffffffffffffff8116810361280b5790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561280b570180359067ffffffffffffffff821161280b5760200191813603831361280b57565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561280b57016020813591019167ffffffffffffffff821161280b57813603831361280b57565b80359173ffffffffffffffffffffffffffffffffffffffff8316830361280b5773ffffffffffffffffffffffffffffffffffffffff612be6931681526020820135602082015261334c6133406133076132ec6132d9604087018761322a565b610120604088015261012087019161301e565b6132f9606087018761322a565b90868303606088015261301e565b6080850135608085015260a085013560a085015260c085013560c085015261333260e086018661322a565b9085830360e087015261301e565b9261010081019061322a565b9161010081850391015261301e565b3d610800811161337f575b604051906020818301016040528082525f602083013e90565b50610800613366565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016301480613488575b156133f0577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a0815261348260c0826129d0565b51902090565b507f000000000000000000000000000000000000000000000000000000000000000046146133c7565b9093929384831161280b57841161280b578101920390565b6134d660408201826131d9565b90916134e28284614a96565b156135e0576134f36134f8916131b8565b614aeb565b91601482116135415750506040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000602082019260601b168252601481526134826034826129d0565b8160141161280b576020613482916040519384917fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008484019760601b16875260147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec83019101603484013781015f8382015203017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018352826129d0565b5050505f90565b92919092835f5b8181106135fb5750505050565b6136058185612fde565b51613611828486612f71565b5f915a81519273ffffffffffffffffffffffffffffffffffffffff613635826131b8565b168452602081013560208501526080810135936fffffffffffffffffffffffffffffffff8560801c951694604082019060608301968752815260c0820160a0840135815260c0840135906fffffffffffffffffffffffffffffffff8260801c921691610120850190610100860193845281526136b460e08701876131d9565b9081614115575b50506040516136c987612d51565b9960208a019a8b528160405285519586855117825117926effffffffffffffffffffffffffffff60808a01948551179560a08b0196875117895117905117116140b35750519051019051019051019051019051029560408601918783528973ffffffffffffffffffffffffffffffffffffffff60e0895161375e8b848351169561375660408d018d6131d9565b92909161515d565b015116985f99801561408c575b89516040810151905173ffffffffffffffffffffffffffffffffffffffff1680916040519d8e808d8b519360208301947f19822f7c00000000000000000000000000000000000000000000000000000000865260248401926137cc9361561c565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810182526137fc90826129d0565b51905f6020948194f15f519c3d602003614084575b60405215613f99575015613f1f575b505073ffffffffffffffffffffffffffffffffffffffff8451166020850151905f52600160205260405f2077ffffffffffffffffffffffffffffffffffffffffffffffff8260401c165f5260205267ffffffffffffffff60405f209182549261388884612cad565b90551603613eba575a860311613e555773ffffffffffffffffffffffffffffffffffffffff60e0606094015116613bbb575b505073ffffffffffffffffffffffffffffffffffffffff949260a0859360809360606138f19801520135905a900301910152614ecb565b92909116613b5657613a8a575061391c73ffffffffffffffffffffffffffffffffffffffff91614ecb565b92909116613a255761393157506001016135ee565b6139c05760a490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f41413337207061796d617374657220696e76616c20626c6f636b2072616e67656064820152fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b82608491613af457604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413237206f7574736964652076616c696420626c6f636b2072616e676500006064820152fd5b608484604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b909c9b9a99989796505a9085519d60e08f015173ffffffffffffffffffffffffffffffffffffffff168151613bef9161563e565b15613df057613c427f52b7512c00000000000000000000000000000000000000000000000000000000999a9b9c9d9e9f608001519261097960405193849251905190602084019d8e52896024850161561c565b5f8088518b82608073ffffffffffffffffffffffffffffffffffffffff60e08501511693015192865193f1983d90815f843e51948251604084019b8c519015918215613de4575b508115613db4575b50613d375750601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101160191826040525a900311613cd55750948260a06138ba565b80887f220266b60000000000000000000000000000000000000000000000000000000060849352600482015260406024820152602060448201527f41413336206f76657220706d566572696669636174696f6e4761734c696d69746064820152fd5b8b610b2a613d4361335b565b6040519384937f65c8fd4d00000000000000000000000000000000000000000000000000000000855260048501526024840152600d60648401527f4141333320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612b8c565b9050601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa084019101105f613c91565b6040141591505f613c89565b608489604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413331207061796d6173746572206465706f73697420746f6f206c6f7700006064820152fd5b608489604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413236206f76657220766572696669636174696f6e4761734c696d697400006064820152fd5b60848a604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601a60448201527f4141323520696e76616c6964206163636f756e74206e6f6e63650000000000006064820152fd5b613f289161563e565b15613f34575f80613820565b60848a604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152fd5b8d903b61400557608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601960448201527f41413230206163636f756e74206e6f74206465706c6f796564000000000000006064820152fd5b61400d61335b565b90610b2a6040519283927f65c8fd4d000000000000000000000000000000000000000000000000000000008452600484015260606024840152600d60648401527f4141323320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612b8c565b5f9150613811565b9950815f525f60205260405f20548181115f146140ac57505f5b9961376b565b81036140a6565b808f7f220266b60000000000000000000000000000000000000000000000000000000060849352600482015260406024820152601860448201527f41413934206761732076616c756573206f766572666c6f7700000000000000006064820152fd5b603482106141a6578160141161280b57803560601c916024811061280b5760148201359060341161280b576fffffffffffffffffffffffffffffffff60248193013560801c1660a089015260801c166080870152801561417b5760e08601525f806136bb565b7fd8ccb292000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b507f120aaab5000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9092915a6020820180515f5d60608301519060405196876141f660608301836131d9565b5f60038211614873575b7fffffffff00000000000000000000000000000000000000000000000000000000167f8dd7712f0000000000000000000000000000000000000000000000000000000003614705575050505f61430b6143ff6142996142cb60209587516040519384927f8dd7712f000000000000000000000000000000000000000000000000000000008a85015260406024850152606484019061327a565b906044830152037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018352826129d0565b6109796040519384927e42dc5300000000000000000000000000000000000000000000000000000000888501526102006024850152610224840190612b8c565b6143ce604484018c60806101a091610120815173ffffffffffffffffffffffffffffffffffffffff8151168652602081015160208701526040810151604087015260608101516060870152838101518487015260a081015160a087015260c081015160c087015273ffffffffffffffffffffffffffffffffffffffff60e08201511660e087015261010081015161010087015201516101208501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8382030161020484015288612b8c565b828151910182305af15f51976040521561441b575b5050505050565b909192939495505f3d6020146146f8575b7fdeaddead0000000000000000000000000000000000000000000000000000000081036144b857608486604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152600f60448201527f41413935206f7574206f662067617300000000000000000000000000000000006064820152fd5b7fdeadaa5100000000000000000000000000000000000000000000000000000000919293949550145f146145205750506145046144f9614514925a90612cda565b608084015190612c2a565b60408301518361262d8295614a46565b905b5f80808080614414565b91614591919260405190518551907ff62676f440ff169a3a9afdbf812e89e7f95975ee8e5c31214ffdef631c5f4792602073ffffffffffffffffffffffffffffffffffffffff84511693015161457461335b565b9061458460405192839283612bcf565b0390a36040525a90612cda565b6145a16080840191825190612c2a565b915f905a92855161010081015161012082015148018082105f146146f05750955b6145ef73ffffffffffffffffffffffffffffffffffffffff60e08401511693518203606084015190614925565b01925f92806146c15750505173ffffffffffffffffffffffffffffffffffffffff16935b5a900301019283026040850151928184105f146146755750508061464857509081614642929361262d81614a46565b90614516565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526021600452fd5b6146aa908284939795039073ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081540180915590565b50614648575090825f6146bc936149c5565b614642565b959190516146d0575b50614613565b935090506146e95a9360a05f955a900391015190614925565b905f6146ca565b9050956145c2565b5060205f803e5f5161442c565b61486a935061483e9161474a917e42dc53000000000000000000000000000000000000000000000000000000006020860152610200602486015261022485019161301e565b61480d604484018960806101a091610120815173ffffffffffffffffffffffffffffffffffffffff8151168652602081015160208701526040810151604087015260608101516060870152838101518487015260a081015160a087015260c081015160c087015273ffffffffffffffffffffffffffffffffffffffff60e08201511660e087015261010081015161010087015201516101208501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8382030161020484015285612b8c565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018952886129d0565b60205f886143ff565b508135614200565b73ffffffffffffffffffffffffffffffffffffffff1680156148fa575f80808085855af16148a7612ce7565b90156148b257505050565b610b2a906040519384937f40848e6100000000000000000000000000000000000000000000000000000000855260048501526024840152606060448401526064830190612b8c565b7f1a3b45fd000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90619c40820181111561493e57606491600a9103020490565b50505f90565b9190917f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f6080602083015192519473ffffffffffffffffffffffffffffffffffffffff86511694602073ffffffffffffffffffffffffffffffffffffffff60e089015116970151916040519283525f602084015260408301526060820152a4565b9060807f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f91602084015193519573ffffffffffffffffffffffffffffffffffffffff87511695602073ffffffffffffffffffffffffffffffffffffffff60e08a015116980151926040519384521515602084015260408301526060820152a4565b60208101519051907f67b4fa9642f42120bf031f3051d1824b0fe25627945b27b8a6a65d5761d5482e60208073ffffffffffffffffffffffffffffffffffffffff855116940151604051908152a3565b90600211614ae657357fffffffffffffffffffffffffffffffffffffffff000000000000000000000000167f77020000000000000000000000000000000000000000000000000000000000001490565b505f90565b60175f80833c5f51907fef010000000000000000000000000000000000000000000000000000000000007fffffff0000000000000000000000000000000000000000000000000000000000831603614b5a575060481c73ffffffffffffffffffffffffffffffffffffffff1690565b8073ffffffffffffffffffffffffffffffffffffffff913b15614ba3577f9f4e4cc9000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b7fe5819b95000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b60ff8114614c2e5760ff811690601f8211614c065760405191614bf36040846129d0565b6020808452838101919036833783525290565b7fb3512b0c000000000000000000000000000000000000000000000000000000005f5260045ffd5b506040515f6002548060011c9160018216918215614d3b575b602084108314614d0e578385528492908115614cd15750600114614c72575b612be6925003826129d0565b5060025f90815290917f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b818310614cb5575050906020612be692820101614c66565b6020919350806001915483858801015201910190918392614c9d565b60209250612be69491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b820101614c66565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b92607f1692614c47565b60ff8114614d695760ff811690601f8211614c065760405191614bf36040846129d0565b506040515f6003548060011c9160018216918215614e0b575b602084108314614d0e578385528492908115614cd15750600114614dac57612be6925003826129d0565b5060035f90815290917fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b818310614def575050906020612be692820101614c66565b6020919350806001915483858801015201910190918392614dd7565b92607f1692614d82565b614e1f8282614fc5565b80614e305750816040519182372090565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe919203604051927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff682019084377f22e325a2974396560000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6828501015201902090565b8015614fbc575f60408051614edf816129b4565b828152826020820152015273ffffffffffffffffffffffffffffffffffffffff81169065ffffffffffff8160a01c16908115614fae575b60409060d01c918151614f28816129b4565b84815283602082015265ffffffffffff8216928391015265800000000000831180614f9f575b15614f8257657fffffffffff9150164311908115614f6f575b509060019092565b657fffffffffff9150164311155f614f67565b504211908115614f94575b50905f9092565b90504211155f614f8d565b50658000000000008211614f4e565b65ffffffffffff9150614f16565b505f905f905f90565b603e821061493e577f22e325a2974396560000000000000000000000000000000000000000000000007fffffffffffffffff00000000000000000000000000000000000000000000000061503d847ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8810181866134b1565b90358281169160088110615148575b5050160361493e578161508391817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff68101916134b1565b90357fffff00000000000000000000000000000000000000000000000000000000000081169160028110615113575b505060f01c907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc2810182116150e5575090565b7f07b9a191000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7fffff0000000000000000000000000000000000000000000000000000000000009250829060020360031b1b16165f806150b2565b839250829060080360031b1b16165f8061504c565b929091925f8261516e575050505050565b83519473ffffffffffffffffffffffffffffffffffffffff865116956151948583614a96565b6154c6575060148410615461578360141161545d57803560601c93863b615427576152009160209160408851015190856040518096819582947f570e1a36000000000000000000000000000000000000000000000000000000008452886004850152602484019161301e565b039273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690f191821561541b57916153fc575b5073ffffffffffffffffffffffffffffffffffffffff81168015615397578503615332573b156152cd575060407fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d9173ffffffffffffffffffffffffffffffffffffffff60e06020860151955101511682519182526020820152a35f80808080614414565b608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152fd5b608482604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313420696e6974436f6465206d7573742072657475726e2073656e6465726064820152fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601b60448201527f4141313320696e6974436f6465206661696c6564206f72204f4f4700000000006064820152fd5b615415915060203d6020116116e9576116db81836129d0565b5f615248565b604051903d90823e3d90fd5b50505050906020807fa39bcda08ffd11bafb11c4f170ef24fc6dc1a9d1b0394d90dbd19e0b919050e992015192604051908152a3565b5080fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f4141393920696e6974436f646520746f6f20736d616c6c0000000000000000006064820152fd5b91959493909250601481116154de575b505050505050565b604073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169201518160141161280b57823b1561280b57615599935f80946040518097819682957fc09ad0d90000000000000000000000000000000000000000000000000000000084528c60048501526040602485015260147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec604486019301910161301e565b0393f18015615611576155fc575b507f7c9f9ade6a03a0bba484e52df872467a270e798ffc1adab9dfaa8d0e627f054473ffffffffffffffffffffffffffffffffffffffff60206155e985614aeb565b93015192169380a45f80808080806154d6565b6156099193505f906129d0565b5f915f6155a7565b6040513d5f823e3d90fd5b6156346040929594939560608352606083019061327a565b9460208201520152565b73ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081548181106135e05703905560019056fea2646970667358221220583ee7261cae36e6617e47c8a79c1de63aa871da28a288e87a6d3163d57f12df64736f6c634300081c003360a08060405234602f57336080526104ba9081610034823960805181818160c30152818161023701526102cf0152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063570e1a361461025b578063b0d691fe146101ed5763c09ad0d91461003a575f80fd5b346101e95760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e95760043573ffffffffffffffffffffffffffffffffffffffff811681036101e95760243567ffffffffffffffff81116101e957366023820112156101e9575f916100bd8392369060248160040135910161038a565b906101027f0000000000000000000000000000000000000000000000000000000000000000303373ffffffffffffffffffffffffffffffffffffffff8316331461042c565b82602083519301915af11561011357005b3d61080081116101e0575b60c460405160208382010160405282815260208101925f843e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6040519485937f65c8fd4d0000000000000000000000000000000000000000000000000000000085525f6004860152606060248601528260648601527f4141313320454950373730322073656e64657220696e6974206661696c656400608486015260a060448601525180918160a48701528686015e5f85828601015201168101030190fd5b5061080061011e565b5f80fd5b346101e9575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e957602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101e95760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e95760043567ffffffffffffffff81116101e957366023820112156101e95780600401359067ffffffffffffffff82116101e95736602483830101116101e9575f9161030e7f0000000000000000000000000000000000000000000000000000000000000000303373ffffffffffffffffffffffffffffffffffffffff8316331461042c565b806014116101e95760209161034b5f927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec3691016038840161038a565b90826024858451940192013560601c5af1610382575b60209073ffffffffffffffffffffffffffffffffffffffff60405191168152f35b505f51610361565b92919267ffffffffffffffff82116103ff57604051917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81601f8401160116830183811067ffffffffffffffff8211176103ff576040528294818452818301116101e9578281602093845f960137010152565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b1561043657505050565b73ffffffffffffffffffffffffffffffffffffffff92918380927ffe34a6d3000000000000000000000000000000000000000000000000000000005f5216600452166024521660445260645ffdfea26469706673582212209f4b5fb9b995829222b126c981ed4f159f25b877b806551c47ef8dafebccd94364736f6c634300081c0033"; -address constant ENTRYPOINT_0_9 = 0x43370900c8de573dB349BEd8DD53b4Ebd3Cce709; + hex"7702864008ddeab30aa67b7adc3d2653bc8d162714b1fe8fe4582df814f3bf616101806040523461019557604051610018604082610199565b600781526020810190664552433433333760c81b82526040519161003d604084610199565b600183526020830191603160f81b8352610056816101bc565b6101205261006384610357565b61014052519020918260e05251902080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526100cc60c082610199565b5190206080523060c0526040516104ee8082016001600160401b03811183821017610181578291615c29833903905ff0801561017657610160526040516157999081610490823960805181613447015260a05181613504015260c05181613418015260e05181613496015261010051816134bc015261012051816118750152610140518161189e0152610160518181816116bf015281816120560152818161530c01526155ea0152f35b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b601f909101601f19168101906001600160401b0382119082101761018157604052565b908151602081105f14610236575090601f8151116101f65760208151910151602082106101e7571790565b5f198260200360031b1b161790565b604460209160405192839163305a27a960e01b83528160048401528051918291826024860152018484015e5f828201840152601f01601f19168101030190fd5b6001600160401b03811161018157600254600181811c9116801561034d575b602082101461033957601f8111610306575b50602092601f82116001146102a557928192935f9261029a575b50508160011b915f199060031b1c19161760025560ff90565b015190505f80610281565b601f1982169360025f52805f20915f5b8681106102ee57508360019596106102d6575b505050811b0160025560ff90565b01515f1960f88460031b161c191690555f80806102c8565b919260206001819286850151815501940192016102b5565b60025f52601f60205f20910160051c810190601f830160051c015b81811061032e5750610267565b5f8155600101610321565b634e487b7160e01b5f52602260045260245ffd5b90607f1690610255565b908151602081105f14610382575090601f8151116101f65760208151910151602082106101e7571790565b6001600160401b03811161018157600354600181811c91168015610485575b602082101461033957601f8111610452575b50602092601f82116001146103f157928192935f926103e6575b50508160011b915f199060031b1c19161760035560ff90565b015190505f806103cd565b601f1982169360035f52805f20915f5b86811061043a5750836001959610610422575b505050811b0160035560ff90565b01515f1960f88460031b161c191690555f8080610414565b91926020600181928685015181550194019201610401565b60035f52601f60205f20910160051c810190601f830160051c015b81811061047a57506103b3565b5f815560010161046d565b90607f16906103a156fe6101606040526004361015610024575b3615610019575f80fd5b610022336130d5565b005b5f610140525f3560e01c806242dc531461242b57806301ffc9a7146122d95780630396cb601461207a57806309ccb880146120095780630bd28e3b14611f6d57806313c65a6e14611f32578063154e58dc14611ed75780631b2e01b814611e41578063205c287814611cf257806322cdde4c14611c6e57806335567e1a14611bb45780635287ce1214611a9457806370a0823114611a29578063765e827f1461197c57806384b0196e1461183c578063850aaf62146117775780639b249f6914611613578063b0a398d1146115d3578063b760faf914611599578063bb9fe6bf14611443578063c23a5cea146112635763dbed18e00361000f5734610fd25761012c36612b72565b6101005260e0523332148061125a575b1561122c576101405190815b60e051811061100b575061015b82612f1b565b61012052610140516080526101405160c0525b60e05160c05110610286577fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9726101405161014051a161014051608081905290815b60e05181106101cc576101c58361010051614969565b6101405180f35b61022e6101dc8260e0518561319d565b73ffffffffffffffffffffffffffffffffffffffff6101fd60208301613231565b167f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d6101405161014051a2806131dd565b9061014051915b808310610247575050506001016101af565b90919460019061027461025b888587612fea565b61026a60805161012051613057565b51906080516142c0565b01958160805101608052019190610235565b61029560c05160e0518361319d565b73ffffffffffffffffffffffffffffffffffffffff6102c360206102b984806131dd565b60a0529301613231565b61014051911691905b60a05181106102f05750505060a05160805101608052600160c0510160c05261016e565b610301816080510161012051613057565b5161030f8260a05185612fea565b61014051915a81519273ffffffffffffffffffffffffffffffffffffffff61033682613231565b168452602081810135908501526fffffffffffffffffffffffffffffffff6080808301358281166060880152811c604087015260a083013560c0808801919091528301359182166101008701521c61012085015261039760e0820182613252565b9081610f31575b5050604051936103ad82612dca565b6020850152846040526040810151946effffffffffffffffffffffffffffff8660c08401511760608401511760808401511760a084015117610100840151176101208401511711610ecb5750604081015160608201510160808201510160a08201510160c0820151016101008201510294856040860152845173ffffffffffffffffffffffffffffffffffffffff60e08183511692610460898d61045460408b018b613252565b92909160805101615250565b0151169661014051978015610e9a575b87516040810151905173ffffffffffffffffffffffffffffffffffffffff169061014051506040519a8b8960208d01519260208301937f19822f7c00000000000000000000000000000000000000000000000000000000855260248401926104d79361570f565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018d52610507908d612a49565b61014051908c5190846101405190602095f161014051519a3d602003610e8f575b60405215610d9c575015610d1e575b505073ffffffffffffffffffffffffffffffffffffffff825116602083015190610140515260016020526040610140512077ffffffffffffffffffffffffffffffffffffffffffffffff8260401c165f5260205267ffffffffffffffff60405f20918254926105a584612d26565b90551603610cb5575a840311610c4c5760e0015160609073ffffffffffffffffffffffffffffffffffffffff166108f0575b73ffffffffffffffffffffffffffffffffffffffff949260a08593608093606061060c9801520135905a900301910152614fbc565b929091168603610887576107b3575061063973ffffffffffffffffffffffffffffffffffffffff91614fbc565b9290911661074a5761064e57506001016102cc565b6106e15760a490604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608490604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152602060448201527f41413337207061796d617374657220696e76616c20626c6f636b2072616e67656064820152fd5b608483604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b8260849161082157604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601e60448201527f41413237206f7574736964652076616c696420626c6f636b2072616e676500006064820152fd5b608484604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b9897969594505a9883519961092473ffffffffffffffffffffffffffffffffffffffff60e08d015116604087015190615731565b15610be35760807f52b7512c000000000000000000000000000000000000000000000000000000009798999a9b01516040516109a58161097960208a015160408b015190602084019d8e52896024850161570f565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282612a49565b8651608073ffffffffffffffffffffffffffffffffffffffff60e08301511691015161014051918b61014051928551926101405191f1983d908161014051843e519482519a604084019b8c519115610b615760401490811591610b2f575b50610aaa5750601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101160191826040525a900311610a445750946105d7565b80887f220266b6000000000000000000000000000000000000000000000000000000006084935260805101600482015260406024820152602060448201527f41413336206f76657220706d566572696669636174696f6e4761734c696d69746064820152fd5b8b610b2b610ab66133d4565b6040519384937f65c8fd4d0000000000000000000000000000000000000000000000000000000085526080510160048501526024840152601d60648401527f41413335206d616c666f726d6564207061796d61737465722064617461000000608484015260a0604484015260a4830190612c05565b0390fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09150601f011681018214155f610a03565b828e610b2b610b6e6133d4565b6040519384937f65c8fd4d0000000000000000000000000000000000000000000000000000000085526080510160048501526024840152600d60648401527f4141333320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612c05565b608487604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601e60448201527f41413331207061796d6173746572206465706f73697420746f6f206c6f7700006064820152fd5b608487604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601e60448201527f41413236206f76657220766572696669636174696f6e4761734c696d697400006064820152fd5b608488604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601a60448201527f4141323520696e76616c6964206163636f756e74206e6f6e63650000000000006064820152fd5b610d2791615731565b15610d33578b80610537565b608488604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152fd5b8b903b610e0c57608490604051907f220266b600000000000000000000000000000000000000000000000000000000825260805101600482015260406024820152601960448201527f41413230206163636f756e74206e6f74206465706c6f796564000000000000006064820152fd5b610e146133d4565b90610b2b6040519283927f65c8fd4d00000000000000000000000000000000000000000000000000000000845260805101600484015260606024840152600d60648401527f4141323320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612c05565b610140519150610528565b6101408051849052516020819052604090205490985081811115610ec45750610140515b97610470565b8103610ebe565b80887f220266b6000000000000000000000000000000000000000000000000000000006084935260805101600482015260406024820152601860448201527f41413934206761732076616c756573206f766572666c6f7700000000000000006064820152fd5b60348210610fd95781601411610fd25780359160248110610fd257603411610fd2576024810135608090811c60a0880152601490910135811c90860152606081901c15610f875760601c60e0850152898061039e565b73ffffffffffffffffffffffffffffffffffffffff907fd8ccb29200000000000000000000000000000000000000000000000000000000610140515260601c16600452602461014051fd5b6101405180fd5b507f120aaab5000000000000000000000000000000000000000000000000000000006101405152600452602461014051fd5b6110188160e0518461319d565b9261102384806131dd565b919073ffffffffffffffffffffffffffffffffffffffff61104660208801613231565b1695600187146111fa5786611063575b5050019250600101610148565b806040611071920190613252565b91873b15610fd257916040519283917f2dd8113300000000000000000000000000000000000000000000000000000000835286604484016040600486015252606483019160648860051b8501019281610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee182360301915b8b82106111a057505050505081611131917ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc858095030160248501526101405195613097565b0381610140518a5af19081611185575b5061117857847f86a9f750000000000000000000000000000000000000000000000000000000006101405152600452602461014051fd5b929350839260015f611056565b6101405161119291612a49565b61014051610fd2575f611141565b9193967fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c90879294969703018552863584811215610fd25760206111e9600193858394016132f3565b9801950192018896959493916110eb565b867f86a9f750000000000000000000000000000000000000000000000000000000006101405152600452602461014051fd5b7fab143c06000000000000000000000000000000000000000000000000000000006101405152600461014051fd5b50333b1561013c565b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25761129a612afa565b33610140515261014051602052600160406101405120019081549165ffffffffffff6dffffffffffffffffffffffffffff8460081c16936112eb60ff821663ffffffff8360781c1687801515613148565b60981c16801561140e574281116113d9575080547fffffffffffffff000000000000000000000000000000000000000000000000ff1690556040805173ffffffffffffffffffffffffffffffffffffffff831681526020810184905233917fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda391a2610140518080808573ffffffffffffffffffffffffffffffffffffffff86165af1611395612d60565b90156113a2576101405180f35b610b2b906040519384937f0dcf087c0000000000000000000000000000000000000000000000000000000085523360048601612d8f565b7f561d331200000000000000000000000000000000000000000000000000000000610140515260045242602452604461014051fd5b7ffbd021d600000000000000000000000000000000000000000000000000000000610140515260045242602452604461014051fd5b34610fd257610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd257336101405152610140516020526001604061014051200180546114c963ffffffff8260781c16918260ff6dffffffffffffffffffffffffffff8360081c169216916114c3838383811515613148565b82613148565b65ffffffffffff4216019065ffffffffffff82116115665780547fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff001678ffffffffffff00000000000000000000000000000000000000609884901b1617905560405165ffffffffffff909116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a26101405180f35b7f4e487b710000000000000000000000000000000000000000000000000000000061014051526011600452602461014051fd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd2576101c56115ce612afa565b6130d5565b34610fd257610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd2576020610140515c604051908152f35b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25760043567ffffffffffffffff8111610fd25760206116676116a2923690600401612b1d565b60405193849283927f570e1a360000000000000000000000000000000000000000000000000000000084528560048501526024840191613097565b03816101405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165af180156117695773ffffffffffffffffffffffffffffffffffffffff91610140519161173a575b507f6ca7b80600000000000000000000000000000000000000000000000000000000610140515216600452602461014051fd5b61175c915060203d602011611762575b6117548183612a49565b81019061306b565b82611707565b503d61174a565b6040513d61014051823e3d90fd5b34610fd25760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd2576117ae612afa565b60243567ffffffffffffffff8111610fd2576117ce903690600401612b1d565b604051929181908437820190610140518252610140519280610140519303915af46117f7612d60565b90610b2b6040519283927f9941055400000000000000000000000000000000000000000000000000000000845215156004840152604060248401526044830190612c05565b34610fd257610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25761191a6118997f0000000000000000000000000000000000000000000000000000000000000000614cc0565b6118c27f0000000000000000000000000000000000000000000000000000000000000000614e36565b60405190602090611928906118d78385612a49565b6101405184525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e0870190612c05565b908582036040870152612c05565b4660608501523060808501526101405160a085015283810360c0850152818084519283815201930191610140515b82811061196557505050500390f35b835185528695509381019392810192600101611956565b34610fd25761198a36612b72565b91909133321480611a20575b1561122c576119a483612f1b565b6119af818585613660565b5061014051927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9728480a161014051915b8583106119f0576101c58585614969565b909193600190611a16611a04878987612fea565b611a0e8886613057565b5190886142c0565b01940191906119df565b50333b15611996565b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25773ffffffffffffffffffffffffffffffffffffffff611a75612afa565b1661014051526101405160205260206040610140512054604051908152f35b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25773ffffffffffffffffffffffffffffffffffffffff611ae0612afa565b604051611aec816129c7565b6101405181526101405160208201526101405160408201526101405160608201526080610140519101521661014051526101405160205260a06040610140512065ffffffffffff604051611b3f816129c7565b63ffffffff60018454948584520154916dffffffffffffffffffffffffffff6020820160ff8516151581526040830190828660081c1682528660806060860195878960781c168752019660981c1686526040519788525115156020880152511660408601525116606084015251166080820152f35b34610fd25760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd2576020611bed612afa565b73ffffffffffffffffffffffffffffffffffffffff611c0a612b4b565b91166101405152600182526040610140512077ffffffffffffffffffffffffffffffffffffffffffffffff82165f52825260405f20547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25760043567ffffffffffffffff8111610fd2576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610fd257611cea602091600401612dca565b604051908152f35b34610fd25760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd257611d29612afa565b602435903361014051526101405160205260406101405120828154808211611e0d5790611d5591612d53565b90556040805173ffffffffffffffffffffffffffffffffffffffff831681526020810184905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2610140518080808573ffffffffffffffffffffffffffffffffffffffff86165af1611dc9612d60565b9015611dd6576101405180f35b610b2b906040519384937f9f3d69330000000000000000000000000000000000000000000000000000000085523360048601612d8f565b7f25c3f46e000000000000000000000000000000000000000000000000000000006101405152600452602452604461014051fd5b34610fd25760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd257611e78612afa565b73ffffffffffffffffffffffffffffffffffffffff611e95612b4b565b91166101405152600160205277ffffffffffffffffffffffffffffffffffffffffffffffff6040610140512091165f52602052602060405f2054604051908152f35b34610fd257610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25760206040517f29a0bca4af4be3421398da00295e58e6d7de38cb492214754cb6a47507dd6f8e8152f35b34610fd257610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd2576020611cea613401565b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25760043577ffffffffffffffffffffffffffffffffffffffffffffffff81168103610fd257336101405152600160205277ffffffffffffffffffffffffffffffffffffffffffffffff6040610140512091165f5260205260405f206120008154612d26565b90556101405180f35b34610fd257610140517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd257602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd25760043563ffffffff8116808203610fd257336101405152610140516020526122a16dffffffffffffffffffffffffffff604061014051209361210660018601549163ffffffff8360781c16906120fd8282891515612c62565b81871015612c62565b60081c169261213e6121183486612ca3565b946121268134881515612cdd565b346dffffffffffffffffffffffffffff871115612cdd565b546040519061214c826129c7565b815265ffffffffffff602082019160018352604081016dffffffffffffffffffffffffffff87168152606082019086825260016080840193610140518552336101405152610140516020526040610140512090518155019451151560ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008754169116178555517fffffffffffffffffffffffffffffffffff0000000000000000000000000000ff6effffffffffffffffffffffffffff008087549360081b16169116178455517fffffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffff72ffffffff0000000000000000000000000000008086549360781b1616911617835551167fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffff78ffffffffffff0000000000000000000000000000000000000083549260981b169116179055565b60405191825260208201527fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c0160403392a26101405180f35b34610fd25760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610fd2576004357fffffffff000000000000000000000000000000000000000000000000000000008116809103610fd257807fd9934b3f0000000000000000000000000000000000000000000000000000000060209214908115612401575b81156123d7575b81156123ad575b8115612383575b506040519015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501482612378565b7f3e84f0210000000000000000000000000000000000000000000000000000000081149150612371565b7fcf28ef97000000000000000000000000000000000000000000000000000000008114915061236a565b7f283f54890000000000000000000000000000000000000000000000000000000081149150612363565b34612884576102007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126128845760043567ffffffffffffffff811161288457366023820112156128845761248c903690602481600401359101612ac4565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101c0811261288457610140604051916124c8836129c7565b12612884576040516124d981612a10565b60243573ffffffffffffffffffffffffffffffffffffffff8116810361288457815260443560208201526064356040820152608435606082015260a435608082015260c43560a082015260e43560c08201526101043573ffffffffffffffffffffffffffffffffffffffff811681036128845760e082015261012435610100820152610144356101208201528152602081019161016435835260408201906101843582526101a435606084015260808301916101c43583526101e43567ffffffffffffffff8111612884576125b2903690600401612b1d565b955a9030330361299f578651606081015195603f5a0260061c61271060a084015189010111612977575f96815191826128bd575b5050505050906125fe915a9003855101963691612ac4565b925a93855161010081015161012082015148018082105f146128b55750975b61264a73ffffffffffffffffffffffffffffffffffffffff60e08401511694518203606084015190614a16565b01925f92816127605750505173ffffffffffffffffffffffffffffffffffffffff16945b5a900301019485029051928184105f1461270c57505060038110156126d9576002036126ab5760209281611cea92936126a681614b37565b614a35565b7fdeadaa51000000000000000000000000000000000000000000000000000000006101405152602061014051fd5b7f4e487b710000000000000000000000000000000000000000000000000000000061014051526021600452602461014051fd5b81612742929594969396039073ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081540180915590565b5060038410156126d9578261275b926020951590614ab6565b611cea565b909691878251612773575b50505061266e565b90919293505a92600388101561288857600288036127a9575b505060a06127a0925a900391015190614a16565b9088808061276b565b60a083015191803b15612884578b925f9283612805938c8b88604051998a98899788957f7c627b210000000000000000000000000000000000000000000000000000000087526004870152608060248701526084860190612c05565b9202604484015260648301520393f1908161286f575b5061286557610b2b61282b6133d4565b6040519182917fad7954bc000000000000000000000000000000000000000000000000000000008352602060048401526024830190612c05565b60a06127a061278c565b5f61287991612a49565b5f610140528a61281b565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b90509761261d565b915f9291838093602073ffffffffffffffffffffffffffffffffffffffff885116910192f1156128f0575b8080806125e6565b6125fe93929550604051916129036133d4565b90815161291c575b5050506040526001939091886128e8565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a201905191602073ffffffffffffffffffffffffffffffffffffffff85511694015161296c60405192839283612c48565b0390a388808061290b565b7fdeaddead000000000000000000000000000000000000000000000000000000005f5260205ffd5b7f9fbdaa09000000000000000000000000000000000000000000000000000000005f5260045ffd5b60a0810190811067ffffffffffffffff8211176129e357604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610140810190811067ffffffffffffffff8211176129e357604052565b6060810190811067ffffffffffffffff8211176129e357604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176129e357604052565b67ffffffffffffffff81116129e357601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b929192612ad082612a8a565b91612ade6040519384612a49565b829481845281830111612884578281602093845f960137010152565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361288457565b9181601f840112156128845782359167ffffffffffffffff8311612884576020838186019501011161288457565b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361288457565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126128845760043567ffffffffffffffff81116128845760040182601f820112156128845780359267ffffffffffffffff8411612884576020808301928560051b01011161288457919060243573ffffffffffffffffffffffffffffffffffffffff811681036128845790565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b604090612c5f939281528160208201520190612c05565b90565b15612c6b575050565b9063ffffffff80927fe1823bce000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b91908201809211612cb057565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b15612ce6575050565b6dffffffffffffffffffffffffffff92507f0e10009c000000000000000000000000000000000000000000000000000000005f526004521660245260445ffd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114612cb05760010190565b91908203918211612cb057565b3d15612d8a573d90612d7182612a8a565b91612d7f6040519384612a49565b82523d5f602084013e565b606090565b909273ffffffffffffffffffffffffffffffffffffffff60809381612c5f979616845216602083015260408201528160608201520190612c05565b604290612dd681613542565b612dde613401565b91612de881613231565b918015612ee657905b60c0612e006060830183613252565b90816040519182372091612e20612e1a60e0830183613252565b90614f06565b926040519473ffffffffffffffffffffffffffffffffffffffff60208701977f29a0bca4af4be3421398da00295e58e6d7de38cb492214754cb6a47507dd6f8e895216604087015260208301356060870152608086015260a085015260808101358285015260a081013560e085015201356101008301526101208201526101208152612eae61014082612a49565b519020604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b50612ef46040820182613252565b90816040519182372090612df1565b67ffffffffffffffff81116129e35760051b60200190565b90612f2582612f03565b612f326040519182612a49565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0612f608294612f03565b01905f5b828110612f7057505050565b602090604051612f7f816129c7565b604051612f8b81612a10565b5f81525f848201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201525f61012082015281525f838201525f60408201525f60608201525f608082015282828501015201612f64565b919081101561302a5760051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee181360301821215612884570190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b805182101561302a5760209160051b010190565b90816020910312612884575173ffffffffffffffffffffffffffffffffffffffff811681036128845790565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b7f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4602073ffffffffffffffffffffffffffffffffffffffff61313c348573ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081540180915590565b936040519485521692a2565b1561315257505050565b906dffffffffffffffffffffffffffff63ffffffff927f8421e8e5000000000000000000000000000000000000000000000000000000005f521660045216602452151560445260645ffd5b919081101561302a5760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa181360301821215612884570190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215612884570180359067ffffffffffffffff821161288457602001918160051b3603831361288457565b3573ffffffffffffffffffffffffffffffffffffffff811681036128845790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215612884570180359067ffffffffffffffff82116128845760200191813603831361288457565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561288457016020813591019167ffffffffffffffff821161288457813603831361288457565b80359173ffffffffffffffffffffffffffffffffffffffff831683036128845773ffffffffffffffffffffffffffffffffffffffff612c5f93168152602082013560208201526133c56133b961338061336561335260408701876132a3565b6101206040880152610120870191613097565b61337260608701876132a3565b908683036060880152613097565b6080850135608085015260a085013560a085015260c085013560c08501526133ab60e08601866132a3565b9085830360e0870152613097565b926101008101906132a3565b91610100818503910152613097565b3d61080081116133f8575b604051906020818301016040528082525f602083013e90565b506108006133df565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016301480613501575b15613469577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a081526134fb60c082612a49565b51902090565b507f00000000000000000000000000000000000000000000000000000000000000004614613440565b90939293848311612884578411612884578101920390565b61354f6040820182613252565b909161355b8284614b87565b156136595761356c61357191613231565b614bdc565b91601482116135ba5750506040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000602082019260601b168252601481526134fb603482612a49565b816014116128845760206134fb916040519384917fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008484019760601b16875260147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec83019101603484013781015f8382015203017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282612a49565b5050505f90565b92919092835f5b8181106136745750505050565b61367e8185613057565b5161368a828486612fea565b5f915a81519273ffffffffffffffffffffffffffffffffffffffff6136ae82613231565b168452602081013560208501526080810135936fffffffffffffffffffffffffffffffff8560801c951694604082019060608301968752815260c0820160a0840135815260c0840135906fffffffffffffffffffffffffffffffff8260801c9216916101208501906101008601938452815261372d60e0870187613252565b9081614203575b505060405161374287612dca565b9960208a019a8b528160405285519586855117825117926effffffffffffffffffffffffffffff60808a01948551179560a08b0196875117895117905117116141a15750519051019051019051019051019051029560408601918783528973ffffffffffffffffffffffffffffffffffffffff60e089516137d78b84835116956137cf60408d018d613252565b929091615250565b015116985f99801561417a575b89516040810151905173ffffffffffffffffffffffffffffffffffffffff1680916040519d8e808d8b519360208301947f19822f7c00000000000000000000000000000000000000000000000000000000865260248401926138459361570f565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810182526138759082612a49565b51905f6020948194f15f519c3d602003614172575b6040521561408757501561400d575b505073ffffffffffffffffffffffffffffffffffffffff8451166020850151905f52600160205260405f2077ffffffffffffffffffffffffffffffffffffffffffffffff8260401c165f5260205267ffffffffffffffff60405f209182549261390184612d26565b90551603613fa8575a860311613f435773ffffffffffffffffffffffffffffffffffffffff60e0606094015116613c34575b505073ffffffffffffffffffffffffffffffffffffffff949260a08593608093606061396a9801520135905a900301910152614fbc565b92909116613bcf57613b03575061399573ffffffffffffffffffffffffffffffffffffffff91614fbc565b92909116613a9e576139aa5750600101613667565b613a395760a490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f41413337207061796d617374657220696e76616c20626c6f636b2072616e67656064820152fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b82608491613b6d57604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413237206f7574736964652076616c696420626c6f636b2072616e676500006064820152fd5b608484604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b909c9b9a99989796505a9085519d60e08f015173ffffffffffffffffffffffffffffffffffffffff168151613c6891615731565b15613ede57613cbb7f52b7512c00000000000000000000000000000000000000000000000000000000999a9b9c9d9e9f608001519261097960405193849251905190602084019d8e52896024850161570f565b5f8088518b82608073ffffffffffffffffffffffffffffffffffffffff60e08501511693015192865193f1983d90815f843e519482519a604084019b8c519115613e605760401490811591613e2e575b50613db15750601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101160191826040525a900311613d4f5750948260a0613933565b80887f220266b60000000000000000000000000000000000000000000000000000000060849352600482015260406024820152602060448201527f41413336206f76657220706d566572696669636174696f6e4761734c696d69746064820152fd5b8b610b2b613dbd6133d4565b6040519384937f65c8fd4d00000000000000000000000000000000000000000000000000000000855260048501526024840152601d60648401527f41413335206d616c666f726d6564207061796d61737465722064617461000000608484015260a0604484015260a4830190612c05565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09150601f011681018214155f613d0b565b828e610b2b613e6d6133d4565b6040519384937f65c8fd4d00000000000000000000000000000000000000000000000000000000855260048501526024840152600d60648401527f4141333320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612c05565b608489604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413331207061796d6173746572206465706f73697420746f6f206c6f7700006064820152fd5b608489604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413236206f76657220766572696669636174696f6e4761734c696d697400006064820152fd5b60848a604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601a60448201527f4141323520696e76616c6964206163636f756e74206e6f6e63650000000000006064820152fd5b61401691615731565b15614022575f80613899565b60848a604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152fd5b8d903b6140f357608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601960448201527f41413230206163636f756e74206e6f74206465706c6f796564000000000000006064820152fd5b6140fb6133d4565b90610b2b6040519283927f65c8fd4d000000000000000000000000000000000000000000000000000000008452600484015260606024840152600d60648401527f4141323320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a4830190612c05565b5f915061388a565b9950815f525f60205260405f20548181115f1461419a57505f5b996137e4565b8103614194565b808f7f220266b60000000000000000000000000000000000000000000000000000000060849352600482015260406024820152601860448201527f41413934206761732076616c756573206f766572666c6f7700000000000000006064820152fd5b60348210614294578160141161288457803560601c916024811061288457601482013590603411612884576fffffffffffffffffffffffffffffffff60248193013560801c1660a089015260801c16608087015280156142695760e08601525f80613734565b7fd8ccb292000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b507f120aaab5000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9092915a6020820180515f5d60608301519060405196876142e46060830183613252565b5f60038211614961575b7fffffffff00000000000000000000000000000000000000000000000000000000167f8dd7712f00000000000000000000000000000000000000000000000000000000036147f3575050505f6143f96144ed6143876143b960209587516040519384927f8dd7712f000000000000000000000000000000000000000000000000000000008a8501526040602485015260648401906132f3565b906044830152037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282612a49565b6109796040519384927e42dc5300000000000000000000000000000000000000000000000000000000888501526102006024850152610224840190612c05565b6144bc604484018c60806101a091610120815173ffffffffffffffffffffffffffffffffffffffff8151168652602081015160208701526040810151604087015260608101516060870152838101518487015260a081015160a087015260c081015160c087015273ffffffffffffffffffffffffffffffffffffffff60e08201511660e087015261010081015161010087015201516101208501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8382030161020484015288612c05565b828151910182305af15f519760405215614509575b5050505050565b909192939495505f3d6020146147e6575b7fdeaddead0000000000000000000000000000000000000000000000000000000081036145a657608486604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152600f60448201527f41413935206f7574206f662067617300000000000000000000000000000000006064820152fd5b7fdeadaa5100000000000000000000000000000000000000000000000000000000919293949550145f1461460e5750506145f26145e7614602925a90612d53565b608084015190612ca3565b6040830151836126a68295614b37565b905b5f80808080614502565b9161467f919260405190518551907ff62676f440ff169a3a9afdbf812e89e7f95975ee8e5c31214ffdef631c5f4792602073ffffffffffffffffffffffffffffffffffffffff8451169301516146626133d4565b9061467260405192839283612c48565b0390a36040525a90612d53565b61468f6080840191825190612ca3565b915f905a92855161010081015161012082015148018082105f146147de5750955b6146dd73ffffffffffffffffffffffffffffffffffffffff60e08401511693518203606084015190614a16565b01925f92806147af5750505173ffffffffffffffffffffffffffffffffffffffff16935b5a900301019283026040850151928184105f14614763575050806147365750908161473092936126a681614b37565b90614604565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526021600452fd5b614798908284939795039073ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081540180915590565b50614736575090825f6147aa93614ab6565b614730565b959190516147be575b50614701565b935090506147d75a9360a05f955a900391015190614a16565b905f6147b8565b9050956146b0565b5060205f803e5f5161451a565b614958935061492c91614838917e42dc530000000000000000000000000000000000000000000000000000000060208601526102006024860152610224850191613097565b6148fb604484018960806101a091610120815173ffffffffffffffffffffffffffffffffffffffff8151168652602081015160208701526040810151604087015260608101516060870152838101518487015260a081015160a087015260c081015160c087015273ffffffffffffffffffffffffffffffffffffffff60e08201511660e087015261010081015161010087015201516101208501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8382030161020484015285612c05565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101895288612a49565b60205f886144ed565b5081356142ee565b73ffffffffffffffffffffffffffffffffffffffff1680156149eb575f805d5f80808085855af1614998612d60565b90156149a357505050565b610b2b906040519384937f40848e6100000000000000000000000000000000000000000000000000000000855260048501526024840152606060448401526064830190612c05565b7f1a3b45fd000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90619c408201811115614a2f57606491600a9103020490565b50505f90565b9190917f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f6080602083015192519473ffffffffffffffffffffffffffffffffffffffff86511694602073ffffffffffffffffffffffffffffffffffffffff60e089015116970151916040519283525f602084015260408301526060820152a4565b9060807f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f91602084015193519573ffffffffffffffffffffffffffffffffffffffff87511695602073ffffffffffffffffffffffffffffffffffffffff60e08a015116980151926040519384521515602084015260408301526060820152a4565b60208101519051907f67b4fa9642f42120bf031f3051d1824b0fe25627945b27b8a6a65d5761d5482e60208073ffffffffffffffffffffffffffffffffffffffff855116940151604051908152a3565b90600211614bd757357fffffffffffffffffffffffffffffffffffffffff000000000000000000000000167f77020000000000000000000000000000000000000000000000000000000000001490565b505f90565b60175f80833c5f51907fef010000000000000000000000000000000000000000000000000000000000007fffffff0000000000000000000000000000000000000000000000000000000000831603614c4b575060481c73ffffffffffffffffffffffffffffffffffffffff1690565b8073ffffffffffffffffffffffffffffffffffffffff913b15614c94577f9f4e4cc9000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b7fe5819b95000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b60ff8114614d1f5760ff811690601f8211614cf75760405191614ce4604084612a49565b6020808452838101919036833783525290565b7fb3512b0c000000000000000000000000000000000000000000000000000000005f5260045ffd5b506040515f6002548060011c9160018216918215614e2c575b602084108314614dff578385528492908115614dc25750600114614d63575b612c5f92500382612a49565b5060025f90815290917f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b818310614da6575050906020612c5f92820101614d57565b6020919350806001915483858801015201910190918392614d8e565b60209250612c5f9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b820101614d57565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b92607f1692614d38565b60ff8114614e5a5760ff811690601f8211614cf75760405191614ce4604084612a49565b506040515f6003548060011c9160018216918215614efc575b602084108314614dff578385528492908115614dc25750600114614e9d57612c5f92500382612a49565b5060035f90815290917fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b818310614ee0575050906020612c5f92820101614d57565b6020919350806001915483858801015201910190918392614ec8565b92607f1692614e73565b614f1082826150b8565b80614f215750816040519182372090565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe919203604051927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff682019084377f22e325a2974396560000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6828501015201902090565b80156150af575f60408051614fd081612a2d565b828152826020820152015273ffffffffffffffffffffffffffffffffffffffff81169065ffffffffffff8160a01c169081156150a1575b60409060d01c91815161501981612a2d565b84815283602082015265ffffffffffff821692839101526580000000000083101580615091575b1561507457657fffffffffff9150164311908115615061575b509060019092565b657fffffffffff9150164311155f615059565b504211908115615086575b50905f9092565b90504211155f61507f565b5065800000000000821015615040565b65ffffffffffff9150615007565b505f905f905f90565b603e8210614a2f577f22e325a2974396560000000000000000000000000000000000000000000000007fffffffffffffffff000000000000000000000000000000000000000000000000615130847ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff88101818661352a565b9035828116916008811061523b575b50501603614a2f578161517691817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff681019161352a565b90357fffff00000000000000000000000000000000000000000000000000000000000081169160028110615206575b505060f01c907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc2810182116151d8575090565b7f07b9a191000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7fffff0000000000000000000000000000000000000000000000000000000000009250829060020360031b1b16165f806151a5565b839250829060080360031b1b16165f8061513f565b929091925f82615261575050505050565b83519473ffffffffffffffffffffffffffffffffffffffff865116956152878583614b87565b6155b9575060148410615554578360141161555057803560601c93863b61551a576152f39160209160408851015190856040518096819582947f570e1a360000000000000000000000000000000000000000000000000000000084528860048501526024840191613097565b039273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690f191821561550e57916154ef575b5073ffffffffffffffffffffffffffffffffffffffff8116801561548a578503615425573b156153c0575060407fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d9173ffffffffffffffffffffffffffffffffffffffff60e06020860151955101511682519182526020820152a35f80808080614502565b608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152fd5b608482604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313420696e6974436f6465206d7573742072657475726e2073656e6465726064820152fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601b60448201527f4141313320696e6974436f6465206661696c6564206f72204f4f4700000000006064820152fd5b615508915060203d602011611762576117548183612a49565b5f61533b565b604051903d90823e3d90fd5b50505050906020807fa39bcda08ffd11bafb11c4f170ef24fc6dc1a9d1b0394d90dbd19e0b919050e992015192604051908152a3565b5080fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f4141393920696e6974436f646520746f6f20736d616c6c0000000000000000006064820152fd5b91959493909250601481116155d1575b505050505050565b604073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169201518160141161288457823b156128845761568c935f80946040518097819682957fc09ad0d90000000000000000000000000000000000000000000000000000000084528c60048501526040602485015260147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec6044860193019101613097565b0393f18015615704576156ef575b507f7c9f9ade6a03a0bba484e52df872467a270e798ffc1adab9dfaa8d0e627f054473ffffffffffffffffffffffffffffffffffffffff60206156dc85614bdc565b93015192169380a45f80808080806155c9565b6156fc9193505f90612a49565b5f915f61569a565b6040513d5f823e3d90fd5b615727604092959493956060835260608301906132f3565b9460208201520152565b73ffffffffffffffffffffffffffffffffffffffff165f525f60205260405f209081548181106136595703905560019056fea2646970667358221220103fb192e0a71c317870b5572e8de9adf2590b83d7b291358c06304b0f96152e64736f6c634300081c003360a08060405234602f57336080526104ba9081610034823960805181818160c30152818161023701526102cf0152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063570e1a361461025b578063b0d691fe146101ed5763c09ad0d91461003a575f80fd5b346101e95760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e95760043573ffffffffffffffffffffffffffffffffffffffff811681036101e95760243567ffffffffffffffff81116101e957366023820112156101e9575f916100bd8392369060248160040135910161038a565b906101027f0000000000000000000000000000000000000000000000000000000000000000303373ffffffffffffffffffffffffffffffffffffffff8316331461042c565b82602083519301915af11561011357005b3d61080081116101e0575b60c460405160208382010160405282815260208101925f843e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6040519485937f65c8fd4d0000000000000000000000000000000000000000000000000000000085525f6004860152606060248601528260648601527f4141313320454950373730322073656e64657220696e6974206661696c656400608486015260a060448601525180918160a48701528686015e5f85828601015201168101030190fd5b5061080061011e565b5f80fd5b346101e9575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e957602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101e95760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101e95760043567ffffffffffffffff81116101e957366023820112156101e95780600401359067ffffffffffffffff82116101e95736602483830101116101e9575f9161030e7f0000000000000000000000000000000000000000000000000000000000000000303373ffffffffffffffffffffffffffffffffffffffff8316331461042c565b806014116101e95760209161034b5f927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec3691016038840161038a565b90826024858451940192013560601c5af1610382575b60209073ffffffffffffffffffffffffffffffffffffffff60405191168152f35b505f51610361565b92919267ffffffffffffffff82116103ff57604051917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81601f8401160116830183811067ffffffffffffffff8211176103ff576040528294818452818301116101e9578281602093845f960137010152565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b1561043657505050565b73ffffffffffffffffffffffffffffffffffffffff92918380927ffe34a6d3000000000000000000000000000000000000000000000000000000005f5216600452166024521660445260645ffdfea2646970667358221220cc5451baa1f82147b8b7837944f12ea87c38e96ac492c14160f7ceeb020c438f64736f6c634300081c0033"; +address constant ENTRYPOINT_0_9 = 0x433709009B8330FDa32311DF1C2AFA402eD8D009; address constant DETERMINISTIC_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; library EntryPointLib { From ef2bb8968606842bd46b50d410d8b4da33cfcb6a Mon Sep 17 00:00:00 2001 From: taek Date: Wed, 5 Aug 2026 09:46:19 +0900 Subject: [PATCH 2/8] feat: remove Hook Remove generic type-4 hooks, hook registration storage, sentinel-based installation state, and embedded hook configuration from validations, executors, and fallback selectors. --- src/Kernel.sol | 94 +++++++++--------------------- src/core/ExecutorManager.sol | 42 +++++-------- src/core/HookManager.sol | 67 --------------------- src/core/ModuleManager.sol | 36 ++---------- src/core/SelectorManager.sol | 39 +++---------- src/core/ValidationManager.sol | 81 +++++++------------------ src/interfaces/IERC7579Modules.sol | 9 --- src/types/Constants.sol | 6 -- src/types/Error.sol | 5 +- src/types/Events.sol | 2 +- src/types/Structs.sol | 33 ++++------- 11 files changed, 91 insertions(+), 323 deletions(-) delete mode 100644 src/core/HookManager.sol diff --git a/src/Kernel.sol b/src/Kernel.sol index 65a23cdd..1aabd163 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; import {IERC7579Account} from "./interfaces/IERC7579Account.sol"; -import {IValidator, IExecutor, IHook, IModule} from "./interfaces/IERC7579Modules.sol"; +import {IValidator, IExecutor, IModule} from "./interfaces/IERC7579Modules.sol"; import {ModuleManager, Install} from "./core/ModuleManager.sol"; import {ExecutionManager} from "./core/ExecutionManager.sol"; import {Lib4337} from "./lib/Lib4337.sol"; @@ -42,11 +42,8 @@ import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, - MODULE_TYPE_HOOK, MODULE_TYPE_POLICY, - MODULE_TYPE_SIGNER, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK + MODULE_TYPE_SIGNER } from "./types/Constants.sol"; import { ValidationStorage, @@ -59,7 +56,7 @@ import { /// @title Kernel /// @author taek -/// @notice ERC-7579 compliant modular smart account with pluggable validation, execution, and hook modules. +/// @notice ERC-7579 compliant modular smart account with pluggable validation and execution modules. abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { IEntryPoint immutable ENTRYPOINT; @@ -161,36 +158,17 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } ValidationStorage storage $ = _validationStorage(); - // For non-root validation, check if validation exists before checking selectors + // Root is the unconditional recovery path and bypasses selector handling. if (vType != VALIDATION_TYPE_ROOT) { - require($.vInfo[vId].hook > HOOK_MODULE_NOT_INSTALLED, InvalidVid(vId)); - } - - // Bypass selector + hook handling when: - // - vType == ROOT: ROOT is the unconditional last-resort access path. It is - // intentionally exempt from selector allow-listing and from validation hooks - // so that a misconfigured / compromised hook on a scoped validation cannot - // lock the user out of their own account. Users who want hook-monitored - // access should reach for it via a non-root validator or permission. - // - non-ROOT with leading allow-listed selector AND no hook installed - // (HOOK_MODULE_INSTALLED_NO_HOOK sentinel): cheap fast-path that skips the - // executeUserOp wrapper because there is nothing for a hook to wrap. - // Any other case must route through executeUserOp with an allow-listed inner - // selector, and the validation-scoped hook is attached so executeUserOp's - // _preHook/_postHook fire around the inner delegatecall. - if ( - vType == VALIDATION_TYPE_ROOT - || (_allowedSelector(vId, bytes4(userOp.callData[0:4])) - && $.vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK) - ) { - // No-op, this is cheaper in gas - } else { - require( - bytes4(userOp.callData[0:4]) == this.executeUserOp.selector - && _allowedSelector(vId, bytes4(userOp.callData[4:])), - UnauthorizedCallData() - ); - _setValidationHook(userOpHash, IHook($.vInfo[vId].hook)); + ValidationInfo storage info = $.vInfo[vId]; + require(info.installed, InvalidVid(vId)); + if (!_allowedSelector(vId, bytes4(userOp.callData[0:4]))) { + require( + bytes4(userOp.callData[0:4]) == this.executeUserOp.selector + && _allowedSelector(vId, bytes4(userOp.callData[4:])), + UnauthorizedCallData() + ); + } } (vId, validateUserOpFn) = _checkValidation(vType, vId); bytes32 opHash = isReplayable(vMode) ? Lib4337.chainAgnosticUserOpHash(msg.sender, userOp) : userOpHash; @@ -198,19 +176,16 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { Lib4337.intersectValidationData(validationData, validateUserOpFn(vId, opHash, userOp, signature)); } - /// @notice Executes a user operation with validation-hook context. - /// @dev Called by the entry point after validateUserOp. Runs pre/post hooks stored transiently - /// and delegatecalls the inner calldata (userOp.callData[4:]). + /// @notice Executes a user operation by delegatecalling its inner calldata. /// @dev SECURITY: The inner calldata (userOp.callData[4:]) is delegatecalled to `address(this)` /// with no additional selector or target validation. Any function on Kernel (including /// privileged ones like `installModule`, `setRoot`, `execute`) can be invoked this way. /// Authorization relies entirely on `validateUserOp` having approved the outer UserOp. /// @param userOp The packed user operation containing the execution calldata. - /// @param userOpHash The hash of the user operation, used to retrieve the transient validation hook. + /// @param userOpHash The hash of the user operation. function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external payable { _onlyEntryPointOrSelf(); - IHook hook = _validationHook(userOpHash); - bytes memory context = _preHook(hook, userOp.callData[4:]); + userOpHash; (bool success, bytes memory ret) = address(this).delegatecall(userOp.callData[4:]); // propagate the revert message if (!success) { @@ -218,7 +193,6 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { revert(add(ret, 0x20), mload(ret)) } } - _postHook(hook, context); } /// @notice Executes a call according to the given ERC-7579 execution mode. @@ -230,7 +204,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } /// @notice Executes a call on behalf of an installed executor module. - /// @dev The calling executor must be installed with a valid hook configuration. + /// @dev The calling executor must be installed. /// @param mode The execution mode encoding call type and exec type. /// @param executionData The ABI-encoded execution data matching the call type. /// @return returnData Array of return data from each executed call. @@ -244,9 +218,9 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { function _executeFromExecutor(bytes32 mode, bytes calldata executionData) internal - executorHook returns (bytes[] memory returnData) { + require(_executorConfig(IExecutor(msg.sender)).installed, Unauthorized()); return _execute(mode, executionData); } @@ -270,12 +244,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { bytes4 selector = bytes4(msg.data[0:4]); SelectorConfig storage $ = _selectorConfig(selector); - // target must be initialized, and if hook is not set only entrypoint can call it - require( - $.target != address(0) && ($.hook != IHook(HOOK_MODULE_NOT_INSTALLED) || msg.sender == address(ENTRYPOINT)), - InvalidSelector() - ); - bytes memory hookData = _preHook($.hook, msg.data); + require($.target != address(0), InvalidSelector()); bool success; if ($.callType == CALLTYPE_SINGLE) { @@ -290,7 +259,6 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } else { res = _getReturn(); } - _postHook($.hook, hookData); } /// @notice Advances the nonce for a given key, invalidating all lower nonce values. @@ -310,7 +278,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { /// @notice Installs a single module per ERC-7579. /// @dev The initData is decoded as `InstallModuleDataFormat(bytes installData, bytes internalData)`. - /// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 4=hook, 5=policy, 6=signer). + /// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). /// @param module The address of the module contract to install. /// @param initData ABI-encoded `InstallModuleDataFormat` containing install data and internal configuration. function installModule(uint256 moduleType, address module, bytes calldata initData) external payable override { @@ -381,11 +349,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { // forge-lint: disable-next-line(unchecked-call) vInfo.signer - .call( - abi.encodeWithSelector( - IModule.onUninstall.selector, uninstallDataArr[uninstallDataArr.length - 1] - ) - ); + .call(abi.encodeWithSelector(IModule.onUninstall.selector, uninstallDataArr[vInfo.policies.length])); _uninstallSignerWithVid(vInfo.signer, vId); } else { revert InvalidRootValidation(); @@ -455,10 +419,12 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } /// @notice Returns whether the given module type is supported. - /// @param moduleTypeId The module type identifier (1-6 are supported). - /// @return True if the module type is supported. + /// @param moduleTypeId The module type identifier. + /// @return True for validator, executor, fallback, policy, and signer modules. function supportsModule(uint256 moduleTypeId) external pure returns (bool) { - return moduleTypeId < 7 && moduleTypeId != 0; + return moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR + || moduleTypeId == MODULE_TYPE_FALLBACK || moduleTypeId == MODULE_TYPE_POLICY + || moduleTypeId == MODULE_TYPE_SIGNER; } /// @notice Checks whether a specific module is currently installed. @@ -474,15 +440,13 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { { if (moduleTypeId == MODULE_TYPE_VALIDATOR) { ValidationId vId = validatorToIdentifier(IValidator(module)); - return _validationStorage().vInfo[vId].hook != HOOK_MODULE_NOT_INSTALLED; + return _validationStorage().vInfo[vId].installed; } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { - return address(_executorConfig(IExecutor(module)).hook) != HOOK_MODULE_NOT_INSTALLED; + return _executorConfig(IExecutor(module)).installed; } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { // forge-lint: disable-next-line(unsafe-typecast) bytes4 selector = bytes4(additionalContext); return _selectorConfig(selector).target == module; - } else if (moduleTypeId == MODULE_TYPE_HOOK) { - return _hookStorage().enabled[module]; } else if (moduleTypeId == MODULE_TYPE_POLICY) { // forge-lint: disable-next-line(unsafe-typecast) ValidationId vId = permissionToIdentifier(PermissionId.wrap(bytes4(additionalContext))); @@ -497,7 +461,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { // forge-lint: disable-next-line(unsafe-typecast) ValidationId vId = permissionToIdentifier(PermissionId.wrap(bytes4(additionalContext))); ValidationInfo storage $ = _validationStorage().vInfo[vId]; - return $.signer == module; + return module != address(0) && $.signer == module; } else { revert NotImplemented(); } diff --git a/src/core/ExecutorManager.sol b/src/core/ExecutorManager.sol index 1617750e..941441e1 100644 --- a/src/core/ExecutorManager.sol +++ b/src/core/ExecutorManager.sol @@ -1,21 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { - EXECUTOR_MANAGER_STORAGE_SLOT, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK -} from "../types/Constants.sol"; -import {IExecutor, IHook} from "../interfaces/IERC7579Modules.sol"; +import {EXECUTOR_MANAGER_STORAGE_SLOT} from "../types/Constants.sol"; +import {IExecutor} from "../interfaces/IERC7579Modules.sol"; import {ExecutorStorage, ExecutorConfig} from "../types/Structs.sol"; -import {NotInstalled} from "../types/Error.sol"; +import {InvalidDataLength} from "../types/Error.sol"; /// @title ExecutorManager /// @author taek -/// @notice Manages executor module installation and their associated hook configurations. +/// @notice Manages executor module installation state. abstract contract ExecutorManager { - function _hookEnabled(IHook _hook) internal view virtual returns (bool); - /// @notice Returns the executor manager storage reference. function _executorStorage() internal pure returns (ExecutorStorage storage $) { assembly { @@ -23,9 +17,7 @@ abstract contract ExecutorManager { } } - /// @notice Returns the hook configuration for a given executor. - /// @param executor The executor module address. - /// @return The ExecutorConfig containing the hook address. + /// @notice Returns the configuration for a given executor. function executorConfig(address executor) external view returns (ExecutorConfig memory) { return _executorConfig(IExecutor(executor)); } @@ -34,24 +26,16 @@ abstract contract ExecutorManager { config = _executorStorage().executorConfig[executor]; } - /// @notice Installs an executor module with an optional hook. - /// @dev internalData format: first 20 bytes = hook address (address(0) means no hook, stored as address(1)). - /// @param _executor The executor module address. - /// @param _internalData Hook address (20 bytes); if empty, no hook is set. + /// @notice Installs an executor module without a hook. function _installExecutor(address _executor, bytes calldata _internalData, bool) internal { - // NOTE: we don't care if install was successful - address hook = _internalData.length >= 20 ? address(bytes20(_internalData[0:20])) : HOOK_MODULE_NOT_INSTALLED; - if (hook == HOOK_MODULE_NOT_INSTALLED) { - hook = HOOK_MODULE_INSTALLED_NO_HOOK; // address(1) indicates it is installed and does not require any hook - } else { - require(hook == HOOK_MODULE_INSTALLED_NO_HOOK || _hookEnabled(IHook(hook)), NotInstalled()); - } - _executorConfig(IExecutor(_executor)).hook = IHook(hook); + require(_internalData.length == 0, InvalidDataLength()); + // Executor installation intentionally does not depend on onInstall success. + _executorConfig(IExecutor(_executor)).installed = true; } - /// @notice Uninstalls an executor module by zeroing its hook. - /// @param _executor The executor module address. - function _uninstallExecutor(address _executor, bytes calldata, bool) internal { - _executorConfig(IExecutor(_executor)).hook = IHook(HOOK_MODULE_NOT_INSTALLED); + /// @notice Uninstalls an executor module. + function _uninstallExecutor(address _executor, bytes calldata _internalData, bool) internal { + require(_internalData.length == 0, InvalidDataLength()); + _executorConfig(IExecutor(_executor)).installed = false; } } diff --git a/src/core/HookManager.sol b/src/core/HookManager.sol deleted file mode 100644 index 8c003021..00000000 --- a/src/core/HookManager.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {IHook} from "../interfaces/IERC7579Modules.sol"; -import { - HOOK_MANAGER_STORAGE_SLOT, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK -} from "../types/Constants.sol"; -import {ModuleInstallFailed} from "../types/Error.sol"; -import {HookStorage} from "../types/Structs.sol"; - -/// @title HookManager -/// @author taek -/// @notice Manages hook module installation and pre/post execution checks. -abstract contract HookManager { - /// @notice Returns whether a hook module is enabled. - /// @param _hook The hook to check. - /// @return True if the hook is enabled. - function _hookEnabled(IHook _hook) internal view virtual returns (bool) { - return _hookStorage().enabled[address(_hook)]; - } - - /// @notice Returns the hook manager storage reference. - function _hookStorage() internal pure returns (HookStorage storage hs) { - bytes32 slot = HOOK_MANAGER_STORAGE_SLOT; - assembly { - hs.slot := slot - } - } - - /// @notice Installs a hook module by marking it as enabled. - /// @param _hook The hook module address. - /// @param _internalData If empty, requires onInstall success; otherwise ignored. - /// @param _installSuccess Whether the module's onInstall succeeded. - function _installHook(address _hook, bytes calldata _internalData, bool _installSuccess) internal { - if (_internalData.length == 0) { - require(_installSuccess, ModuleInstallFailed()); - } - _hookStorage().enabled[_hook] = true; - } - - /// @notice Uninstalls a hook module by marking it as disabled. - /// @param _hook The hook module address. - function _uninstallHook(address _hook, bytes calldata, bool) internal { - _hookStorage().enabled[_hook] = false; - } - - /// @notice Executes the hook's preCheck if the hook is a real module (not sentinel values). - /// @param _hook The hook to call. - /// @param _data The calldata to pass to the hook's preCheck. - /// @return context The context data returned by the hook for use in postCheck. - function _preHook(IHook _hook, bytes calldata _data) internal returns (bytes memory context) { - if (address(_hook) != HOOK_MODULE_INSTALLED_NO_HOOK && address(_hook) != HOOK_MODULE_NOT_INSTALLED) { - context = _hook.preCheck(msg.sender, msg.value, _data); - } - } - - /// @notice Executes the hook's postCheck if the hook is a real module. - /// @param _hook The hook to call. - /// @param context The context data from preCheck. - function _postHook(IHook _hook, bytes memory context) internal { - if (address(_hook) != HOOK_MODULE_INSTALLED_NO_HOOK && address(_hook) != HOOK_MODULE_NOT_INSTALLED) { - _hook.postCheck(context); - } - } -} diff --git a/src/core/ModuleManager.sol b/src/core/ModuleManager.sol index 633ed481..d4d6650a 100644 --- a/src/core/ModuleManager.sol +++ b/src/core/ModuleManager.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IHook, IExecutor, IModule, IValidator, IStatelessValidatorWithSender} from "../interfaces/IERC7579Modules.sol"; +import {IModule, IValidator, IStatelessValidatorWithSender} from "../interfaces/IERC7579Modules.sol"; import {ValidationManager} from "./ValidationManager.sol"; import {ExecutorManager} from "./ExecutorManager.sol"; -import {HookManager} from "./HookManager.sol"; import {SelectorManager} from "./SelectorManager.sol"; import {ERC1271} from "../lib/ERC1271.sol"; import { @@ -14,7 +13,6 @@ import { InvalidPermissionId, InvalidSignature, NotImplemented, - Unauthorized, PermissionInstallNotFinished, LastSignatureShouldBeSigner } from "../types/Error.sol"; @@ -40,27 +38,16 @@ import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, - MODULE_TYPE_HOOK, MODULE_TYPE_POLICY, - MODULE_TYPE_SIGNER, - HOOK_MODULE_NOT_INSTALLED + MODULE_TYPE_SIGNER } from "../types/Constants.sol"; import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; /// @title ModuleManager /// @author taek -/// @notice Composes validation, executor, hook, and selector managers; handles module installation, +/// @notice Composes validation, executor, and selector managers; handles module installation, /// enable-mode signature verification, nonce management, and ERC-1271 signature flows. -abstract contract ModuleManager is ValidationManager, ExecutorManager, HookManager, SelectorManager, ERC1271 { - /// @dev Modifier that wraps executor calls with their configured hook's pre/post checks. - modifier executorHook() { - IHook hook = _executorConfig(IExecutor(msg.sender)).hook; - require(address(hook) != HOOK_MODULE_NOT_INSTALLED, Unauthorized()); - bytes memory hookData = _preHook(hook, msg.data); - _; - _postHook(hook, hookData); - } - +abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorManager, ERC1271 { /// @dev Override this function to integrate an ERC-7484 module registry check. function _installModuleCheck(uint256 moduleType, address module) internal virtual {} @@ -93,15 +80,6 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, HookManag return (uint256(key) << 64) + seq; } - function _hookEnabled(IHook _hook) - internal - view - override(ValidationManager, ExecutorManager, HookManager, SelectorManager) - returns (bool) - { - return HookManager._hookEnabled(_hook); - } - function _moduleStorage() internal pure returns (ModuleStorage storage $) { assembly { $.slot := MODULE_MANAGER_STORAGE_SLOT @@ -182,7 +160,7 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, HookManag } /// @notice Routes a module installation to the appropriate type-specific handler. - /// @param moduleType The module type (1=validator, 2=executor, 3=fallback, 4=hook, 5=policy, 6=signer). + /// @param moduleType The module type (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). /// @param module The module address. /// @param moduleData Data forwarded to the module's onInstall callback. /// @param internalData Kernel-internal configuration data (format varies by module type). @@ -197,8 +175,6 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, HookManag hook = _installExecutor; } else if (moduleType == MODULE_TYPE_FALLBACK) { hook = _installSelector; - } else if (moduleType == MODULE_TYPE_HOOK) { - hook = _installHook; } else if (moduleType == MODULE_TYPE_POLICY) { hook = _installPolicy; } else if (moduleType == MODULE_TYPE_SIGNER) { @@ -228,8 +204,6 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, HookManag hook = _uninstallExecutor; } else if (moduleType == MODULE_TYPE_FALLBACK) { hook = _uninstallSelector; - } else if (moduleType == MODULE_TYPE_HOOK) { - hook = _uninstallHook; } else if (moduleType == MODULE_TYPE_POLICY) { hook = _uninstallPolicy; } else if (moduleType == MODULE_TYPE_SIGNER) { diff --git a/src/core/SelectorManager.sol b/src/core/SelectorManager.sol index 8fe1a6b8..0d5c4afd 100644 --- a/src/core/SelectorManager.sol +++ b/src/core/SelectorManager.sol @@ -1,26 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IHook} from "../interfaces/IERC7579Modules.sol"; import {CallType} from "../types/Types.sol"; -import { - SELECTOR_MANAGER_STORAGE_SLOT, - CALLTYPE_DELEGATECALL, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK -} from "../types/Constants.sol"; -import {ModuleInstallFailed, NotInstalled, InvalidSelectorTarget} from "../types/Error.sol"; +import {SELECTOR_MANAGER_STORAGE_SLOT, CALLTYPE_DELEGATECALL} from "../types/Constants.sol"; +import {ModuleInstallFailed, InvalidSelectorTarget, InvalidDataLength} from "../types/Error.sol"; import {SelectorConfig, SelectorStorage} from "../types/Structs.sol"; /// @title SelectorManager /// @author taek -/// @notice Manages fallback module routing by function selector, including call type and hook configuration. +/// @notice Manages fallback module routing by function selector. abstract contract SelectorManager { - function _hookEnabled(IHook _hook) internal view virtual returns (bool); - /// @notice Returns the fallback selector configuration for a given selector. - /// @param selector The 4-byte function selector. - /// @return The SelectorConfig (hook, target, callType). function selectorConfig(bytes4 selector) external view returns (SelectorConfig memory) { return _selectorConfig(selector); } @@ -38,33 +28,22 @@ abstract contract SelectorManager { } /// @notice Installs a fallback selector handler. - /// @dev internalData format: `[bytes4 selector | bytes1 callType | bytes20 hookAddress]`. - /// @param _module The fallback module address. - /// @param _internalData Packed selector, call type, and hook address. - /// @param _installSuccess Whether the module's onInstall call succeeded (required for non-delegatecall). + /// @dev internalData format: `[bytes4 selector | bytes1 callType]`. function _installSelector(address _module, bytes calldata _internalData, bool _installSuccess) internal { + require(_internalData.length == 5, InvalidDataLength()); require(_module != address(0), InvalidSelectorTarget()); CallType callType = CallType.wrap(bytes1(_internalData[4])); require(callType == CALLTYPE_DELEGATECALL || _installSuccess, ModuleInstallFailed()); - bytes4 selector = bytes4(_internalData[0:4]); - address hook = address(bytes20(_internalData[5:25])); - // address(0) = entryPoint-only (no hook), address(1) = anyone (no hook), else = real hook - if (hook != HOOK_MODULE_NOT_INSTALLED && hook != HOOK_MODULE_INSTALLED_NO_HOOK) { - require(_hookEnabled(IHook(hook)), NotInstalled()); - } - SelectorConfig storage $ = _selectorConfig(selector); + SelectorConfig storage $ = _selectorConfig(bytes4(_internalData[0:4])); $.target = _module; $.callType = callType; - $.hook = IHook(hook); } - /// @notice Uninstalls a fallback selector handler by zeroing its configuration. - /// @param _internalData Must contain the selector in the first 4 bytes. + /// @notice Uninstalls a fallback selector handler. function _uninstallSelector(address, bytes calldata _internalData, bool) internal { - bytes4 selector = bytes4(_internalData[0:4]); - SelectorConfig storage $ = _selectorConfig(selector); + require(_internalData.length == 4, InvalidDataLength()); + SelectorConfig storage $ = _selectorConfig(bytes4(_internalData[0:4])); $.target = address(0); $.callType = CallType.wrap(bytes1(0x00)); - $.hook = IHook(address(0)); } } diff --git a/src/core/ValidationManager.sol b/src/core/ValidationManager.sol index 5765881b..17aecb77 100644 --- a/src/core/ValidationManager.sol +++ b/src/core/ValidationManager.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; import {IAccountExecute} from "account-abstraction/interfaces/IAccountExecute.sol"; -import {IValidator, IPolicy, ISigner, IHook} from "../interfaces/IERC7579Modules.sol"; +import {IValidator, IPolicy, ISigner} from "../interfaces/IERC7579Modules.sol"; import { InvalidRootValidation, ModuleInstallFailed, @@ -15,7 +15,6 @@ import { CannotUninstallRoot, InvalidVid, InvalidDataLength, - NotInstalled, InvalidPermissionInstall, InvalidSignature } from "../types/Error.sol"; @@ -31,9 +30,7 @@ import { MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER, SIG_VALIDATION_FAILED_UINT, - SIG_VALIDATION_SUCCESS_UINT, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK + SIG_VALIDATION_SUCCESS_UINT } from "../types/Constants.sol"; import {PermissionSignature, ValidationStorage, ValidationInfo, Install} from "../types/Structs.sol"; import {Lib4337} from "../lib/Lib4337.sol"; @@ -46,8 +43,6 @@ abstract contract ValidationManager { /// @dev Tracks the permission being installed within a batch to ensure consistency. ValidationId transient installingPermission; - function _hookEnabled(IHook _hook) internal view virtual returns (bool); - /// @notice Returns the current root validation identifier. /// @return The root ValidationId. function root() external view returns (ValidationId) { @@ -55,24 +50,6 @@ abstract contract ValidationManager { return $.root; } - /// @notice Retrieves the validation hook stored transiently for a given userOp hash. - /// @param userOpHash The user operation hash used as the transient storage key. - /// @return hook The hook address stored for this userOp. - function _validationHook(bytes32 userOpHash) internal view returns (IHook hook) { - assembly { - hook := tload(userOpHash) - } - } - - /// @notice Stores a validation hook in transient storage keyed by the userOp hash. - /// @param userOpHash The user operation hash used as the transient storage key. - /// @param hook The hook to store. - function _setValidationHook(bytes32 userOpHash, IHook hook) internal { - assembly { - tstore(userOpHash, hook) - } - } - /// @notice Returns the validation info (hook, signer, policies) for a given ValidationId. /// @param vId The validation identifier to query. /// @return The ValidationInfo struct for this identifier. @@ -111,45 +88,28 @@ abstract contract ValidationManager { } } - /// @dev returns bool if nonce matches the selector allowance, you should also check hook to make sure validation is installed + /// @dev Returns whether the selector allowance nonce matches the validation's current nonce. function _allowedSelector(ValidationId vId, bytes4 selector) internal view returns (bool) { ValidationStorage storage $ = _validationStorage(); return $.allowed[vId][selector] == $.vInfo[vId].nonce; } - /// @notice Initializes a validation's hook and allowed selectors. - /// @dev If _internalData is empty, the validation is marked as installed with no hook and no selectors. - /// Otherwise: first 20 bytes = hook address, remaining bytes = packed bytes4 selectors. - /// @param vId The validation identifier to initialize. - /// @param _internalData The internal configuration data. - function _initializeValidation(ValidationId vId, bytes calldata _internalData) internal { - ValidationStorage storage $ = _validationStorage(); - require($.vInfo[vId].hook == HOOK_MODULE_NOT_INSTALLED, OccupiedValidationId()); - // if _internalData is empty, skip the initialization but bump the nonce so any - // `allowed[vId][sel]` entries from a prior incarnation of this vId (after - // uninstall+reinstall) become stale -- giving empty-internalData installs the - // same default-deny semantics as the non-empty path (where _grantAccess bumps). - if (_internalData.length == 0) { - $.vInfo[vId].hook = HOOK_MODULE_INSTALLED_NO_HOOK; - ++$.vInfo[vId].nonce; - return; + /// @notice Marks a validation as installed and initializes its allowed selectors. + function _initializeValidation(ValidationId vId, bytes calldata selectors) internal { + ValidationInfo storage info = _validationStorage().vInfo[vId]; + require(!info.installed, OccupiedValidationId()); + info.installed = true; + if (selectors.length == 0) { + // Invalidate selector grants from any prior installation of this ValidationId. + ++info.nonce; + } else { + _grantAccess(vId, selectors); } - // if not, first 20 bytes is the hook address - address hook = address(bytes20(_internalData[0:20])); - require( - hook == HOOK_MODULE_NOT_INSTALLED || hook == HOOK_MODULE_INSTALLED_NO_HOOK || _hookEnabled(IHook(hook)), - NotInstalled() - ); - $.vInfo[vId].hook = hook == HOOK_MODULE_NOT_INSTALLED ? HOOK_MODULE_INSTALLED_NO_HOOK : hook; - _internalData = _internalData[20:]; - // _grantAccess bumps nonce by 1 and writes `allowed[vId][sel] = nonce` for each - // selector, so non-empty installs also end with nonce = previous + 1. - _grantAccess(vId, _internalData); } /// @notice Installs a validator module and initializes its validation storage. /// @param _validator The validator module address. - /// @param _internalData Hook address (20 bytes) + packed selectors. + /// @param _internalData Packed bytes4 selectors. /// @param _installSuccess Whether the module's onInstall call succeeded. function _installValidator(address _validator, bytes calldata _internalData, bool _installSuccess) internal { require(_installSuccess, ModuleInstallFailed()); @@ -176,7 +136,7 @@ abstract contract ValidationManager { /// @dev Must be installed after all policies for the same PermissionId. Finalizes the permission by /// initializing validation and resetting the transient installingPermission. /// @param _signer The signer module address. - /// @param _internalData PermissionId (4 bytes) + hook/selectors data for _initializeValidation. + /// @param _internalData PermissionId (4 bytes) followed by packed bytes4 selectors. /// @param _installSuccess Whether the module's onInstall call succeeded. function _installSigner(address _signer, bytes calldata _internalData, bool _installSuccess) internal { ValidationInfo storage $ = _checkPermissionInstall(_internalData, _installSuccess); @@ -194,6 +154,7 @@ abstract contract ValidationManager { internal returns (ValidationInfo storage $) { + require(_internalData.length >= 4, InvalidDataLength()); require(_installSuccess, ModuleInstallFailed()); ValidationId vId = permissionToIdentifier(PermissionId.wrap(bytes4(_internalData[0:4]))); $ = _validationStorage().vInfo[vId]; @@ -205,12 +166,12 @@ abstract contract ValidationManager { } } - /// @notice Marks a validation as uninstalled by zeroing its hook. Cannot uninstall root. + /// @notice Marks a validation as uninstalled. Cannot uninstall root. /// @param _vId The validation identifier to uninstall. function _uninstallValidation(ValidationId _vId) internal { ValidationStorage storage $ = _validationStorage(); require($.root != _vId, CannotUninstallRoot()); - $.vInfo[_vId].hook = HOOK_MODULE_NOT_INSTALLED; + $.vInfo[_vId].installed = false; } /// @notice Uninstalls a validator module. @@ -286,7 +247,7 @@ abstract contract ValidationManager { } ValidationInfo storage info = _validationStorage().vInfo[v]; - require(info.hook > HOOK_MODULE_NOT_INSTALLED, InvalidVid(v)); + require(info.installed, InvalidVid(v)); if (vType == VALIDATION_TYPE_PERMISSION) { validateUserOp = _validateUserOpPermission; @@ -311,7 +272,7 @@ abstract contract ValidationManager { _verifyFallbackSignature(_hash, _signature) ? SIG_VALIDATION_SUCCESS_UINT : SIG_VALIDATION_FAILED_UINT; } ValidationInfo storage vInfo = _validationStorage().vInfo[vId]; - require(vInfo.hook > HOOK_MODULE_NOT_INSTALLED, InvalidVid(vId)); + require(vInfo.installed, InvalidVid(vId)); ValidationType vType = getType(vId); if (vType == VALIDATION_TYPE_VALIDATOR) { IValidator validator = getValidator(vId); @@ -471,7 +432,7 @@ abstract contract ValidationManager { // Require the validation to actually be installed before promoting it to root. // The fallback path (vId == bytes21(0)) is exempt since it has no install step. if (ValidationId.unwrap(vId) != bytes21(0)) { - require($.vInfo[vId].hook > HOOK_MODULE_NOT_INSTALLED, InvalidVid(vId)); + require($.vInfo[vId].installed, InvalidVid(vId)); } // Invalidate stale grants on the previous root when rotating. The first install // (oldRoot zero) and identity rotation (oldRoot == newRoot) are no-ops. diff --git a/src/interfaces/IERC7579Modules.sol b/src/interfaces/IERC7579Modules.sol index 084bd672..0132ac18 100644 --- a/src/interfaces/IERC7579Modules.sol +++ b/src/interfaces/IERC7579Modules.sol @@ -60,15 +60,6 @@ interface IValidator is IModule { interface IExecutor is IModule {} -interface IHook is IModule { - function preCheck(address msgSender, uint256 msgValue, bytes calldata msgData) - external - payable - returns (bytes memory hookData); - - function postCheck(bytes calldata hookData) external payable; -} - interface IFallback is IModule {} interface IPolicy is IModule { diff --git a/src/types/Constants.sol b/src/types/Constants.sol index a7954146..07ded92b 100644 --- a/src/types/Constants.sol +++ b/src/types/Constants.sol @@ -14,7 +14,6 @@ CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); uint256 constant MODULE_TYPE_VALIDATOR = 1; uint256 constant MODULE_TYPE_EXECUTOR = 2; uint256 constant MODULE_TYPE_FALLBACK = 3; -uint256 constant MODULE_TYPE_HOOK = 4; uint256 constant MODULE_TYPE_POLICY = 5; uint256 constant MODULE_TYPE_SIGNER = 6; uint256 constant MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER = 10; @@ -34,8 +33,6 @@ bytes32 constant SELECTOR_MANAGER_STORAGE_SLOT = 0x550d18e77e0b3e646dcc27a9961c7 bytes32 constant MODULE_MANAGER_STORAGE_SLOT = 0x9bc558e75ed0a57385e96d6b87fd2864d462eed29668be6fed742168fd90ab0f; ///@custom:storage-location bytes32(uint256(keccak256('kernel.v4.executor')) - 1) bytes32 constant EXECUTOR_MANAGER_STORAGE_SLOT = 0xc98f19fae81314cbf0302e1e3c0554f60c259fab8e2d5d392893489d40eb0045; -///@custom:storage-location bytes32(uint256(keccak256('kernel.v4.hook'))-1) -bytes32 constant HOOK_MANAGER_STORAGE_SLOT = 0x5419def70c6ad54339f14ca6da31808409bec8ff0f178491c5b59f0d8276d4d3; ///@custom:storage-location bytes32(uint256(keccak256('kernel.v4.validation')) - 1) bytes32 constant VALIDATION_MANAGER_STORAGE_SLOT = 0xded5d420c407eac3c615e6abe13ab4a0bd7173e5045ea543765b46f0df6e260c; ///@custom:storage-location bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) @@ -45,9 +42,6 @@ bytes4 constant ERC1271_INVALID = 0xffffffff; uint256 constant SIG_VALIDATION_FAILED_UINT = 1; uint256 constant SIG_VALIDATION_SUCCESS_UINT = 0; -address constant HOOK_MODULE_NOT_INSTALLED = address(0); -address constant HOOK_MODULE_INSTALLED_NO_HOOK = address(1); - //InstallPackages(uint256 nonce,Install[] packages)Install(uint256 moduleType,address module,bytes moduleData,bytes internalData) bytes32 constant INSTALL_PACKAGES_STRUCT_HASH = 0x633d6810f7f4053622dad4c187707d9c3cd7f57b8b68943473d3437060aefc6d; //keccak256("Install(uint256 moduleType,address module,bytes moduleData,bytes internalData)"), diff --git a/src/types/Error.sol b/src/types/Error.sol index 5bfc688a..80bb39db 100644 --- a/src/types/Error.sol +++ b/src/types/Error.sol @@ -15,9 +15,6 @@ error InvalidValidator(); /// @notice Thrown when an unsupported module type is encountered. error NotImplemented(); -/// @notice Thrown when a required module (hook) is not installed. -error NotInstalled(); - /// @notice Thrown when an install-mode signature fails verification. error InstallSignatureVerificationFailed(); @@ -63,7 +60,7 @@ error CannotUninstallRoot(); /// @notice Thrown when a signature is invalid or has the wrong number of sub-signatures. error InvalidSignature(); -/// @notice Thrown when a ValidationId is not installed (hook == address(0)). +/// @notice Thrown when a ValidationId is not installed. /// @param vId The invalid validation identifier. error InvalidVid(ValidationId vId); diff --git a/src/types/Events.sol b/src/types/Events.sol index 9c13611c..c3e5aa0d 100644 --- a/src/types/Events.sol +++ b/src/types/Events.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; /// @notice Emitted when a module is installed on the account. -/// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 4=hook, 5=policy, 6=signer). +/// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). /// @param module The address of the installed module. event ModuleInstalled(uint256 moduleType, address module); diff --git a/src/types/Structs.sol b/src/types/Structs.sol index 50ee167d..0c2967a8 100644 --- a/src/types/Structs.sol +++ b/src/types/Structs.sol @@ -2,19 +2,18 @@ pragma solidity ^0.8.0; import {ValidationId, CallType} from "./Types.sol"; -import {IHook, IExecutor} from "../interfaces/IERC7579Modules.sol"; +import {IExecutor} from "../interfaces/IERC7579Modules.sol"; /// @notice Describes a module installation: the module type, address, and its data payloads. /// @dev The `moduleData` is forwarded to the module's onInstall/onUninstall callback. /// The `internalData` configures Kernel-internal state and its format varies by module type: -/// - Validators (type 1): `[bytes20 hookAddress | bytes4[] allowedSelectors]` -/// - Executors (type 2): `[bytes20 hookAddress]` -/// - Fallback/Selectors (type 3): `[bytes4 selector | bytes1 callType | bytes20 hookAddress]` -/// - Hooks (type 4): ignored (empty OK) +/// - Validators (type 1): `[bytes4[] allowedSelectors]` +/// - Executors (type 2): ignored (empty OK) +/// - Fallback/Selectors (type 3): `[bytes4 selector | bytes1 callType]` /// - Policies (type 5): `[bytes4 permissionId | ...]` -/// - Signers (type 6): `[bytes4 permissionId | bytes20 hookAddress | bytes4[] allowedSelectors]` +/// - Signers (type 6): `[bytes4 permissionId | bytes4[] allowedSelectors]` struct Install { - /// @dev The module type identifier (1=validator, 2=executor, 3=fallback, 4=hook, 5=policy, 6=signer). + /// @dev The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). uint256 moduleType; /// @dev The module contract address. address module; @@ -24,12 +23,12 @@ struct Install { bytes internalData; } -/// @notice Stores per-validation state: nonce for selector access, hook, signer, and policies. +/// @notice Stores per-validation state: installation, selector nonce, signer, and policies. struct ValidationInfo { /// @dev Incremented when selectors are (re)granted; used to invalidate old selector allowances. uint32 nonce; - /// @dev The hook address for this validation. address(0) = not installed, address(1) = installed with no hook. - address hook; + /// @dev Whether this validator or permission is installed. + bool installed; /// @dev The signer module address (only for permission-based validations). address signer; /// @dev Array of policy module addresses (only for permission-based validations). @@ -84,8 +83,6 @@ struct PermissionUninstallData { /// @notice Fallback selector routing configuration. struct SelectorConfig { - /// @dev The hook module for this selector. address(0) = not installed, address(1) = no hook. - IHook hook; /// @dev The fallback module that handles calls to this selector. address target; /// @dev The call type: CALLTYPE_SINGLE (0x00) for call, CALLTYPE_DELEGATECALL (0xFF) for delegatecall. @@ -98,10 +95,10 @@ struct SelectorStorage { mapping(bytes4 => SelectorConfig) selectorConfig; } -/// @notice Configuration for an installed executor module. +/// @notice Configuration for an executor module. struct ExecutorConfig { - /// @dev The hook for this executor. address(1) = installed with no hook, address(0) = not installed. - IHook hook; + /// @dev Whether the executor is installed. + bool installed; } /// @notice Storage for all executor configurations. @@ -110,12 +107,6 @@ struct ExecutorStorage { mapping(IExecutor => ExecutorConfig) executorConfig; } -/// @notice Storage tracking which hook modules are enabled. -struct HookStorage { - /// @dev Maps hook address to enabled status. - mapping(address => bool) enabled; -} - /// @notice Storage for module-level nonce management and optional registry. struct ModuleStorage { /// @dev ERC-7484 module registry address (reserved for future use, not used in vanilla Kernel). From 7dd9c00e155d5a99f7c8cadf083b4a90f21a421d Mon Sep 17 00:00:00 2001 From: taek Date: Wed, 5 Aug 2026 09:48:32 +0900 Subject: [PATCH 3/8] feat: remove enable mode from ERC-1271 Remove stateless ERC-1271 module installation and the validation-mode signature byte. Structured signatures now begin with their validation type while ERC-4337 enable mode remains supported. --- src/core/ModuleManager.sol | 151 +++++------------------------ src/interfaces/IERC7579Modules.sol | 9 -- src/lib/Lib4337.sol | 6 +- src/types/Constants.sol | 1 - src/types/Error.sol | 6 -- src/types/Structs.sol | 2 +- 6 files changed, 28 insertions(+), 147 deletions(-) diff --git a/src/core/ModuleManager.sol b/src/core/ModuleManager.sol index d4d6650a..80a4b084 100644 --- a/src/core/ModuleManager.sol +++ b/src/core/ModuleManager.sol @@ -1,33 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IModule, IValidator, IStatelessValidatorWithSender} from "../interfaces/IERC7579Modules.sol"; +import {IModule, IValidator} from "../interfaces/IERC7579Modules.sol"; import {ValidationManager} from "./ValidationManager.sol"; import {ExecutorManager} from "./ExecutorManager.sol"; import {SelectorManager} from "./SelectorManager.sol"; import {ERC1271} from "../lib/ERC1271.sol"; -import { - InvalidValidationType, - InvalidNonce, - InvalidValidator, - InvalidPermissionId, - InvalidSignature, - NotImplemented, - PermissionInstallNotFinished, - LastSignatureShouldBeSigner -} from "../types/Error.sol"; +import {InvalidValidationType, InvalidNonce, NotImplemented, PermissionInstallNotFinished} from "../types/Error.sol"; import {ModuleInstalled, ModuleUninstalled} from "../types/Events.sol"; -import {Install, EnableModeSignature, ModuleStorage, PermissionSignature} from "../types/Structs.sol"; -import { - ValidationId, - ValidationMode, - ValidationType, - PermissionId, - isEnable, - isEnableReplayable -} from "../types/Types.sol"; +import {Install, ModuleStorage} from "../types/Structs.sol"; +import {ValidationId, ValidationType, PermissionId} from "../types/Types.sol"; import {Lib4337} from "../lib/Lib4337.sol"; -import {getType, getValidator, getPermissionId, validatorToIdentifier, permissionToIdentifier} from "../lib/Utils.sol"; +import {validatorToIdentifier, permissionToIdentifier} from "../lib/Utils.sol"; import { MODULE_MANAGER_STORAGE_SLOT, VALIDATION_TYPE_ROOT, @@ -92,47 +76,26 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM override returns (bool result) { - // check if fallback signature is allowed - if (_erc1271RawAllowed()) { - result = _verifyFallbackSignature(hash, signature); - } - if (!result) { - ValidationMode vMode = ValidationMode.wrap(bytes1(signature[0])); - ValidationType vType = ValidationType.wrap(bytes1(signature[1])); - ValidationId vId; - if (vType == VALIDATION_TYPE_ROOT) { - vId = _validationStorage().root; - signature = signature[2:]; - } else if (vType == VALIDATION_TYPE_VALIDATOR) { - vId = validatorToIdentifier(IValidator(address(bytes20(signature[2:22])))); - signature = signature[22:]; - } else if (vType == VALIDATION_TYPE_PERMISSION) { - vId = permissionToIdentifier(PermissionId.wrap(bytes4(signature[2:6]))); - signature = signature[6:]; - } else { - revert InvalidValidationType(); - } - uint256 validationData; - if (isEnable(vMode)) { - require(vType != VALIDATION_TYPE_ROOT, InvalidValidationType()); - bool enableReplayable = isEnableReplayable(vMode); - EnableModeSignature calldata sig; - assembly { - sig := signature.offset - } - if (!Lib4337.checkValidation( - _verifyInstallSignatureRaw(enableReplayable, sig.nonce, sig.packages, sig.enableSignature) - )) { - // if enable sig is invalid, short circuit - return false; - } - _checkNonce(sig.nonce); - return _verifyStatelessSignature(sig.packages, vId, hash, sig.userOpSignature); - } else { - validationData = _verifySignature(vId, msg.sender, hash, signature); - } - result = Lib4337.checkValidation(validationData); + bool rawAllowed = _erc1271RawAllowed(); + if (rawAllowed && _verifyFallbackSignature(hash, signature)) return true; + if (signature.length == 0) return false; + ValidationType vType = ValidationType.wrap(bytes1(signature[0])); + ValidationId vId; + if (vType == VALIDATION_TYPE_ROOT) { + vId = _validationStorage().root; + signature = signature[1:]; + } else if (vType == VALIDATION_TYPE_VALIDATOR) { + if (signature.length < 21) return false; + vId = validatorToIdentifier(IValidator(address(bytes20(signature[1:21])))); + signature = signature[21:]; + } else if (vType == VALIDATION_TYPE_PERMISSION) { + if (signature.length < 5) return false; + vId = permissionToIdentifier(PermissionId.wrap(bytes4(signature[1:5]))); + signature = signature[5:]; + } else { + revert InvalidValidationType(); } + result = Lib4337.checkValidation(_verifySignature(vId, msg.sender, hash, signature)); } /// @notice Computes the EIP-712 hash of an array of Install packages. @@ -347,70 +310,4 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM hashTypedData(EfficientHashLib.hash(INSTALL_PACKAGES_STRUCT_HASH, bytes32(_nonce), _installHash(packages))); return _verifySignature(vId, address(this), digest, signature); } - - /// @notice Verifies a stateless signature for enable-mode ERC-1271 flows. - /// @dev Locates the validator/permission modules in the packages and calls their stateless verify. - /// @param packages The install packages containing the modules to verify against. - /// @param vId The validation identifier to use. - /// @param hash The hash to verify. - /// @param signature The signature bytes. - /// @return True if the stateless signature verification succeeds. - function _verifyStatelessSignature( - Install[] calldata packages, - ValidationId vId, - bytes32 hash, - bytes calldata signature - ) internal view returns (bool) { - ValidationType vType = getType(vId); - if (vType == VALIDATION_TYPE_VALIDATOR) { - IValidator validator = getValidator(vId); - uint256 i; - for (i; i < packages.length; i++) { - Install calldata pkg = packages[i]; - if (pkg.moduleType == MODULE_TYPE_VALIDATOR && pkg.module == address(validator)) { - break; - } - } - require(i < packages.length, InvalidValidator()); - return IStatelessValidatorWithSender(address(validator)) - .validateSignatureWithDataWithSender(msg.sender, hash, signature, packages[i].moduleData); - } else if (vType == VALIDATION_TYPE_PERMISSION) { - PermissionId pId = getPermissionId(vId); - PermissionSignature calldata permissionSig; - assembly { - permissionSig := signature.offset - } - require(permissionSig.signatures.length > 0, InvalidSignature()); - uint256 sigIdx; - for (uint256 i; i < packages.length; i++) { - Install calldata pkg = packages[i]; - // Restrict matching to policy (5) / signer (6) modules. Otherwise a - // package of a different module type (e.g. selector type 3 or hook type 4) - // whose internalData happens to start with `pId` would be enrolled into the - // permission's signature chain. - if (PermissionId.wrap(bytes4(pkg.internalData)) == pId && (pkg.moduleType == 5 || pkg.moduleType == 6)) - { - if (sigIdx == permissionSig.signatures.length - 1) { - require(pkg.moduleType == MODULE_TYPE_SIGNER, LastSignatureShouldBeSigner()); - require(IModule(pkg.module).isModuleType(MODULE_TYPE_SIGNER), LastSignatureShouldBeSigner()); - } - bool res = IStatelessValidatorWithSender(pkg.module) - .validateSignatureWithDataWithSender( - msg.sender, - hash, - permissionSig.signatures[sigIdx], - pkg.moduleData // NOTE: not passing the permissionId as stateless does not need any permissionId - ); - if (!res) { - return false; - } - sigIdx++; - } - } - require(sigIdx == permissionSig.signatures.length, InvalidPermissionId()); - return true; - } else { - revert InvalidValidationType(); - } - } } diff --git a/src/interfaces/IERC7579Modules.sol b/src/interfaces/IERC7579Modules.sol index 0132ac18..ccf84c2a 100644 --- a/src/interfaces/IERC7579Modules.sol +++ b/src/interfaces/IERC7579Modules.sol @@ -77,12 +77,3 @@ interface ISigner is IModule { returns (uint256); function checkSignature(bytes32 id, address sender, bytes32 hash, bytes calldata sig) external view returns (bytes4); } - -interface IStatelessValidatorWithSender is IModule { - function validateSignatureWithDataWithSender( - address sender, - bytes32 hash, - bytes calldata signature, - bytes calldata data - ) external view returns (bool); -} diff --git a/src/lib/Lib4337.sol b/src/lib/Lib4337.sol index 5e0203b1..b1fa2b01 100644 --- a/src/lib/Lib4337.sol +++ b/src/lib/Lib4337.sol @@ -9,7 +9,7 @@ import {DOMAIN_TYPEHASH_SANS_CHAIN_ID} from "../types/Constants.sol"; import {ValidityFormatMismatch} from "../types/Error.sol"; library Lib4337 { - /// @dev Highest bit of uint48, indicates block number mode when set on both validAfter and validUntil + /// @dev EntryPoint v0.9 flag for block number mode. uint48 internal constant MODE_BIT = 0x800000000000; function chainAgnosticUserOpHash(address ep, PackedUserOperation calldata userOp) internal view returns (bytes32) { @@ -73,9 +73,9 @@ library Lib4337 { return _intersectValidationData(a, b); } - /// @dev Returns true if validation data uses block number format (both validAfter and validUntil have MODE_BIT set) + /// @dev Returns true if validation data uses block number format per EntryPoint v0.9. function _usesBlockNumberFormat(uint48 validAfter, uint48 validUntil) internal pure returns (bool) { - return (validAfter & MODE_BIT != 0) && (validUntil & MODE_BIT != 0); + return validAfter > MODE_BIT && validUntil > MODE_BIT; } function _intersectValidationData(uint256 preValidationData, uint256 validationRes) diff --git a/src/types/Constants.sol b/src/types/Constants.sol index 07ded92b..152599a2 100644 --- a/src/types/Constants.sol +++ b/src/types/Constants.sol @@ -16,7 +16,6 @@ uint256 constant MODULE_TYPE_EXECUTOR = 2; uint256 constant MODULE_TYPE_FALLBACK = 3; uint256 constant MODULE_TYPE_POLICY = 5; uint256 constant MODULE_TYPE_SIGNER = 6; -uint256 constant MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER = 10; // note : ROOT == FALLBACK, they do indicate same value but to have different meanings in different context // FALLBACK - usually used when using 7702 validation logic diff --git a/src/types/Error.sol b/src/types/Error.sol index 80bb39db..e12b36d9 100644 --- a/src/types/Error.sol +++ b/src/types/Error.sol @@ -9,9 +9,6 @@ error ImplementationNotDeployed(); /// @notice Thrown when a module's onInstall callback fails. error ModuleInstallFailed(); -/// @notice Thrown when a validator cannot be found in the install packages during stateless verification. -error InvalidValidator(); - /// @notice Thrown when an unsupported module type is encountered. error NotImplemented(); @@ -73,9 +70,6 @@ error PermissionInstallNotFinished(); /// @notice Thrown when policies and signer within a batch use inconsistent PermissionIds. error InvalidPermissionInstall(); -/// @notice Thrown when the last signature in a stateless permission verification is not from a signer module. -error LastSignatureShouldBeSigner(); - /// @notice Thrown when a zero-address signer is provided to the ECDSA factory. error InvalidSigner(); diff --git a/src/types/Structs.sol b/src/types/Structs.sol index 0c2967a8..6ef41dd3 100644 --- a/src/types/Structs.sol +++ b/src/types/Structs.sol @@ -63,7 +63,7 @@ struct EnableModeSignature { Install[] packages; /// @dev The root validator's signature authorizing the install. bytes enableSignature; - /// @dev The actual userOp or ERC-1271 signature (after the enable portion). + /// @dev The actual UserOperation signature after the enable portion. bytes userOpSignature; } From 274affb33bec15315833fccdb4abe261b3d05d7c Mon Sep 17 00:00:00 2001 From: taek Date: Wed, 5 Aug 2026 09:48:58 +0900 Subject: [PATCH 4/8] feat: introduce ScopedExecutionHook Add type-11 hooks scoped to validations, executors, and selectors, with explicit scoped identifiers and lifecycle checks. Route hooked validation execution through executeUserOp and prevent direct validation reentry. --- README.md | 110 ++++++++--------------- src/Kernel.sol | 138 +++++++++++++++++++++++------ src/core/ExecutorManager.sol | 8 +- src/core/ModuleManager.sol | 109 +++++++++++++++++++++-- src/core/SelectorManager.sol | 13 ++- src/core/ValidationManager.sol | 70 ++++++++++++++- src/interfaces/IERC7579Modules.sol | 18 ++++ src/lib/Utils.sol | 49 +++++++++- src/types/Constants.sol | 5 ++ src/types/Error.sol | 9 ++ src/types/Events.sol | 2 +- src/types/Structs.sol | 19 ++-- 12 files changed, 426 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 24c6eba2..6709e446 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ # Kernel v4 -ERC-4337 / ERC-7702 modular smart account with pluggable validation, execution, and hook modules. Implements [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) for standardized module interfaces. +ERC-4337 / ERC-7702 modular smart account with pluggable validation and execution modules. Implements [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) for standardized module interfaces. ## Key Features ### Modular Architecture (ERC-7579) -Six pluggable module types that can be installed and uninstalled at runtime: +Six supported module types can be installed and uninstalled at runtime: | Type | Role | -|------|------| +| ------ | ------ | | **Validator** | Validates UserOps and signatures — owns a nonce key namespace | | **Executor** | Calls `executeFromExecutor` to perform actions on behalf of the account | | **Fallback** | Extends the account with new function selectors (call or delegatecall) | -| **Hook** | Pre/post execution checks on validators, executors, and fallback selectors | | **Policy** | Part of a permission — enforces rules (e.g. spending limits, target allowlists) | -| **Signer** | Part of a permission — provides the signature verification (e.g. passkey, multisig) | +| **Signer** | Part of a permission — provides signature verification (e.g. passkey, multisig) | +| **Scoped Execution Hook** | Optional pre/post checks scoped to one validation, executor, or selector (type 11) | ### Permission System @@ -35,29 +35,26 @@ Install modules atomically with the first UserOp — no separate setup transacti The 32-byte ERC-4337 nonce encodes which validator to use, giving each validator/permission its own nonce namespace. See [Data Encoding > UserOp Nonce](#userop-nonce) for the full layout. -### Hook System +### Scoped Execution Hooks -Hooks provide pre/post execution checks. They bind to validators, executors, and fallback selectors independently: +An optional type-11 scoped execution hook runs before and after execution in one of three scopes: validation, executor, or selector. Hooked non-root validations are routed through `executeUserOp`; root validations bypass hooks. Executor hooks wrap `executeFromExecutor`, and selector hooks wrap fallback selector dispatch. Selector hooks apply only to calls that reach Kernel's fallback; native Kernel function dispatch bypasses them, and built-in token-receiver selectors cannot be installed as fallback targets. -- **Validator hook** — Runs around `executeUserOp` when a non-root validator with a hook is used -- **Executor hook** — Runs around `executeFromExecutor` for any installed executor -- **Fallback hook** — Runs around fallback selector dispatch - -Two sentinel values: `address(0)` = not installed, `address(1)` = installed with no hook. +`preCheck` and `postCheck` receive the same Kernel-generated `bytes32 id`. The ID layout is `[bytes1 scope][target][zero padding]`, where the target is a 21-byte ValidationId, 20-byte executor address, or 4-byte selector. `Utils.sol` exposes generation and decoding helpers for every scope. ### Signature Verification (ERC-1271 / ERC-7739) Three signature modes for `isValidSignature`: + 1. **Raw** — Direct hash signing (only on `Kernel7702` where the EOA is the signer) 2. **Chain-specific nested EIP-712** — Wraps the hash in a `TypedDataSign` struct bound to chain ID 3. **Replayable nested EIP-712** — Same wrapping but without chain ID, valid across chains -All modes support both validator-based and permission-based signature verification, selected by the first 21 bytes of the signature. +Structured signatures select root, validator, or permission validation through the leading `vType` and optional ID. Raw Kernel7702 signatures have no validation header. ### Standards | Standard | Support | -|----------|---------| +| ---------- | --------- | | [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) | Account abstraction via EntryPoint v0.9 | | [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) | Modular smart account interfaces | | [ERC-7702](https://eips.ethereum.org/EIPS/eip-7702) | EOA code delegation (`Kernel7702`) | @@ -79,7 +76,7 @@ The 32-byte ERC-4337 nonce encodes the validation mode, type, and identifier: **vMode** (ValidationMode flags): | Value | Meaning | -|-------|---------| +| ------- | --------- | | `0x00` | Standard — chain-specific, no inline install | | `0x08` | Enable — install modules inline, chain-specific enable signature | | `0x0C` | Enable + replayable enable signature | @@ -90,7 +87,7 @@ The 32-byte ERC-4337 nonce encodes the validation mode, type, and identifier: **vType** (ValidationType): | Value | Meaning | vId contains | -|-------|---------|-------------| +| ------- | --------- | ------------- | | `0x00` | Root / Fallback | Ignored (uses stored root) | | `0x01` | Validator | 20-byte validator address | | `0x02` | Permission | 4-byte PermissionId (left-aligned, rest zero) | @@ -143,44 +140,19 @@ If the enable-replayable flag (0x04) is set, the digest uses the chain-agnostic After ERC-6492 unwrapping, the signature is parsed as: ``` -| 1 byte | 1 byte | N bytes | remaining bytes | -| vMode | vType | vId | inner signature | +| 1 byte | N bytes | remaining bytes | +| vType | vId | inner signature | ``` Where N depends on vType: | vType | N | vId content | -|-------|---|-------------| -| `0x00` (root) | 0 | Uses stored root, inner = `signature[2:]` | -| `0x01` (validator) | 20 | Validator address, inner = `signature[22:]` | -| `0x02` (permission) | 4 | PermissionId, inner = `signature[6:]` | - -#### Standard Mode (no enable flag) - -The inner signature is verified via `_verifySignature` against the installed validator or permission, same as UserOp standard mode. - -#### Enable Mode for ERC-1271 - -Since `isValidSignature` is a `view` function, enable mode works differently than in UserOps — it **cannot** modify state (no module installation, no nonce increment). Instead it: - -1. Verifies the install signature is valid (same digest as UserOp enable mode) -2. Checks the nonce is correct (view-only, no increment) -3. Uses **stateless** verification — finds the validator/permission modules inside the `packages` array and calls `IStatelessValidatorWithSender.validateSignatureWithDataWithSender` instead of the normal installed module +| ------- | --- | ------------- | +| `0x00` (root) | 0 | Uses stored root, inner = `signature[1:]` | +| `0x01` (validator) | 20 | Validator address, inner = `signature[21:]` | +| `0x02` (permission) | 4 | PermissionId, inner = `signature[5:]` | -The inner signature format is the same `EnableModeSignature`: - -``` -abi.encode(EnableModeSignature({ - nonce: uint256, - packages: Install[], - enableSignature: bytes, // root validator's signature over the install digest - userOpSignature: bytes // verified statelessly against modules in packages -})) -``` - -For permission-based enable mode, `userOpSignature` is a `PermissionSignature` — one signature per policy/signer found in the packages with the matching PermissionId. - -> **Note:** vType cannot be root (`0x00`) in enable mode — it must specify an explicit validator or permission. +The inner signature is verified via `_verifySignature` against the installed validator or permission. ERC-1271 has no validation-mode byte and does not support enable mode; validation modes remain part of ERC-4337 UserOperation nonces only. #### Nested EIP-712 Wrapping @@ -215,21 +187,15 @@ abi.encode(InstallModuleDataFormat({ #### internalData for Install | Module Type | internalData format | -|-------------|---------------------| -| Validator (1) | `[bytes20 hookAddress][bytes4 selector₁][bytes4 selector₂]...` | -| Executor (2) | `[bytes20 hookAddress]` | -| Fallback (3) | `[bytes4 selector][bytes1 callType][bytes20 hookAddress]` | -| Hook (4) | Ignored (empty OK) | +| ------------- | --------------------- | +| Validator (1) | `[bytes4 selector₁][bytes4 selector₂]...` | +| Executor (2) | Empty (required) | +| Fallback (3) | Exactly `[bytes4 selector][bytes1 callType]` | | Policy (5) | `[bytes4 permissionId]` | -| Signer (6) | `[bytes4 permissionId][bytes20 hookAddress][bytes4 selector₁]...` | - -**hookAddress** sentinel values: +| Signer (6) | `[bytes4 permissionId][bytes4 selector₁]...` | +| Scoped Execution Hook (11) | `[bytes1 scope][target]` (validation: 21-byte ValidationId; executor: 20-byte address; selector: 4-byte selector) | -| Address | Meaning | -|---------|---------| -| `address(0)` | Not installed / entry-point-only (for fallback: only EntryPoint can call) | -| `address(1)` | Installed with no hook | -| Other | Hook contract address (must be installed as hook module first) | +Execution-hook scopes are `0x01` for validation, `0x02` for executor, and `0x03` for selector. **callType** for fallback (type 3): @@ -241,13 +207,13 @@ abi.encode(InstallModuleDataFormat({ #### internalData for Uninstall | Module Type | internalData format | -|-------------|---------------------| +| ------------- | --------------------- | | Validator (1) | Ignored | -| Executor (2) | Ignored | -| Fallback (3) | `[bytes4 selector]` (first 4 bytes used) | -| Hook (4) | Ignored | +| Executor (2) | Empty (required) | +| Fallback (3) | Exactly `[bytes4 selector]` | | Policy (5) | `[bytes4 permissionId]` — must uninstall in LIFO order (last installed first) | -| Signer (6) | `[bytes4 permissionId]` — all policies must be uninstalled first | +| Signer (6) | `[bytes4 permissionId]` — all policies and its scoped execution hook must be removed first | +| Scoped Execution Hook (11) | `[bytes1 scope][target]` (same format as installation) | ### Batch Install via `Install[]` @@ -268,14 +234,14 @@ Each `Install` struct: ```solidity struct Install { - uint256 moduleType; // 1-6 + uint256 moduleType; // 1, 2, 3, 5, 6, or 11 address module; // module contract address bytes moduleData; // forwarded to onInstall bytes internalData; // kernel config (same format as table above) } ``` -**Permission install order**: When installing a permission, all policies (type 5) for that PermissionId must come first, followed by exactly one signer (type 6) with the same PermissionId. The signer finalizes the permission. Multiple permissions can be installed in a single batch — just ensure each permission's policies+signer are grouped together. +**Permission install order**: policies (type 5) come first, followed by exactly one signer (type 6), then an optional scoped execution hook (type 11), all sharing the same PermissionId. The hook targets the permission's full 21-byte ValidationId and requires the signer-completed permission to exist. Validator, executor, and selector hooks are installed after their respective targets. ## Architecture @@ -283,8 +249,7 @@ struct Install { Kernel (abstract) ├── ModuleManager │ ├── ValidationManager — Validator/permission lifecycle, enable-mode, nonce mgmt -│ ├── ExecutorManager — Executor install/uninstall with hook binding -│ ├── HookManager — Hook install/uninstall, pre/post check dispatch +│ ├── ExecutorManager — Executor install/uninstall state │ └── SelectorManager — Fallback handler routing by function selector ├── ExecutionManager — ERC-7579 execution modes (single/batch/delegatecall) └── ERC1271 — ERC-1271 / ERC-7739 signature verification @@ -304,11 +269,10 @@ Supporting contracts: All storage uses [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) namespaced slots to avoid collisions across modules and upgrades: | Manager | Slot | -|---------|------| +| --------- | ------ | | ValidationManager | `keccak256("kernel.v4.validation") - 1` | | ModuleManager | `keccak256("kernel.v4.module") - 1` | | ExecutorManager | `keccak256("kernel.v4.executor") - 1` | -| HookManager | `keccak256("kernel.v4.hook") - 1` | | SelectorManager | `keccak256("kernel.v4.selector") - 1` | ## Project Structure @@ -396,7 +360,7 @@ open coverage/index.html ## Dependencies | Package | Version | -|---------|---------| +| --------- | --------- | | [Solady](https://github.com/Vectorized/solady) | 0.1.26 | | [account-abstraction](https://github.com/eth-infinitism/account-abstraction) | v0.9.0 | | [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) | 5.4.0 | diff --git a/src/Kernel.sol b/src/Kernel.sol index 1aabd163..00c0f644 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -4,12 +4,20 @@ pragma solidity ^0.8.0; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; import {IERC7579Account} from "./interfaces/IERC7579Account.sol"; -import {IValidator, IExecutor, IModule} from "./interfaces/IERC7579Modules.sol"; +import {IValidator, IExecutor, IScopedExecutionHook, IModule} from "./interfaces/IERC7579Modules.sol"; import {ModuleManager, Install} from "./core/ModuleManager.sol"; import {ExecutionManager} from "./core/ExecutionManager.sol"; import {Lib4337} from "./lib/Lib4337.sol"; import {ERC1271} from "./lib/ERC1271.sol"; -import {parseNonce, getType, getValidator, validatorToIdentifier, permissionToIdentifier} from "./lib/Utils.sol"; +import { + parseNonce, + getType, + getValidator, + validatorToIdentifier, + permissionToIdentifier, + executorScopedExecutionHookId, + selectorScopedExecutionHookId +} from "./lib/Utils.sol"; import {LibERC7579} from "solady/accounts/LibERC7579.sol"; import { ValidationId, @@ -43,15 +51,17 @@ import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_POLICY, - MODULE_TYPE_SIGNER + MODULE_TYPE_SIGNER, + MODULE_TYPE_SCOPED_EXECUTION_HOOK } from "./types/Constants.sol"; import { ValidationStorage, ValidationInfo, + ExecutorConfig, EnableModeSignature, SelectorConfig, InstallModuleDataFormat, - PermissionUninstallData + ValidationUninstallData } from "./types/Structs.sol"; /// @title Kernel @@ -64,6 +74,10 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { require(msg.sender == address(ENTRYPOINT) || msg.sender == address(this), Unauthorized()); } + function _onlyEntryPoint() internal view { + require(msg.sender == address(ENTRYPOINT), Unauthorized()); + } + constructor(IEntryPoint _entrypoint) { ENTRYPOINT = _entrypoint; } @@ -99,7 +113,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { payable returns (uint256 validationData) { - _onlyEntryPointOrSelf(); + _onlyEntryPoint(); validationData = _processUserOp(userOp, userOpHash); assembly { if missingAccountFunds { @@ -126,6 +140,12 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { internal returns (uint256 validationData) { + bytes4 callDataSelector = bytes4(userOp.callData[0:4]); + // Block recursive validation both directly and through the executeUserOp wrapper. + require(callDataSelector != this.validateUserOp.selector, UnauthorizedCallData()); + if (callDataSelector == this.executeUserOp.selector && userOp.callData.length >= 8) { + require(bytes4(userOp.callData[4:8]) != this.validateUserOp.selector, UnauthorizedCallData()); + } /* userOp.nonce = vMode | vType | vId */ @@ -143,12 +163,8 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { sig := signature.offset } validationData = _verifyInstallSignatureRaw(enableReplayable, sig.nonce, sig.packages, sig.enableSignature); - // Root did not authorize this install -> surface the failure and install nothing. - // Compare only the failure field: a valid enable signature may carry nonzero - // validity bounds, so the full packed word must not be compared against 1. - // Without this guard, a call to validateUserOp outside the EntryPoint validation - // phase (where the returned validationData is ignored) would install modules and - // advance the nonce despite a failed root signature. + // EntryPoint owns validity-window enforcement. Reject hard signature failures before + // mutating state; EntryPoint rolls back installs for all other invalid results. if (uint160(validationData) == 1) { return validationData; } @@ -158,17 +174,22 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } ValidationStorage storage $ = _validationStorage(); - // Root is the unconditional recovery path and bypasses selector handling. + // Root is the unconditional recovery path and bypasses validation-scoped execution hooks. if (vType != VALIDATION_TYPE_ROOT) { ValidationInfo storage info = $.vInfo[vId]; require(info.installed, InvalidVid(vId)); - if (!_allowedSelector(vId, bytes4(userOp.callData[0:4]))) { + bool hasScopedExecutionHook = address(info.scopedExecutionHook) != address(0); + // Validation-scoped hooks must wrap execution even when the outer selector is directly allowed. + if (hasScopedExecutionHook || !_allowedSelector(vId, callDataSelector)) { require( - bytes4(userOp.callData[0:4]) == this.executeUserOp.selector + callDataSelector == this.executeUserOp.selector && _allowedSelector(vId, bytes4(userOp.callData[4:])), UnauthorizedCallData() ); } + if (hasScopedExecutionHook) { + _setValidationScopedExecutionHook(userOpHash, vId, info.scopedExecutionHook); + } } (vId, validateUserOpFn) = _checkValidation(vType, vId); bytes32 opHash = isReplayable(vMode) ? Lib4337.chainAgnosticUserOpHash(msg.sender, userOp) : userOpHash; @@ -176,16 +197,24 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { Lib4337.intersectValidationData(validationData, validateUserOpFn(vId, opHash, userOp, signature)); } - /// @notice Executes a user operation by delegatecalling its inner calldata. + /// @notice Executes a user operation with validation-hook context. + /// @dev Called by the entry point after validateUserOp. Runs pre/post hooks stored transiently + /// and delegatecalls the inner calldata (userOp.callData[4:]). /// @dev SECURITY: The inner calldata (userOp.callData[4:]) is delegatecalled to `address(this)` /// with no additional selector or target validation. Any function on Kernel (including /// privileged ones like `installModule`, `setRoot`, `execute`) can be invoked this way. /// Authorization relies entirely on `validateUserOp` having approved the outer UserOp. /// @param userOp The packed user operation containing the execution calldata. - /// @param userOpHash The hash of the user operation. + /// @param userOpHash The hash of the user operation, used to retrieve the transient validation hook. function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external payable { _onlyEntryPointOrSelf(); - userOpHash; + (ValidationId vId, IScopedExecutionHook hook) = _validationScopedExecutionHook(userOpHash); + bytes32 hookId; + bytes memory context; + if (address(hook) != address(0)) { + hookId = _validationScopedExecutionHookId(vId); + context = hook.preCheck(hookId, msg.sender, msg.value, userOp.callData[4:]); + } (bool success, bytes memory ret) = address(this).delegatecall(userOp.callData[4:]); // propagate the revert message if (!success) { @@ -193,6 +222,9 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { revert(add(ret, 0x20), mload(ret)) } } + if (address(hook) != address(0)) { + hook.postCheck(hookId, context); + } } /// @notice Executes a call according to the given ERC-7579 execution mode. @@ -220,8 +252,19 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { internal returns (bytes[] memory returnData) { - require(_executorConfig(IExecutor(msg.sender)).installed, Unauthorized()); - return _execute(mode, executionData); + ExecutorConfig storage config = _executorConfig(IExecutor(msg.sender)); + require(config.installed, Unauthorized()); + IScopedExecutionHook hook = config.scopedExecutionHook; + bytes32 id; + bytes memory context; + if (address(hook) != address(0)) { + id = executorScopedExecutionHookId(msg.sender); + context = hook.preCheck(id, msg.sender, msg.value, msg.data); + } + returnData = _execute(mode, executionData); + if (address(hook) != address(0)) { + hook.postCheck(id, context); + } } /// @dev SECURITY: When `callType` is `CALLTYPE_DELEGATECALL`, the fallback target executes @@ -246,6 +289,14 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { SelectorConfig storage $ = _selectorConfig(selector); require($.target != address(0), InvalidSelector()); + IScopedExecutionHook hook = $.scopedExecutionHook; + bytes32 id; + bytes memory context; + if (address(hook) != address(0)) { + id = selectorScopedExecutionHookId(selector); + context = hook.preCheck(id, msg.sender, msg.value, msg.data); + } + bool success; if ($.callType == CALLTYPE_SINGLE) { success = _call($.target, 0, abi.encodePacked(msg.data, msg.sender)); @@ -259,6 +310,9 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } else { res = _getReturn(); } + if (address(hook) != address(0)) { + hook.postCheck(id, context); + } } /// @notice Advances the nonce for a given key, invalidating all lower nonce values. @@ -278,7 +332,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { /// @notice Installs a single module per ERC-7579. /// @dev The initData is decoded as `InstallModuleDataFormat(bytes installData, bytes internalData)`. - /// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). + /// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer, 11=scoped execution hook). /// @param module The address of the module contract to install. /// @param initData ABI-encoded `InstallModuleDataFormat` containing install data and internal configuration. function installModule(uint256 moduleType, address module, bytes calldata initData) external payable override { @@ -321,21 +375,45 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { ValidationType vType = getType(vId); ValidationInfo memory vInfo = _validationStorage().vInfo[vId]; if (vType == VALIDATION_TYPE_VALIDATOR) { - (bool success,) = - address(getValidator(vId)).call(abi.encodeWithSelector(IModule.onUninstall.selector, uninstallData)); + bytes calldata validatorUninstallData = uninstallData; + if (address(vInfo.scopedExecutionHook) != address(0)) { + ValidationUninstallData calldata data; + assembly { + data := uninstallData.offset + } + require(data.uninstallData.length == 2, InvalidDataLength()); + validatorUninstallData = data.uninstallData[0]; + // forge-lint: disable-next-line(unchecked-call) + address(vInfo.scopedExecutionHook) + .call(abi.encodeWithSelector(IModule.onUninstall.selector, data.uninstallData[1])); + _uninstallScopedExecutionHookWithVid(address(vInfo.scopedExecutionHook), vId); + } + (bool success,) = address(getValidator(vId)) + .call(abi.encodeWithSelector(IModule.onUninstall.selector, validatorUninstallData)); _uninstallValidator( address(getValidator(vId)), - // passing in uninstallData here to use calldata, but it's never used - uninstallData, + // passing in validatorUninstallData here to use calldata, but it's never used + validatorUninstallData, success ); } else if (vType == VALIDATION_TYPE_PERMISSION) { - PermissionUninstallData calldata data; + ValidationUninstallData calldata data; assembly { data := uninstallData.offset } bytes[] calldata uninstallDataArr = data.uninstallData; - require(uninstallDataArr.length == vInfo.policies.length + 1, InvalidDataLength()); + uint256 hookOffset = address(vInfo.scopedExecutionHook) == address(0) ? 0 : 1; + require(uninstallDataArr.length == vInfo.policies.length + 1 + hookOffset, InvalidDataLength()); + if (hookOffset == 1) { + // forge-lint: disable-next-line(unchecked-call) + address(vInfo.scopedExecutionHook) + .call( + abi.encodeWithSelector( + IModule.onUninstall.selector, uninstallDataArr[vInfo.policies.length + 1] + ) + ); + _uninstallScopedExecutionHookWithVid(address(vInfo.scopedExecutionHook), vId); + } // uninstall policies first // NOTE : success is not checked on purpose as we are focusing on removing not actually calling onUninstall unchecked { @@ -420,17 +498,17 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { /// @notice Returns whether the given module type is supported. /// @param moduleTypeId The module type identifier. - /// @return True for validator, executor, fallback, policy, and signer modules. + /// @return True for validator, executor, fallback, policy, signer, and scoped-execution-hook modules. function supportsModule(uint256 moduleTypeId) external pure returns (bool) { return moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR || moduleTypeId == MODULE_TYPE_FALLBACK || moduleTypeId == MODULE_TYPE_POLICY - || moduleTypeId == MODULE_TYPE_SIGNER; + || moduleTypeId == MODULE_TYPE_SIGNER || moduleTypeId == MODULE_TYPE_SCOPED_EXECUTION_HOOK; } /// @notice Checks whether a specific module is currently installed. /// @param moduleTypeId The module type identifier. /// @param module The module address to check. - /// @param additionalContext For fallback modules: the bytes4 selector. For policies/signers: the bytes4 PermissionId. + /// @param additionalContext Selector, permission ID, or scoped execution-hook target context. /// @return True if the module is installed for the given type and context. function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external @@ -462,6 +540,8 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { ValidationId vId = permissionToIdentifier(PermissionId.wrap(bytes4(additionalContext))); ValidationInfo storage $ = _validationStorage().vInfo[vId]; return module != address(0) && $.signer == module; + } else if (moduleTypeId == MODULE_TYPE_SCOPED_EXECUTION_HOOK) { + return module != address(0) && _isScopedExecutionHookInstalled(module, additionalContext); } else { revert NotImplemented(); } diff --git a/src/core/ExecutorManager.sol b/src/core/ExecutorManager.sol index 941441e1..ab5d3323 100644 --- a/src/core/ExecutorManager.sol +++ b/src/core/ExecutorManager.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {EXECUTOR_MANAGER_STORAGE_SLOT} from "../types/Constants.sol"; import {IExecutor} from "../interfaces/IERC7579Modules.sol"; import {ExecutorStorage, ExecutorConfig} from "../types/Structs.sol"; -import {InvalidDataLength} from "../types/Error.sol"; +import {InvalidDataLength, ScopedExecutionHookStillInstalled} from "../types/Error.sol"; /// @title ExecutorManager /// @author taek @@ -26,7 +26,7 @@ abstract contract ExecutorManager { config = _executorStorage().executorConfig[executor]; } - /// @notice Installs an executor module without a hook. + /// @notice Installs an executor module. function _installExecutor(address _executor, bytes calldata _internalData, bool) internal { require(_internalData.length == 0, InvalidDataLength()); // Executor installation intentionally does not depend on onInstall success. @@ -36,6 +36,8 @@ abstract contract ExecutorManager { /// @notice Uninstalls an executor module. function _uninstallExecutor(address _executor, bytes calldata _internalData, bool) internal { require(_internalData.length == 0, InvalidDataLength()); - _executorConfig(IExecutor(_executor)).installed = false; + ExecutorConfig storage config = _executorConfig(IExecutor(_executor)); + require(address(config.scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); + config.installed = false; } } diff --git a/src/core/ModuleManager.sol b/src/core/ModuleManager.sol index 80a4b084..726c5d73 100644 --- a/src/core/ModuleManager.sol +++ b/src/core/ModuleManager.sol @@ -1,17 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IModule, IValidator} from "../interfaces/IERC7579Modules.sol"; +import {IModule, IValidator, IExecutor, IScopedExecutionHook} from "../interfaces/IERC7579Modules.sol"; import {ValidationManager} from "./ValidationManager.sol"; import {ExecutorManager} from "./ExecutorManager.sol"; import {SelectorManager} from "./SelectorManager.sol"; import {ERC1271} from "../lib/ERC1271.sol"; -import {InvalidValidationType, InvalidNonce, NotImplemented, PermissionInstallNotFinished} from "../types/Error.sol"; +import { + InvalidValidationType, + InvalidNonce, + NotImplemented, + PermissionInstallNotFinished, + InvalidDataLength, + InvalidScopedExecutionHookTarget, + ScopedExecutionHookAlreadyInstalled, + ModuleInstallFailed +} from "../types/Error.sol"; import {ModuleInstalled, ModuleUninstalled} from "../types/Events.sol"; -import {Install, ModuleStorage} from "../types/Structs.sol"; +import {Install, ModuleStorage, ExecutorConfig, SelectorConfig} from "../types/Structs.sol"; import {ValidationId, ValidationType, PermissionId} from "../types/Types.sol"; import {Lib4337} from "../lib/Lib4337.sol"; -import {validatorToIdentifier, permissionToIdentifier} from "../lib/Utils.sol"; +import {getType, validatorToIdentifier, permissionToIdentifier} from "../lib/Utils.sol"; import { MODULE_MANAGER_STORAGE_SLOT, VALIDATION_TYPE_ROOT, @@ -23,7 +32,11 @@ import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_POLICY, - MODULE_TYPE_SIGNER + MODULE_TYPE_SIGNER, + MODULE_TYPE_SCOPED_EXECUTION_HOOK, + SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, + SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE, + SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE } from "../types/Constants.sol"; import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; @@ -122,8 +135,88 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM return EfficientHashLib.hash(buffer); } + /// @notice Installs a scoped execution hook for a validation, executor, or selector. + /// @dev internalData is `[scope | target]`: 22 bytes for a ValidationId, + /// 21 bytes for an executor address, or 5 bytes for a selector. + function _installScopedExecutionHook(address hook, bytes calldata internalData, bool installSuccess) internal { + require(installSuccess && hook.code.length > 0, ModuleInstallFailed()); + bytes1 scope = _scopedExecutionHookScope(internalData); + if (scope == SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) { + require(internalData.length == 22, InvalidDataLength()); + ValidationId vId = ValidationId.wrap(bytes21(internalData[1:22])); + ValidationType vType = getType(vId); + require( + vType == VALIDATION_TYPE_VALIDATOR || vType == VALIDATION_TYPE_PERMISSION, + InvalidScopedExecutionHookTarget() + ); + _installValidationScopedExecutionHook(hook, vId, installSuccess); + } else if (scope == SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) { + require(internalData.length == 21, InvalidDataLength()); + IExecutor executor = IExecutor(address(bytes20(internalData[1:21]))); + ExecutorConfig storage config = _executorConfig(executor); + require(config.installed, InvalidScopedExecutionHookTarget()); + require(address(config.scopedExecutionHook) == address(0), ScopedExecutionHookAlreadyInstalled()); + config.scopedExecutionHook = IScopedExecutionHook(hook); + } else if (scope == SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) { + require(internalData.length == 5, InvalidDataLength()); + bytes4 selector = bytes4(internalData[1:5]); + SelectorConfig storage config = _selectorConfig(selector); + require(config.target != address(0), InvalidScopedExecutionHookTarget()); + require(address(config.scopedExecutionHook) == address(0), ScopedExecutionHookAlreadyInstalled()); + config.scopedExecutionHook = IScopedExecutionHook(hook); + } else { + revert InvalidScopedExecutionHookTarget(); + } + } + + /// @notice Uninstalls a scoped execution hook from a validation, executor, or selector. + function _uninstallScopedExecutionHook(address hook, bytes calldata internalData, bool) internal { + bytes1 scope = _scopedExecutionHookScope(internalData); + if (scope == SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) { + require(internalData.length == 22, InvalidDataLength()); + _uninstallScopedExecutionHookWithVid(hook, ValidationId.wrap(bytes21(internalData[1:22]))); + } else if (scope == SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) { + require(internalData.length == 21, InvalidDataLength()); + ExecutorConfig storage config = _executorConfig(IExecutor(address(bytes20(internalData[1:21])))); + require(address(config.scopedExecutionHook) == hook, InvalidScopedExecutionHookTarget()); + config.scopedExecutionHook = IScopedExecutionHook(address(0)); + } else if (scope == SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) { + require(internalData.length == 5, InvalidDataLength()); + SelectorConfig storage config = _selectorConfig(bytes4(internalData[1:5])); + require(address(config.scopedExecutionHook) == hook, InvalidScopedExecutionHookTarget()); + config.scopedExecutionHook = IScopedExecutionHook(address(0)); + } else { + revert InvalidScopedExecutionHookTarget(); + } + } + + function _scopedExecutionHookScope(bytes calldata internalData) private pure returns (bytes1 scope) { + require(internalData.length > 0, InvalidDataLength()); + scope = bytes1(internalData[0]); + } + + function _isScopedExecutionHookInstalled(address hook, bytes calldata context) internal view returns (bool) { + if (context.length == 0) return false; + bytes1 scope = bytes1(context[0]); + if (scope == SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) { + if (context.length != 22) return false; + ValidationId vId = ValidationId.wrap(bytes21(context[1:22])); + return address(_validationStorage().vInfo[vId].scopedExecutionHook) == hook; + } + if (scope == SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) { + if (context.length != 21) return false; + address executor = address(bytes20(context[1:21])); + return address(_executorConfig(IExecutor(executor)).scopedExecutionHook) == hook; + } + if (scope == SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) { + if (context.length != 5) return false; + return address(_selectorConfig(bytes4(context[1:5])).scopedExecutionHook) == hook; + } + return false; + } + /// @notice Routes a module installation to the appropriate type-specific handler. - /// @param moduleType The module type (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). + /// @param moduleType The module type (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer, 11=scoped execution hook). /// @param module The module address. /// @param moduleData Data forwarded to the module's onInstall callback. /// @param internalData Kernel-internal configuration data (format varies by module type). @@ -142,6 +235,8 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM hook = _installPolicy; } else if (moduleType == MODULE_TYPE_SIGNER) { hook = _installSigner; + } else if (moduleType == MODULE_TYPE_SCOPED_EXECUTION_HOOK) { + hook = _installScopedExecutionHook; } else { revert NotImplemented(); } @@ -171,6 +266,8 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM hook = _uninstallPolicy; } else if (moduleType == MODULE_TYPE_SIGNER) { hook = _uninstallSigner; + } else if (moduleType == MODULE_TYPE_SCOPED_EXECUTION_HOOK) { + hook = _uninstallScopedExecutionHook; } else { revert NotImplemented(); } diff --git a/src/core/SelectorManager.sol b/src/core/SelectorManager.sol index 0d5c4afd..30b183e5 100644 --- a/src/core/SelectorManager.sol +++ b/src/core/SelectorManager.sol @@ -3,7 +3,12 @@ pragma solidity ^0.8.0; import {CallType} from "../types/Types.sol"; import {SELECTOR_MANAGER_STORAGE_SLOT, CALLTYPE_DELEGATECALL} from "../types/Constants.sol"; -import {ModuleInstallFailed, InvalidSelectorTarget, InvalidDataLength} from "../types/Error.sol"; +import { + ModuleInstallFailed, + InvalidSelectorTarget, + InvalidDataLength, + ScopedExecutionHookStillInstalled +} from "../types/Error.sol"; import {SelectorConfig, SelectorStorage} from "../types/Structs.sol"; /// @title SelectorManager @@ -29,12 +34,15 @@ abstract contract SelectorManager { /// @notice Installs a fallback selector handler. /// @dev internalData format: `[bytes4 selector | bytes1 callType]`. + /// Selectors handled natively by Kernel are valid to install, but native dispatch takes precedence, + /// so their selector configuration and scoped execution hook have no effect. function _installSelector(address _module, bytes calldata _internalData, bool _installSuccess) internal { require(_internalData.length == 5, InvalidDataLength()); require(_module != address(0), InvalidSelectorTarget()); + bytes4 selector = bytes4(_internalData[0:4]); CallType callType = CallType.wrap(bytes1(_internalData[4])); require(callType == CALLTYPE_DELEGATECALL || _installSuccess, ModuleInstallFailed()); - SelectorConfig storage $ = _selectorConfig(bytes4(_internalData[0:4])); + SelectorConfig storage $ = _selectorConfig(selector); $.target = _module; $.callType = callType; } @@ -43,6 +51,7 @@ abstract contract SelectorManager { function _uninstallSelector(address, bytes calldata _internalData, bool) internal { require(_internalData.length == 4, InvalidDataLength()); SelectorConfig storage $ = _selectorConfig(bytes4(_internalData[0:4])); + require(address($.scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); $.target = address(0); $.callType = CallType.wrap(bytes1(0x00)); } diff --git a/src/core/ValidationManager.sol b/src/core/ValidationManager.sol index 17aecb77..77386f7a 100644 --- a/src/core/ValidationManager.sol +++ b/src/core/ValidationManager.sol @@ -3,12 +3,15 @@ pragma solidity ^0.8.0; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; import {IAccountExecute} from "account-abstraction/interfaces/IAccountExecute.sol"; -import {IValidator, IPolicy, ISigner} from "../interfaces/IERC7579Modules.sol"; +import {IValidator, IPolicy, ISigner, IScopedExecutionHook} from "../interfaces/IERC7579Modules.sol"; import { InvalidRootValidation, ModuleInstallFailed, OccupiedValidationId, InvalidPermissionUninstallOrder, + ScopedExecutionHookStillInstalled, + ScopedExecutionHookAlreadyInstalled, + InvalidScopedExecutionHookTarget, InvalidPermissionId, InvalidSelectorGrant, InvalidValidationType, @@ -34,12 +37,23 @@ import { } from "../types/Constants.sol"; import {PermissionSignature, ValidationStorage, ValidationInfo, Install} from "../types/Structs.sol"; import {Lib4337} from "../lib/Lib4337.sol"; -import {getType, getValidator, getPermissionId, validatorToIdentifier, permissionToIdentifier} from "../lib/Utils.sol"; +import { + getType, + getValidator, + getPermissionId, + validatorToIdentifier, + permissionToIdentifier, + validationScopedExecutionHookId +} from "../lib/Utils.sol"; /// @title ValidationManager /// @author taek /// @notice Manages validation identifiers (validators and permissions), root validation, and signature verification. abstract contract ValidationManager { + bytes32 private constant _SCOPED_EXECUTION_HOOK_ADDRESS_KEY = keccak256("kernel.scopedExecutionHook.address"); + bytes32 private constant _SCOPED_EXECUTION_HOOK_VALIDATION_ID_KEY = + keccak256("kernel.scopedExecutionHook.validationId"); + /// @dev Tracks the permission being installed within a batch to ensure consistency. ValidationId transient installingPermission; @@ -50,6 +64,37 @@ abstract contract ValidationManager { return $.root; } + /// @notice Retrieves the validation ID and scoped execution hook stored for a userOp hash. + function _validationScopedExecutionHook(bytes32 userOpHash) + internal + view + returns (ValidationId vId, IScopedExecutionHook hook) + { + bytes32 hookKey = keccak256(abi.encodePacked(_SCOPED_EXECUTION_HOOK_ADDRESS_KEY, userOpHash)); + bytes32 vIdKey = keccak256(abi.encodePacked(_SCOPED_EXECUTION_HOOK_VALIDATION_ID_KEY, userOpHash)); + assembly { + hook := tload(hookKey) + vId := tload(vIdKey) + } + } + + /// @notice Stores a validation ID and scoped execution hook keyed by the userOp hash. + function _setValidationScopedExecutionHook(bytes32 userOpHash, ValidationId vId, IScopedExecutionHook hook) + internal + { + bytes32 hookKey = keccak256(abi.encodePacked(_SCOPED_EXECUTION_HOOK_ADDRESS_KEY, userOpHash)); + bytes32 vIdKey = keccak256(abi.encodePacked(_SCOPED_EXECUTION_HOOK_VALIDATION_ID_KEY, userOpHash)); + assembly { + tstore(hookKey, hook) + tstore(vIdKey, vId) + } + } + + /// @notice Returns the scoped identifier exposed to a validation-scoped execution hook. + function _validationScopedExecutionHookId(ValidationId vId) internal pure returns (bytes32) { + return validationScopedExecutionHookId(vId); + } + /// @notice Returns the validation info (hook, signer, policies) for a given ValidationId. /// @param vId The validation identifier to query. /// @return The ValidationInfo struct for this identifier. @@ -146,6 +191,18 @@ abstract contract ValidationManager { installingPermission = ValidationId.wrap(bytes21(0)); } + /// @notice Installs a scoped execution hook for an existing validator or permission. + function _installValidationScopedExecutionHook(address _hook, ValidationId vId, bool _installSuccess) internal { + require(_installSuccess && _hook.code.length > 0, ModuleInstallFailed()); + ValidationInfo storage info = _validationStorage().vInfo[vId]; + require(info.installed, InvalidScopedExecutionHookTarget()); + if (getType(vId) == VALIDATION_TYPE_PERMISSION) { + require(info.signer != address(0), InvalidScopedExecutionHookTarget()); + } + require(address(info.scopedExecutionHook) == address(0), ScopedExecutionHookAlreadyInstalled()); + info.scopedExecutionHook = IScopedExecutionHook(_hook); + } + /// @notice Validates that a permission install is consistent (same PermissionId within a batch). /// @param _internalData Data with PermissionId in the first 4 bytes. /// @param _installSuccess Whether the module's onInstall call succeeded. @@ -171,6 +228,7 @@ abstract contract ValidationManager { function _uninstallValidation(ValidationId _vId) internal { ValidationStorage storage $ = _validationStorage(); require($.root != _vId, CannotUninstallRoot()); + require(address($.vInfo[_vId].scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); $.vInfo[_vId].installed = false; } @@ -181,6 +239,13 @@ abstract contract ValidationManager { _uninstallValidation(vId); } + /// @notice Uninstalls a scoped execution hook without uninstalling its validator or permission. + function _uninstallScopedExecutionHookWithVid(address _hook, ValidationId vId) internal { + ValidationInfo storage info = _validationStorage().vInfo[vId]; + require(address(info.scopedExecutionHook) == _hook, InvalidScopedExecutionHookTarget()); + info.scopedExecutionHook = IScopedExecutionHook(address(0)); + } + /// @notice Uninstalls a policy module. Policies must be uninstalled in reverse order (LIFO). /// @param _policy The policy module address. /// @param _internalData Data with PermissionId in the first 4 bytes. @@ -215,6 +280,7 @@ abstract contract ValidationManager { /// @param vId The validation identifier the signer belongs to. function _uninstallSignerWithVid(address _signer, ValidationId vId) internal { ValidationInfo storage $ = _validationStorage().vInfo[vId]; + require(address($.scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); require($.policies.length == 0, InvalidPermissionUninstallOrder()); require($.signer == _signer, InvalidPermissionId()); $.signer = address(0); diff --git a/src/interfaces/IERC7579Modules.sol b/src/interfaces/IERC7579Modules.sol index ccf84c2a..e1a9959a 100644 --- a/src/interfaces/IERC7579Modules.sol +++ b/src/interfaces/IERC7579Modules.sol @@ -60,6 +60,24 @@ interface IValidator is IModule { interface IExecutor is IModule {} +interface IScopedExecutionHook is IModule { + /// @notice Runs before execution in a validation, executor, or selector scope. + /// @param id The Kernel-generated scoped execution-hook identifier. + /// @param caller The caller that entered the scoped execution path. + /// @param value The native value supplied to that execution path. + /// @param data The calldata being checked for the scoped execution path. + /// @return hookData Context passed unchanged to postCheck. + function preCheck(bytes32 id, address caller, uint256 value, bytes calldata data) + external + payable + returns (bytes memory hookData); + + /// @notice Runs after execution in the same scope. + /// @param id The same identifier passed to preCheck. + /// @param hookData The context returned by preCheck. + function postCheck(bytes32 id, bytes calldata hookData) external payable; +} + interface IFallback is IModule {} interface IPolicy is IModule { diff --git a/src/lib/Utils.sol b/src/lib/Utils.sol index 4852ad4b..ac663897 100644 --- a/src/lib/Utils.sol +++ b/src/lib/Utils.sol @@ -3,7 +3,12 @@ pragma solidity ^0.8.0; import {IValidator} from "../interfaces/IERC7579Modules.sol"; import {ValidationMode, ValidationId, ValidationType, PermissionId} from "../types/Types.sol"; -import {VALIDATION_TYPE_PERMISSION} from "../types/Constants.sol"; +import { + VALIDATION_TYPE_PERMISSION, + SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, + SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE, + SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE +} from "../types/Constants.sol"; /// @notice Extracts the ValidationType (first byte) from a ValidationId. function getType(ValidationId validator) pure returns (ValidationType vType) { @@ -46,6 +51,47 @@ function permissionToIdentifier(PermissionId permissionId) pure returns (Validat } } +/// @notice Generates the scoped-execution-hook ID for a validation scope. +/// @dev Layout: `[1-byte scope | 21-byte ValidationId | 10 zero bytes]`. +function validationScopedExecutionHookId(ValidationId vId) pure returns (bytes32) { + return bytes32(SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) | (bytes32(ValidationId.unwrap(vId)) >> 8); +} + +/// @notice Generates the scoped-execution-hook ID for an executor scope. +/// @dev Layout: `[1-byte scope | 20-byte executor | 11 zero bytes]`. +function executorScopedExecutionHookId(address executor) pure returns (bytes32) { + return bytes32(SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) | (bytes32(bytes20(executor)) >> 8); +} + +/// @notice Generates the scoped-execution-hook ID for a selector scope. +/// @dev Layout: `[1-byte scope | 4-byte selector | 27 zero bytes]`. +function selectorScopedExecutionHookId(bytes4 selector) pure returns (bytes32) { + return bytes32(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) | (bytes32(selector) >> 8); +} + +/// @notice Extracts the one-byte scope from a scoped-execution-hook ID. +function getScopedExecutionHookScope(bytes32 id) pure returns (bytes1) { + return bytes1(id); +} + +/// @notice Extracts a ValidationId from a validation-scoped execution-hook ID. +/// @dev Callers should verify getScopedExecutionHookScope(id) first. +function getScopedExecutionHookValidationId(bytes32 id) pure returns (ValidationId) { + return ValidationId.wrap(bytes21(id << 8)); +} + +/// @notice Extracts an executor address from an executor-scoped execution-hook ID. +/// @dev Callers should verify getScopedExecutionHookScope(id) first. +function getScopedExecutionHookExecutor(bytes32 id) pure returns (address) { + return address(bytes20(id << 8)); +} + +/// @notice Extracts a selector from a selector-scoped execution-hook ID. +/// @dev Callers should verify getScopedExecutionHookScope(id) first. +function getScopedExecutionHookSelector(bytes32 id) pure returns (bytes4) { + return bytes4(id << 8); +} + /// @notice Parses a 256-bit ERC-4337 nonce into validation mode, type, and identifier. /// @dev Nonce layout (32 bytes, big-endian): /// ``` @@ -63,4 +109,3 @@ function parseNonce(uint256 nonce) pure returns (ValidationMode vMode, Validatio vId = ValidationId.wrap(bytes21(bytes32(nonce << 8))); } } - diff --git a/src/types/Constants.sol b/src/types/Constants.sol index 152599a2..3051c9f9 100644 --- a/src/types/Constants.sol +++ b/src/types/Constants.sol @@ -16,6 +16,11 @@ uint256 constant MODULE_TYPE_EXECUTOR = 2; uint256 constant MODULE_TYPE_FALLBACK = 3; uint256 constant MODULE_TYPE_POLICY = 5; uint256 constant MODULE_TYPE_SIGNER = 6; +uint256 constant MODULE_TYPE_SCOPED_EXECUTION_HOOK = 11; + +bytes1 constant SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE = 0x01; +bytes1 constant SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE = 0x02; +bytes1 constant SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE = 0x03; // note : ROOT == FALLBACK, they do indicate same value but to have different meanings in different context // FALLBACK - usually used when using 7702 validation logic diff --git a/src/types/Error.sol b/src/types/Error.sol index e12b36d9..1ac8d2c4 100644 --- a/src/types/Error.sol +++ b/src/types/Error.sol @@ -39,6 +39,15 @@ error OccupiedValidationId(); /// @notice Thrown when policies are not uninstalled in reverse order (LIFO). error InvalidPermissionUninstallOrder(); +/// @notice Thrown when removing a target before its scoped execution hook. +error ScopedExecutionHookStillInstalled(); + +/// @notice Thrown when a scoped-execution-hook scope or target is invalid or not installed. +error InvalidScopedExecutionHookTarget(); + +/// @notice Thrown when a scoped execution hook is already installed for a target. +error ScopedExecutionHookAlreadyInstalled(); + /// @notice Thrown when a permission ID does not match the expected signer or policy configuration. error InvalidPermissionId(); diff --git a/src/types/Events.sol b/src/types/Events.sol index c3e5aa0d..e2ca85d8 100644 --- a/src/types/Events.sol +++ b/src/types/Events.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; /// @notice Emitted when a module is installed on the account. -/// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). +/// @param moduleType The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer, 11=scoped execution hook). /// @param module The address of the installed module. event ModuleInstalled(uint256 moduleType, address module); diff --git a/src/types/Structs.sol b/src/types/Structs.sol index 6ef41dd3..5cec136c 100644 --- a/src/types/Structs.sol +++ b/src/types/Structs.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {ValidationId, CallType} from "./Types.sol"; -import {IExecutor} from "../interfaces/IERC7579Modules.sol"; +import {IScopedExecutionHook, IExecutor} from "../interfaces/IERC7579Modules.sol"; /// @notice Describes a module installation: the module type, address, and its data payloads. /// @dev The `moduleData` is forwarded to the module's onInstall/onUninstall callback. @@ -12,8 +12,9 @@ import {IExecutor} from "../interfaces/IERC7579Modules.sol"; /// - Fallback/Selectors (type 3): `[bytes4 selector | bytes1 callType]` /// - Policies (type 5): `[bytes4 permissionId | ...]` /// - Signers (type 6): `[bytes4 permissionId | bytes4[] allowedSelectors]` +/// - Execution hooks (type 11): `[bytes1 scope | target]` struct Install { - /// @dev The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer). + /// @dev The module type identifier (1=validator, 2=executor, 3=fallback, 5=policy, 6=signer, 11=scoped execution hook). uint256 moduleType; /// @dev The module contract address. address module; @@ -23,12 +24,14 @@ struct Install { bytes internalData; } -/// @notice Stores per-validation state: installation, selector nonce, signer, and policies. +/// @notice Stores per-validation state: installation, selector nonce, scoped execution hook, signer, and policies. struct ValidationInfo { /// @dev Incremented when selectors are (re)granted; used to invalidate old selector allowances. uint32 nonce; /// @dev Whether this validator or permission is installed. bool installed; + /// @dev Optional execution hook scoped to this validation. + IScopedExecutionHook scopedExecutionHook; /// @dev The signer module address (only for permission-based validations). address signer; /// @dev Array of policy module addresses (only for permission-based validations). @@ -75,9 +78,9 @@ struct InstallModuleDataFormat { bytes internalData; } -/// @notice Wrapper for permission uninstall data containing per-module uninstall payloads. -struct PermissionUninstallData { - /// @dev Array of uninstall data, one per policy plus one for the signer (length = policies.length + 1). +/// @notice Wrapper containing per-module uninstall payloads for a validation. +struct ValidationUninstallData { + /// @dev Permission order is policies, signer, then optional hook; validator order is validator, then hook. bytes[] uninstallData; } @@ -87,6 +90,8 @@ struct SelectorConfig { address target; /// @dev The call type: CALLTYPE_SINGLE (0x00) for call, CALLTYPE_DELEGATECALL (0xFF) for delegatecall. CallType callType; + /// @dev Optional execution hook scoped to this selector. + IScopedExecutionHook scopedExecutionHook; } /// @notice Storage for all selector configurations. @@ -99,6 +104,8 @@ struct SelectorStorage { struct ExecutorConfig { /// @dev Whether the executor is installed. bool installed; + /// @dev Optional execution hook scoped to this executor. + IScopedExecutionHook scopedExecutionHook; } /// @notice Storage for all executor configurations. From 3815fec50ae06a9d6eb3ead7973914c67cc6806d Mon Sep 17 00:00:00 2001 From: taek Date: Wed, 5 Aug 2026 09:50:16 +0900 Subject: [PATCH 5/8] test: remove Hook and add ScopedExecutionHook Replace generic-hook coverage with validation-, executor-, and selector-scoped lifecycle and execution tests. Update formal harnesses, invariants, mocks, and gas snapshots for the scoped model. --- certora/README.md | 33 +- certora/harnesses/KernelHarness.sol | 78 +- certora/specs/CheckValidation.spec | 47 +- certora/specs/Kernel.spec | 23 +- certora/specs/ModuleWriters.spec | 375 +------ certora/specs/PhaseCWriterLocal.spec | 41 +- certora/specs/SetRootLifo.spec | 20 +- certora/specs/SystemComposition.spec | 14 +- snapshots/KernelFactoryTest.json | 2 +- snapshots/KernelImmutableECDSATest.json | 4 +- snapshots/KernelTest.json | 4 +- test/Kernel.t.sol | 9 +- test/KernelExecutorTest.sol | 54 +- test/KernelHookTest.sol | 16 - test/KernelSelectorTest.sol | 104 +- test/KernelTestBase.sol | 7 +- test/KernelUserOpTest.sol | 118 +-- test/KernelValidatorTest.sol | 308 ++++-- test/btt/Kernel.branchCoverage.t.sol | 108 +- test/btt/Kernel.executeFromExecutor.t.sol | 116 -- test/btt/Kernel.executeUserOp.t.sol | 8 +- test/btt/Kernel.fallback.t.sol | 991 +----------------- test/btt/Kernel.initialize.t.sol | 20 +- test/btt/Kernel.installModule.t.sol | 311 +----- .../Kernel.installModuleWithSignature.t.sol | 36 +- test/btt/Kernel.isModuleInstalled.t.sol | 42 +- test/btt/Kernel.setRoot.t.sol | 33 +- test/btt/Kernel.supportsModule.t.sol | 10 +- test/btt/Kernel.uninstallModule.t.sol | 42 +- test/btt/Kernel.validateUserOp.t.sol | 95 +- test/btt/KernelBTTFallback.t.sol | 4 - test/btt/KernelFactory.t.sol | 5 +- test/btt/KernelUUPS.t.sol | 12 +- test/fuzz/KernelFuzz.t.sol | 84 +- test/halmos/InitializeValidationHalmos.t.sol | 49 +- test/halmos/KernelAccessControlHalmos.t.sol | 2 +- test/halmos/KernelFallbackHalmos.t.sol | 35 +- test/halmos/KernelHookBracketingHalmos.t.sol | 151 --- .../KernelModuleIdempotencyHalmos.t.sol | 26 +- test/halmos/KernelSelectorHalmos.t.sol | 60 +- test/halmos/KernelStorageSlotHalmos.t.sol | 16 +- test/halmos/TopLevelExecuteAcHalmos.t.sol | 23 +- test/integration/KernelIntegration.t.sol | 55 - .../KernelIntegrationEdgeCases.t.sol | 234 +---- test/invariant/KernelInvariant.t.sol | 107 +- test/mock/MockHook.sol | 15 +- test/mock/MockRevertingHook.sol | 10 +- test/mock/MockValidator.sol | 25 +- test/unit/GasBenchmark.t.sol | 26 +- test/unit/KernelCoverage.t.sol | 127 +-- test/unit/ModuleManagerCoverage.t.sol | 193 +--- test/unit/RevertPaths.t.sol | 72 +- 52 files changed, 759 insertions(+), 3641 deletions(-) delete mode 100644 test/KernelHookTest.sol delete mode 100644 test/halmos/KernelHookBracketingHalmos.t.sol diff --git a/certora/README.md b/certora/README.md index b35ffed9..0564b515 100644 --- a/certora/README.md +++ b/certora/README.md @@ -2,6 +2,8 @@ Formal verification harness for properties that need multi-step traces or unbounded-array quantification (out of Halmos's reach). +> **v4 scoped-execution-hook migration:** generic type-4 hooks and their sentinels were removed. Type-11 scoped execution hooks can be scoped to a validation, executor, or selector. `ValidationInfo.installed` now tracks validation installation. Historical results below that mention generic hooks describe the pre-migration model and must be rerun before being treated as current evidence. ERC-1271 signatures no longer carry a validation-mode byte; enable-mode installation remains ERC-4337-only. + ## Layout ``` @@ -41,7 +43,7 @@ The run uploads to Certora cloud and prints a job URL. Open it for the report. ### Results (FV Round 1, Phase C — pre-fix) | Rule | Status | Notes | -|---|---|---| +| --- | --- | --- | | `validateUserOpEnforcesInnerSelectorAccess_naive` | **FAIL** | CEX surfaced the fast-path bypass implementation finding | | `validateUserOpEnforcesInnerSelectorAccess_strict` | **PASS** | Property held when the fast-path was explicitly excluded | | `sanityValidateUserOpReachesSuccess` | **PASS** (satisfy) | Confirmed the rule setup was not vacuous | @@ -54,13 +56,13 @@ After the fix at commit `0921b25` (`src/core/ValidationManager.sol` — `_grantAccess` blocks `executeUserOp.selector` for non-root vIds): | Rule / Invariant | Status | Notes | -|---|---|---| +| --- | --- | --- | | `validateUserOpEnforcesInnerSelectorAccess_naive` | ✅ **PASS** | The original CEX witness is now structurally unreachable. The fix closes the immediate attack. | | `validateUserOpEnforcesInnerSelectorAccess_strict` | ✅ **PASS** | Regression — still holds with `!fastPath` precondition. | | `sanityValidateUserOpReachesSuccess` (satisfy) | ✅ **PASS** | Rule setup is not vacuous. | | `nonRootCannotAllowExecuteUserOp` (invariant) | 🚨 **FAIL** | Secondary finding — root rotation leaves `allowed[oldRoot][executeUserOp]` non-zero. See breakdown below. | -Round 2 job URL: https://prover.certora.com/output/3606101/19ed688fd26e43cfa25d435306bec6f1?anonymousKey=a87fff01a79ea67c696ced6eb56bd0dbae8403e7 +Round 2 job URL: #### Secondary finding — `setRoot` residual @@ -69,7 +71,7 @@ Certora's induction step on the invariant `nonRootCannotAllowExecuteUserOp` prod Failing methods (induction step) and how each reaches `_setRoot`: | Method | Path to `_setRoot` | -|---|---| +| --- | --- | | `setRoot(bytes21)` | Direct | | `setRoot((uint256,address,bytes,…))` (overload via install) | Direct | | `initialize((uint256,address,bytes,…))` | Root install path | @@ -86,11 +88,11 @@ Selected remediation: **Option E** — bump `vInfo[oldRoot].nonce` in `_setRoot` After the `_setRoot` fix we tried two stronger invariant formulations: | Round | Invariant form | Result | -|---|---|---| +| --- | --- | --- | | 3 | `allowedNonce(vId, exec) != vInfoNonce(vId)` for non-root vId | FAIL — base case (uninstalled vIds have both sides = 0, so `0 != 0` is false). Helper invariant `installedValidationsHaveNonzeroNonce` timed out at 96 min on one induction step. | | 4 | Bypass-impossible form: `NOT (_allowedSelector(vId, exec) AND hook == INSTALLED_NO_HOOK)` for non-root vId | FAIL — Certora's NONDET / AUTO-HAVOC abstraction for external module callbacks lets it imagine arbitrary writes to ValidationStorage on every entry point that involves a delegatecall or callback (`executeUserOp`, `execute`, `validateUserOp`, `installModule`, `setRoot`, `grantAccess`, `upgradeToAndCall`, ``, `initialize`). `_onlyEntryPointOrSelf` prevents this reentrant write in production, but encoding that as precise CVL summaries for ~10 sites is days of work and likely OOMs. | -Round 4 job URL: https://prover.certora.com/output/3606101/e16f05be609a48b79c6bfa16f7c357f4?anonymousKey=39f0672f112513c790ec1d6d8ba351876073e395 +Round 4 job URL: **Decision (2026-05-21)**: accept the invariant as unprovable under the current CVL summary set. The audit story is carried by: @@ -107,9 +109,9 @@ inner-selector `require` when ALL of the following held: - `vType != ROOT`, - `_allowedSelector(vId, outerSel)` was true with `outerSel == executeUserOp.selector`, -- `vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK`. +- `vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0)`. -When this happened, `_setValidationHook` was never called, the transient hook +When this happened, `_setValidationScopedExecutionHook` was never called, the transient hook stayed at 0, and `executeUserOp`'s inner delegatecall ran with NO selector check — handing a non-ROOT validation the equivalent of root privileges. @@ -138,7 +140,7 @@ rotation. Report any such CEX honestly to the orchestrator. ### #4 — Permission validation totality | Rule | Status | Notes | -|---|---|---| +| --- | --- | --- | | `policyFailureImpliesAggregateFailure` | ✅ PASS | If any policy returns failure, the aggregate is failure. | | `signerFailureImpliesAggregateFailure` | ✅ PASS | If the signer returns failure, the aggregate is failure. | | `sanityCanSucceed` (satisfy) | ✅ PASS | Non-vacuous: a non-reverting call exists. | @@ -149,22 +151,23 @@ Files: `certora/specs/Permission.spec`, `certora/conf/Permission.conf`. ### #6 — `setRoot` LIFO clear of permission state | Rule | Status | -|---|---| +| --- | --- | | `setRootClearsOldPermissionState` | ✅ PASS | | `sanitySetRootReaches` (satisfy) | ✅ PASS | -After `setRoot(packages, removeCurrent=true)` on a `VALIDATION_TYPE_PERMISSION` root, the old root's `policies.length == 0`, `signer == 0`, and `hook == HOOK_MODULE_NOT_INSTALLED`. LIFO loop bound at `policies.length <= 3`. +After `setRoot(packages, removeCurrent=true)` on a `VALIDATION_TYPE_PERMISSION` root, the old root's `policies.length == 0`, `signer == 0`, `scopedExecutionHook == 0`, and `installed == false`. LIFO loop bound at `policies.length <= 3`. Files: `certora/specs/SetRootLifo.spec`, `certora/conf/SetRootLifo.conf`. ### #11 — View/write permission path equivalence | Rule | Status | Notes | -|---|---|---| +| --- | --- | --- | | `viewAndWritePathsAgreeOnSuccess` | ✅ PASS | View (ERC-1271) and write (ERC-4337) paths agree on the success/failure binary outcome. | | `sanityViewPathReaches` (satisfy) | ✅ PASS | Non-vacuous. | Notable spec evolution (Rounds 1-4): + 1. Full `uint256` equality assertion — FAIL by design (view path's `bytes4`-lift cannot represent ERC-4337 time bounds). 2. Binary outcome with `signerGhostUint == 0 <=> bytes4 == MAGIC` axiom — FAIL (axiom too narrow; ignored aggregator bits). 3. Tightened axiom to `AGG_OK(signerGhostUint) <=> bytes4 == MAGIC` + iff on intersectGhost — FAIL (signer ghost still had arbitrary upper bits). @@ -177,16 +180,16 @@ Files: `certora/specs/PermissionEquivalence.spec`, `certora/conf/PermissionEquiv The Round 1 global invariant `nonRootCannotBypassFastPathWithExecuteUserOp` in `specs/Kernel.spec` is replaced for verification purposes by **four writer-local rules** in `specs/PhaseCWriterLocal.spec`: | Rule | Status | -|---|---| +| --- | --- | | `grantAccessPreservesNonBypass` | ✅ PASS | | `setRootPreservesNonBypass` | ✅ PASS | | `uninstallValidationPreservesNonBypass` | ✅ PASS | | `initializeValidationPreservesNonBypass` | ✅ PASS | | 4 corresponding `sanity*` rules | ✅ PASS (all 4) | -Job: https://prover.certora.com/output/3606101/37e8675776484e4998f16f528d2dd29a +Job: -**Implication chain** (documented in `specs/PhaseCWriterLocal.spec` docstring; not formally proven in CVL but verified by grep over `src/`): the four functions are the ONLY paths that write `$.allowed`, `$.vInfo[*].nonce`, `$.vInfo[*].hook`, or `$.root`. Genesis state trivially satisfies the bypass-impossible property. Each writer preserves it. Therefore by structural induction, every reachable state satisfies it. The conjunction of the 4 local rules + static-writer-completeness ⇒ the original global property. +**Implication chain** (documented in `specs/PhaseCWriterLocal.spec` docstring; not formally proven in CVL but verified by grep over `src/`): the four functions are the ONLY paths that write `$.allowed`, `$.vInfo[*].nonce`, `$.vInfo[*].installed`, or `$.root`. Genesis state trivially satisfies the bypass-impossible property. Each writer preserves it. Therefore by structural induction, every reachable state satisfies it. The conjunction of the 4 local rules + static-writer-completeness ⇒ the original global property. The original `Kernel.spec` invariant remains as documented intent + regression target for future Certora releases with stronger summaries. diff --git a/certora/harnesses/KernelHarness.sol b/certora/harnesses/KernelHarness.sol index c89af3af..ef98416a 100644 --- a/certora/harnesses/KernelHarness.sol +++ b/certora/harnesses/KernelHarness.sol @@ -6,9 +6,9 @@ import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOper import {KernelUUPS} from "src/KernelUUPS.sol"; import {ValidationId, ValidationType, ValidationMode, PermissionId} from "src/types/Types.sol"; import {ValidationInfo, ValidationStorage} from "src/types/Structs.sol"; -import {IHook, IExecutor} from "src/interfaces/IERC7579Modules.sol"; +import {IScopedExecutionHook, IExecutor} from "src/interfaces/IERC7579Modules.sol"; import {CallType} from "src/types/Types.sol"; -import {ExecutorStorage, SelectorStorage, HookStorage} from "src/types/Structs.sol"; +import {ExecutorStorage, SelectorStorage} from "src/types/Structs.sol"; import {parseNonce, getType, permissionToIdentifier} from "src/lib/Utils.sol"; import { VALIDATION_TYPE_ROOT, @@ -16,10 +16,7 @@ import { VALIDATION_TYPE_PERMISSION, VALIDATION_MANAGER_STORAGE_SLOT, EXECUTOR_MANAGER_STORAGE_SLOT, - SELECTOR_MANAGER_STORAGE_SLOT, - HOOK_MANAGER_STORAGE_SLOT, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK + SELECTOR_MANAGER_STORAGE_SLOT } from "src/types/Constants.sol"; /// @title KernelHarness — Certora-only wrapper exposing internal views. @@ -41,8 +38,12 @@ contract KernelHarness is KernelUUPS { return _vs().vInfo[ValidationId.wrap(vId)].nonce; } - function harness_vInfoHook(bytes21 vId) external view returns (address) { - return _vs().vInfo[ValidationId.wrap(vId)].hook; + function harness_vInfoInstalled(bytes21 vId) external view returns (bool) { + return _vs().vInfo[ValidationId.wrap(vId)].installed; + } + + function harness_vInfoScopedExecutionHook(bytes21 vId) external view returns (address) { + return address(_vs().vInfo[ValidationId.wrap(vId)].scopedExecutionHook); } function harness_allowedNonce(bytes21 vId, bytes4 sel) external view returns (uint32) { @@ -108,12 +109,14 @@ contract KernelHarness is KernelUUPS { return ValidationMode.unwrap(vMode); } - function harness_validationHook(bytes32 userOpHash) external view returns (address) { - IHook h; - assembly { - h := tload(userOpHash) - } - return address(h); + function harness_validationScopedExecutionHook(bytes32 userOpHash) external view returns (address) { + (, IScopedExecutionHook hook) = _validationScopedExecutionHook(userOpHash); + return address(hook); + } + + function harness_validationScopedExecutionHookVId(bytes32 userOpHash) external view returns (bytes21) { + (ValidationId vId,) = _validationScopedExecutionHook(userOpHash); + return ValidationId.unwrap(vId); } // ------------------------------------------------------------------ @@ -132,14 +135,6 @@ contract KernelHarness is KernelUUPS { return ValidationType.unwrap(VALIDATION_TYPE_PERMISSION); } - function harness_HOOK_NOT_INSTALLED() external pure returns (address) { - return HOOK_MODULE_NOT_INSTALLED; - } - - function harness_HOOK_INSTALLED_NO_HOOK() external pure returns (address) { - return HOOK_MODULE_INSTALLED_NO_HOOK; - } - function harness_executeUserOpSelector() external pure returns (bytes4) { return this.executeUserOp.selector; } @@ -240,7 +235,7 @@ contract KernelHarness is KernelUUPS { // 3. _uninstallValidation(_vId) -- ValidationManager.sol:210 // 4. _initializeValidation(vId, _internalData) -- ValidationManager.sol:125 // - // No other path writes $.allowed, $.vInfo[*].nonce, $.vInfo[*].hook, or + // No other path writes $.allowed, $.vInfo[*].nonce, $.vInfo[*].installed, or // $.root. Public entry points (installModule, executeUserOp, etc.) reach // these writers via internal call chains, but the writers themselves are // the only place where the relevant storage slots are mutated. @@ -263,31 +258,23 @@ contract KernelHarness is KernelUUPS { } // ------------------------------------------------------------------ - // Module-storage accessors (Phase 2 — ExecutorManager / SelectorManager / - // HookManager). Mirror the production storage layout reads so CVL can + // Module-storage accessors (Phase 2 — ExecutorManager / SelectorManager). + // Mirror the production storage layout reads so CVL can // observe the per-slot post-state of each module writer. // ------------------------------------------------------------------ - function harness_executorHook(address executor) external view returns (address) { - return address(_es().executorConfig[IExecutor(executor)].hook); + function harness_executorInstalled(address executor) external view returns (bool) { + return _es().executorConfig[IExecutor(executor)].installed; } function harness_selectorTarget(bytes4 selector) external view returns (address) { return _ss().selectorConfig[selector].target; } - function harness_selectorHook(bytes4 selector) external view returns (address) { - return address(_ss().selectorConfig[selector].hook); - } - function harness_selectorCallType(bytes4 selector) external view returns (bytes1) { return CallType.unwrap(_ss().selectorConfig[selector].callType); } - function harness_hookEnabled(address hook) external view returns (bool) { - return _hs().enabled[hook]; - } - /// @notice Returns `bytes4(_internalData[0:4])` -- the selector key /// that `_installSelector` / `_uninstallSelector` derive from /// internalData. Pure projection; reverts if length < 4. @@ -306,11 +293,7 @@ contract KernelHarness is KernelUUPS { // 2. _uninstallExecutor(_executor, _, _) -- ExecutorManager.sol:54 // 3. _installSelector(_module, _internalData, _installSuccess) -- SelectorManager.sol:45 // 4. _uninstallSelector(_, _internalData, _) -- SelectorManager.sol:62 - // 5. _installHook(_hook, _internalData, _installSuccess) -- HookManager.sol:36 - // 6. _uninstallHook(_hook, _, _) -- HookManager.sol:45 - // - // No other code path writes ExecutorStorage, SelectorStorage, or - // HookStorage in src/. Verified by grep on 2026-05-24. + // No other code path writes ExecutorStorage or SelectorStorage. // ------------------------------------------------------------------ function harness_installExecutor(address executor, bytes calldata internalData, bool installSuccess) external { @@ -329,14 +312,6 @@ contract KernelHarness is KernelUUPS { _uninstallSelector(module, internalData, installSuccess); } - function harness_installHook(address hook, bytes calldata internalData, bool installSuccess) external { - _installHook(hook, internalData, installSuccess); - } - - function harness_uninstallHook(address hook, bytes calldata internalData, bool installSuccess) external { - _uninstallHook(hook, internalData, installSuccess); - } - // ------------------------------------------------------------------ // `_checkValidation` routing probes (FV Round 2, Phase 2) // @@ -426,11 +401,4 @@ contract KernelHarness is KernelUUPS { $.slot := slot } } - - function _hs() internal pure returns (HookStorage storage $) { - bytes32 slot = HOOK_MANAGER_STORAGE_SLOT; - assembly { - $.slot := slot - } - } } diff --git a/certora/specs/CheckValidation.spec b/certora/specs/CheckValidation.spec index 1f14193b..6bbf17cb 100644 --- a/certora/specs/CheckValidation.spec +++ b/certora/specs/CheckValidation.spec @@ -12,18 +12,17 @@ * * SPEC PER vType * 1. vType == VALIDATION_TYPE_VALIDATOR (0x01): - * resolves `v = vId`, requires `vInfo[vId].hook > HOOK_MODULE_NOT_INSTALLED`, + * resolves `v = vId`, requires `vInfo[vId].installed`, * and routes to `_validateUserOpValidator`. * 2. vType == VALIDATION_TYPE_PERMISSION (0x02): - * resolves `v = vId`, requires `vInfo[vId].hook > HOOK_MODULE_NOT_INSTALLED`, + * resolves `v = vId`, requires `vInfo[vId].installed`, * and routes to `_validateUserOpPermission`. * 3. vType == VALIDATION_TYPE_ROOT (0x00): * a) if `$.root == 0`: returns `(0, _validateUserOpFallback)` WITHOUT * checking the hook (the production invariant `$.root == 0 => * _fallbackValidatorAvailable()` is enforced by `_setRoot`, not by * `_checkValidation`). - * b) otherwise: resolves `v = $.root`, requires `vInfo[$.root].hook > - * HOOK_MODULE_NOT_INSTALLED`, and routes by `getType($.root)` + * b) otherwise: resolves `v = $.root`, requires `vInfo[$.root].installed`, and routes by `getType($.root)` * (VALIDATOR -> validator, PERMISSION -> permission). * 4. There is NO separate VALIDATION_TYPE_FALLBACK branch: * `VALIDATION_TYPE_FALLBACK == VALIDATION_TYPE_ROOT == 0x00`. Both alias @@ -79,7 +78,8 @@ methods { external returns (uint256); // State accessors. - function harness_vInfoHook(bytes21) external returns (address) envfree; + function harness_vInfoInstalled(bytes21) external returns (bool) envfree; + function harness_vInfoScopedExecutionHook(bytes21) external returns (address) envfree; function harness_root() external returns (bytes21) envfree; function harness_getType(bytes21) external returns (bytes1) envfree; function harness_fallbackAvailable() external returns (bool) envfree; @@ -89,9 +89,6 @@ methods { function harness_VT_VALIDATOR() external returns (bytes1) envfree; function harness_VT_PERMISSION() external returns (bytes1) envfree; function harness_VT_FALLBACK() external returns (bytes1) envfree; - function harness_HOOK_NOT_INSTALLED() external returns (address) envfree; - function harness_HOOK_INSTALLED_NO_HOOK() external returns (address) envfree; - // --- Per-function summaries that return DISTINCT sentinels --- // These are the SOLE observation channel for which function `_checkValidation` // routed to. The harness wrapper invokes the returned pointer with dummy @@ -115,20 +112,20 @@ methods { // // Precondition: // vType == VALIDATION_TYPE_VALIDATOR -// vInfo[vId].hook > HOOK_MODULE_NOT_INSTALLED (installed) +// vInfo[vId].installed (installed) // // Postcondition: harness_checkValidationResolvedV(vType, vId) == vId // // The hook precondition matches the production require: -// require(info.hook > HOOK_MODULE_NOT_INSTALLED, InvalidVid(v)); -// HOOK_MODULE_NOT_INSTALLED is address(0); HOOK_MODULE_INSTALLED_NO_HOOK is +// require(info.installed, InvalidVid(v)); +// installed == false is address(0); a zero scopedExecutionHook is // address(1). "Installed" means hook is any non-zero address. // // Expected outcome: PASS. // =========================================================================== rule routeValidatorResolvesV(bytes21 vId) { bytes1 vType = harness_VT_VALIDATOR(); - require harness_vInfoHook(vId) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(vId); bytes21 v = harness_checkValidationResolvedV(vType, vId); assert v == vId, "VALIDATOR branch should resolve v = vId"; } @@ -144,7 +141,7 @@ rule routeValidatorResolvesV(bytes21 vId) { rule routeValidatorRoutesValidator(bytes21 vId) { env e; bytes1 vType = harness_VT_VALIDATOR(); - require harness_vInfoHook(vId) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(vId); uint256 route = harness_invokeCheckValidationRoute(e, vType, vId); assert route == ROUTE_VALIDATOR(), "VALIDATOR branch should route to _validateUserOpValidator"; @@ -155,7 +152,7 @@ rule routeValidatorRoutesValidator(bytes21 vId) { // // Precondition: // vType == VALIDATION_TYPE_PERMISSION -// vInfo[vId].hook > HOOK_MODULE_NOT_INSTALLED +// vInfo[vId].installed // // Postcondition: harness_checkValidationResolvedV == vId // @@ -163,7 +160,7 @@ rule routeValidatorRoutesValidator(bytes21 vId) { // =========================================================================== rule routePermissionResolvesV(bytes21 vId) { bytes1 vType = harness_VT_PERMISSION(); - require harness_vInfoHook(vId) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(vId); bytes21 v = harness_checkValidationResolvedV(vType, vId); assert v == vId, "PERMISSION branch should resolve v = vId"; } @@ -179,7 +176,7 @@ rule routePermissionResolvesV(bytes21 vId) { rule routePermissionRoutesPermission(bytes21 vId) { env e; bytes1 vType = harness_VT_PERMISSION(); - require harness_vInfoHook(vId) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(vId); uint256 route = harness_invokeCheckValidationRoute(e, vType, vId); assert route == ROUTE_PERMISSION(), "PERMISSION branch should route to _validateUserOpPermission"; @@ -191,7 +188,7 @@ rule routePermissionRoutesPermission(bytes21 vId) { // Precondition: // vType == VALIDATION_TYPE_ROOT // $.root != bytes21(0) -// vInfo[$.root].hook > HOOK_MODULE_NOT_INSTALLED +// vInfo[$.root].installed // getType($.root) ∈ {VALIDATOR, PERMISSION} // // Postcondition: @@ -205,7 +202,7 @@ rule routeRootResolvesV { bytes21 currentRoot = harness_root(); require currentRoot != to_bytes21(0); - require harness_vInfoHook(currentRoot) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(currentRoot); bytes1 rootType = harness_getType(currentRoot); require rootType == harness_VT_VALIDATOR() || rootType == harness_VT_PERMISSION(); @@ -230,7 +227,7 @@ rule routeRootOfValidatorRoutesValidator { bytes21 currentRoot = harness_root(); require currentRoot != to_bytes21(0); - require harness_vInfoHook(currentRoot) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(currentRoot); require harness_getType(currentRoot) == harness_VT_VALIDATOR(); uint256 route = harness_invokeCheckValidationRoute(e, vTypeRoot, anyVid); @@ -252,7 +249,7 @@ rule routeRootOfPermissionRoutesPermission { bytes21 currentRoot = harness_root(); require currentRoot != to_bytes21(0); - require harness_vInfoHook(currentRoot) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(currentRoot); require harness_getType(currentRoot) == harness_VT_PERMISSION(); uint256 route = harness_invokeCheckValidationRoute(e, vTypeRoot, anyVid); @@ -324,11 +321,11 @@ rule routeRootZeroRoutesFallback { // // Precondition: // vType ∈ {VALIDATION_TYPE_VALIDATOR, VALIDATION_TYPE_PERMISSION} -// vInfo[vId].hook == HOOK_MODULE_NOT_INSTALLED +// !vInfo[vId].installed // // Postcondition: // Both probe wrappers revert (the production require: -// require(info.hook > HOOK_MODULE_NOT_INSTALLED, InvalidVid(v)); +// require(info.installed, InvalidVid(v)); // ). // // This is the "only succeeds when the routed validation is installed" half @@ -340,7 +337,7 @@ rule routeRevertsWhenNotInstalled(bytes21 vId, bool useValidator) { env e; bytes1 vType = useValidator ? harness_VT_VALIDATOR() : harness_VT_PERMISSION(); - require harness_vInfoHook(vId) == harness_HOOK_NOT_INSTALLED(); + require !harness_vInfoInstalled(vId); harness_invokeCheckValidationRoute@withrevert(e, vType, vId); assert lastReverted, @@ -390,7 +387,7 @@ rule fallbackRoutedOnlyWhenRootZero(bytes1 vType, bytes21 vId) { rule sanityValidatorRouteReachable { env e; bytes21 vId; - require harness_vInfoHook(vId) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(vId); uint256 route = harness_invokeCheckValidationRoute(e, harness_VT_VALIDATOR(), vId); satisfy route == ROUTE_VALIDATOR(); } @@ -398,7 +395,7 @@ rule sanityValidatorRouteReachable { rule sanityPermissionRouteReachable { env e; bytes21 vId; - require harness_vInfoHook(vId) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(vId); uint256 route = harness_invokeCheckValidationRoute(e, harness_VT_PERMISSION(), vId); satisfy route == ROUTE_PERMISSION(); } diff --git a/certora/specs/Kernel.spec b/certora/specs/Kernel.spec index a8e21a4e..f651e582 100644 --- a/certora/specs/Kernel.spec +++ b/certora/specs/Kernel.spec @@ -21,7 +21,7 @@ * (nonRootCannotBypassFastPathWithExecuteUserOp, commits 0921b25 + ce185f6): * For any vId != $.root: * NOT (_allowedSelector(vId, executeUserOp.selector) - * AND vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK) + * AND vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0)) * - 0921b25 blocks the grant at the source (`_grantAccess` rejects * `executeUserOp.selector` for non-root vIds). * - ce185f6 invalidates orphaned grants on rotation (`_setRoot` bumps the @@ -33,7 +33,7 @@ * regression after the fix): * Adds an additional precondition that EXCLUDES the fast-path bypass: * NOT( allowed[vId][outerSel] == vInfo[vId].nonce - * AND vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK ) + * AND vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0) ) * With that exclusion, validateUserOp reaches the require on * Kernel.sol L179-183 which enforces _allowedSelector(vId, innerSel). * @@ -43,8 +43,8 @@ * - vType != ROOT, * - `_allowedSelector(vId, outerSel)` was true with outerSel == * executeUserOp.selector, - * - `vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK`. - * In that branch, `_setValidationHook` was never called, the transient + * - `vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0)`. + * In that branch, `_setValidationScopedExecutionHook` was never called, the transient * hook for `userOpHash` stayed at 0, and `executeUserOp`'s inner * delegatecall ran with NO selector check — handing a non-ROOT validation * the equivalent of root privileges. @@ -77,7 +77,8 @@ methods { // Harness storage / parse accessors (envfree — no env needed). function harness_vInfoNonce(bytes21) external returns (uint32) envfree; - function harness_vInfoHook(bytes21) external returns (address) envfree; + function harness_vInfoInstalled(bytes21) external returns (bool) envfree; + function harness_vInfoScopedExecutionHook(bytes21) external returns (address) envfree; function harness_allowedNonce(bytes21, bytes4) external returns (uint32) envfree; function harness_allowedSelector(bytes21, bytes4) external returns (bool) envfree; function harness_root() external returns (bytes21) envfree; @@ -89,8 +90,6 @@ methods { function harness_VT_ROOT() external returns (bytes1) envfree; function harness_VT_VALIDATOR() external returns (bytes1) envfree; function harness_VT_PERMISSION() external returns (bytes1) envfree; - function harness_HOOK_NOT_INSTALLED() external returns (address) envfree; - function harness_HOOK_INSTALLED_NO_HOOK() external returns (address) envfree; function harness_executeUserOpSelector() external returns (bytes4) envfree; function harness_isEnableMode(uint256) external returns (bool) envfree; function harness_isReplayableMode(uint256) external returns (bool) envfree; @@ -140,7 +139,7 @@ methods { // // For any vId != $.root: // NOT ( _allowedSelector(vId, executeUserOp.selector) -// AND vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK ) +// AND vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0) ) // // jointly enforced by: // - commit 0921b25 — `_grantAccess` rejects `executeUserOp.selector` for @@ -174,7 +173,7 @@ methods { // true; `_setRoot` (with the fix) preserves the property across // rotation; `_uninstallValidation` zeros the hook (breaks the // conjunction's second conjunct); no other path writes `allowed[]`, -// `vInfo.nonce`, `vInfo.hook`, or `$.root`. Verified by grep over src/. +// `vInfo.nonce`, `vInfo.installed`, or `$.root`. Verified by grep over src/. // // The invariant is retained here as a STATEMENT of intent and a regression // target. If a future Certora run with better summaries can verify it, @@ -184,7 +183,7 @@ methods { invariant nonRootCannotBypassFastPathWithExecuteUserOp(bytes21 vId) vId != harness_root() => !(harness_allowedSelector(vId, harness_executeUserOpSelector()) - && harness_vInfoHook(vId) == harness_HOOK_INSTALLED_NO_HOOK()); + && (harness_vInfoInstalled(vId) && harness_vInfoScopedExecutionHook(vId) == 0)); // -------------------------------------------------------------------------- // Rule: validateUserOpEnforcesInnerSelectorAccess_naive @@ -253,7 +252,7 @@ rule validateUserOpEnforcesInnerSelectorAccess_naive( // - parsed vType != ROOT, // - op.callData has at least 8 bytes, // - NOT(allowed[vId][outerSel] == vInfo[vId].nonce -// AND vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK), +// AND vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0)), // the post-state satisfies allowed[vId][innerSel] == vInfo[vId].nonce. // -------------------------------------------------------------------------- rule validateUserOpEnforcesInnerSelectorAccess_strict( @@ -277,7 +276,7 @@ rule validateUserOpEnforcesInnerSelectorAccess_strict( // the validation has no hook, the require gate is bypassed (this is the // implementation finding, NOT a property of the spec). bool fastPath = harness_allowedSelector(vId, outerSel) - && harness_vInfoHook(vId) == harness_HOOK_INSTALLED_NO_HOOK(); + && (harness_vInfoInstalled(vId) && harness_vInfoScopedExecutionHook(vId) == 0); require !fastPath; validateUserOp@withrevert(e, op, userOpHash, missingAccountFunds); diff --git a/certora/specs/ModuleWriters.spec b/certora/specs/ModuleWriters.spec index 8fd73ff0..82d9b9bd 100644 --- a/certora/specs/ModuleWriters.spec +++ b/certora/specs/ModuleWriters.spec @@ -1,385 +1,48 @@ /* SPDX-License-Identifier: MIT */ /** - * Kernel v4 -- FV Round 2 Phase 2: Writer-local invariants for the - * NON-validation module storage slots. - * - * BACKGROUND - * Round 2 Phase C proved four writer-local rules for ValidationStorage - * writers (see certora/specs/PhaseCWriterLocal.spec). The "writer-local - * decomposition" is reused here to cover the SIX OTHER writers that - * mutate the module-related namespaced storage slots: - * - * 1. ExecutorManager._installExecutor (ExecutorStorage) - * 2. ExecutorManager._uninstallExecutor (ExecutorStorage) - * 3. SelectorManager._installSelector (SelectorStorage) - * 4. SelectorManager._uninstallSelector (SelectorStorage) - * 5. HookManager._installHook (HookStorage) - * 6. HookManager._uninstallHook (HookStorage) - * - * COMPLETENESS OF WRITER SET (verified by static grep on 2026-05-24) - * - ExecutorStorage.executorConfig: written ONLY by _installExecutor and - * _uninstallExecutor. - * - SelectorStorage.selectorConfig: written ONLY by _installSelector and - * _uninstallSelector. Kernel.sol:263 reads the same slot via - * `SelectorConfig storage $` for fallback dispatch -- READ-ONLY. - * - HookStorage.enabled: written ONLY by _installHook and _uninstallHook. - * All public entry points (installModule, uninstallModule, executeUserOp, - * initialize, fallback) reach these writers via internal call chains; - * the writers themselves are the only place these slots mutate. - * - * CROSS-MODULE BYPASS-IMPOSSIBLE - * None of these six writers touches ValidationStorage (no $.allowed, - * $.vInfo, or $.root mutation). The Phase C global "non-root vId cannot - * bypass executeUserOp" property is therefore TRIVIALLY preserved by - * each of these writers -- a separate rule per writer is unnecessary - * here (would be vacuously true and rule_sanity would catch it). - * We focus on the per-module invariants the orchestrator specified. - * - * PROPERTY STATEMENTS (orchestrator obligations, 2026-05-21): - * #1 _installExecutor: - * post-install, executor.hook == HOOK_MODULE_INSTALLED_NO_HOOK - * OR _hookEnabled(executor.hook) == true. - * #2 _uninstallExecutor: - * post-uninstall, executor.hook == HOOK_MODULE_NOT_INSTALLED. - * #3 _installSelector: - * post-install, selector.target != address(0) AND - * (selector.hook == HOOK_MODULE_INSTALLED_NO_HOOK - * OR _hookEnabled(selector.hook) == true). - * #4 _uninstallSelector: - * post-uninstall, selector.target == address(0). - * #5 _installHook: - * post-install, _hookEnabled(hook) == true. - * #6 _uninstallHook: - * post-uninstall, _hookEnabled(hook) == false. - * - * NB: Rules #1 and #3 mention `_hookEnabled` in the post-state. We do - * NOT NONDET-summarise `_hookEnabled` for this spec (it is summarised - * in PhaseCWriterLocal.spec only because that spec doesn't observe - * HookStorage post-state). Here `_hookEnabled` inlines to a direct - * read of `_hookStorage().enabled[h]` which CVL handles concretely; - * the require inside the writer and the post-state check therefore - * agree about HookStorage. - * - * NB on Rule #3: The orchestrator obligation states "valid module - * address (non-zero)" AND "hook == address(1) OR installed hook". - * Static inspection of _installSelector shows the writer does NOT - * guard against _module == address(0) and ALSO permits hook == - * HOOK_MODULE_NOT_INSTALLED (address(0)) as the "entryPoint-only" - * sentinel. If either branch produces a CEX, that is a real finding - * to surface to the orchestrator (impl vs spec gap), not a spec - * weakness to paper over. See "EXPECTED RESULTS" below. - * - * EXPECTED RESULTS (static analysis prior to running certoraRun) - * - Rule #1 installExecutorPostHookOk -- expected PASS - * - Rule #2 uninstallExecutorClearsHook -- expected PASS - * - Rule #3 installSelectorPostInvariant -- expected VIOLATION - * Reason A: `_module == address(0)` is not blocked. The writer - * happily sets target = 0, which violates the "non-zero - * module" half of the obligation. - * Reason B: `hook == HOOK_MODULE_NOT_INSTALLED` (address(0)) is the - * documented "entryPoint-only" sentinel and the writer - * accepts it without an `_hookEnabled` check. Post-state - * hook == 0 violates the obligation as stated. - * Either CEX is a real finding -- HIGH severity for orchestrator - * triage. - * - Rule #4 uninstallSelectorClearsTarget -- expected PASS - * - Rule #5 installHookPostEnabled -- expected PASS - * - Rule #6 uninstallHookPostDisabled -- expected PASS - * - * Verified contract: KernelHarness (extends KernelUUPS). Harness adds - * external wrappers for the six writers plus storage accessors; production - * logic is unchanged. + * Kernel v4 writer-local invariants for executor and fallback-selector storage. + * Generic hooks were removed; executor installation is represented by a bool, + * and selector configuration contains only target and callType. */ methods { - // Module-storage accessors (Phase 2 harness additions). - function harness_executorHook(address) external returns (address) envfree; - function harness_selectorTarget(bytes4) external returns (address) envfree; - function harness_selectorHook(bytes4) external returns (address) envfree; - function harness_selectorCallType(bytes4) external returns (bytes1) envfree; - function harness_hookEnabled(address) external returns (bool) envfree; + function harness_executorInstalled(address) external returns (bool) envfree; + function harness_selectorTarget(bytes4) external returns (address) envfree; function harness_internalDataSelector(bytes) external returns (bytes4) envfree; - function harness_HOOK_NOT_INSTALLED() external returns (address) envfree; - function harness_HOOK_INSTALLED_NO_HOOK() external returns (address) envfree; - - // Writer wrappers (Phase 2 harness additions). - function harness_installExecutor(address, bytes, bool) external; + function harness_installExecutor(address, bytes, bool) external; function harness_uninstallExecutor(address, bytes, bool) external; - function harness_installSelector(address, bytes, bool) external; + function harness_installSelector(address, bytes, bool) external; function harness_uninstallSelector(address, bytes, bool) external; - function harness_installHook(address, bytes, bool) external; - function harness_uninstallHook(address, bytes, bool) external; - - // Internal summaries -- match PhaseCWriterLocal.spec / Kernel.spec for - // consistency, but EXCLUDE _hookEnabled because the property reads - // HookStorage in the post-state and we need the require() - // inside _installExecutor / _installSelector to agree with what the - // post-state observation sees. _hookEnabled is a pure read of - // _hookStorage().enabled[h] -- CVL handles it concretely without help. - function ValidationManager._validateUserOpValidator( - KernelHarness.ValidationId, bytes32, KernelHarness.PackedUserOperation memory, bytes calldata - ) internal returns (uint256) => NONDET; - function ValidationManager._validateUserOpPermission( - KernelHarness.ValidationId, bytes32, KernelHarness.PackedUserOperation memory, bytes calldata - ) internal returns (uint256) => NONDET; - function ValidationManager._validateUserOpFallback( - KernelHarness.ValidationId, bytes32, KernelHarness.PackedUserOperation memory, bytes calldata - ) internal returns (uint256) => NONDET; - function ModuleManager._verifyInstallSignatureRaw(bool, uint256, KernelHarness.Install[] calldata, bytes calldata) - internal returns (uint256) => NONDET; - function Lib4337.chainAgnosticUserOpHash(address, KernelHarness.PackedUserOperation calldata) - internal returns (bytes32) => CONSTANT; - function Lib4337.intersectValidationData(uint256, uint256) internal returns (uint256) => NONDET; } -// =========================================================================== -// RULE 1 -- _installExecutor: post-install, the executor's hook is either -// the "no hook" sentinel (address(1)) OR an enabled hook in HookStorage. -// -// Writer source (ExecutorManager.sol:41): -// address hook = _internalData.length >= 20 -// ? address(bytes20(_internalData[0:20])) -// : HOOK_MODULE_NOT_INSTALLED; // i.e. address(0) -// if (hook == HOOK_MODULE_NOT_INSTALLED) { -// hook = HOOK_MODULE_INSTALLED_NO_HOOK; // remap 0 -> 1 -// } else { -// require(hook == HOOK_MODULE_INSTALLED_NO_HOOK -// || _hookEnabled(IHook(hook)), NotInstalled()); -// } -// _executorConfig(IExecutor(_executor)).hook = IHook(hook); -// -// Analysis: after the if/else, hook is one of: -// (a) HOOK_MODULE_INSTALLED_NO_HOOK (address(1)) -- via empty data / sentinel -// (b) HOOK_MODULE_INSTALLED_NO_HOOK (address(1)) -- explicit -// (c) _hookEnabled(hook) == true -- real hook, pre-validated -// In every branch the post-state satisfies the invariant. -// =========================================================================== -rule installExecutorPostHookOk( - env e, - address executor, - bytes internalData, - bool installSuccess -) { +rule installExecutorMarksInstalled(env e, address executor, bytes internalData, bool installSuccess) { harness_installExecutor@withrevert(e, executor, internalData, installSuccess); bool reverted = lastReverted; - - address postHook = harness_executorHook(executor); - - assert !reverted => - postHook == harness_HOOK_INSTALLED_NO_HOOK() - || harness_hookEnabled(postHook), - "installExecutor left executor.hook in an invalid state"; + assert !reverted => harness_executorInstalled(executor), + "successful executor installation must set installed"; } -// =========================================================================== -// RULE 2 -- _uninstallExecutor: post-uninstall, executor.hook is the -// "not installed" sentinel (address(0)). -// -// Writer source (ExecutorManager.sol:54): -// _executorConfig(IExecutor(_executor)).hook = IHook(HOOK_MODULE_NOT_INSTALLED); -// -// The writer unconditionally sets hook = 0. No branch can produce any other -// value. Note: the writer ignores _internalData and _installSuccess. -// =========================================================================== -rule uninstallExecutorClearsHook( - env e, - address executor, - bytes internalData, - bool installSuccess -) { +rule uninstallExecutorClearsInstalled(env e, address executor, bytes internalData, bool installSuccess) { harness_uninstallExecutor@withrevert(e, executor, internalData, installSuccess); bool reverted = lastReverted; - - address postHook = harness_executorHook(executor); - - assert !reverted => postHook == harness_HOOK_NOT_INSTALLED(), - "uninstallExecutor failed to clear executor.hook"; + assert !reverted => !harness_executorInstalled(executor), + "successful executor uninstall must clear installed"; } -// =========================================================================== -// RULE 3 -- _installSelector: post-install, selector.hook is in the documented -// hook-state envelope (NOT_INSTALLED for entryPoint-only fallback, OR the -// no-hook sentinel address(1), OR an enabled hook). -// -// Writer source (SelectorManager.sol:45): -// CallType callType = CallType.wrap(bytes1(_internalData[4])); -// require(callType == CALLTYPE_DELEGATECALL || _installSuccess, ...); -// bytes4 selector = bytes4(_internalData[0:4]); -// address hook = address(bytes20(_internalData[5:25])); -// if (hook != HOOK_MODULE_NOT_INSTALLED && hook != HOOK_MODULE_INSTALLED_NO_HOOK) { -// require(_hookEnabled(IHook(hook)), NotInstalled()); -// } -// $.target = _module; -// $.callType = callType; -// $.hook = IHook(hook); -// -// RELAXATION FROM ORIGINAL OBLIGATION (documented for auditor review): -// -// 1. `hook == HOOK_MODULE_NOT_INSTALLED (0)` is the DOCUMENTED entryPoint- -// only sentinel per `Kernel.sol:266` — `_fallback()` reads the selector's -// hook field and dispatches to the entryPoint when hook==0. This is a -// first-class production case, not an invariant violation. The rule now -// admits this branch. -// -// 2. `_module == address(0)` is NOT rejected by the writer. `Kernel.sol:266` -// requires `target != 0` for fallback dispatch, so a zero-target install -// is effectively a no-op at the dispatch boundary. This is a FOOTGUN at -// the install layer (the caller's intent is ignored), not a security -// bypass. Documented as MEDIUM-severity hardening candidate for -// `sc-developer` (add `require(_module != address(0))` to -// `_installSelector`), tracked in `audit/FV_COVERAGE.md`. The hook state -// envelope is the property this rule actually enforces. -// -// The rule now PROVES the relaxed (and accurate) property: post-install, the -// hook is in one of the three documented states. If a future writer mutation -// breaks that envelope (e.g., admits an enabled hook that has since been -// disabled), the rule will CEX. -// =========================================================================== -rule installSelectorPostInvariant( - env e, - address module, - bytes internalData, - bool installSuccess -) { - // Constrain internalData length to the minimum required by the writer - // (writer reads _internalData[5:25] => length >= 25). For shorter data - // the writer is guaranteed to revert via calldata-slice OOB; we exclude - // that branch up-front so the spec's selector projection is well-defined. - require internalData.length >= 25; - +rule installSelectorSetsTarget(env e, address module, bytes internalData, bool installSuccess) { bytes4 selector = harness_internalDataSelector(internalData); - harness_installSelector@withrevert(e, module, internalData, installSuccess); bool reverted = lastReverted; - - address postHook = harness_selectorHook(selector); - - assert !reverted => - postHook == harness_HOOK_NOT_INSTALLED() // entryPoint-only sentinel - || postHook == harness_HOOK_INSTALLED_NO_HOOK() // no-hook sentinel - || harness_hookEnabled(postHook), // enabled hook - "installSelector left selector hook in undocumented state"; + assert !reverted => harness_selectorTarget(selector) == module, + "successful selector installation must set target"; } -// =========================================================================== -// RULE 4 -- _uninstallSelector: post-uninstall, selector.target == 0. -// -// Writer source (SelectorManager.sol:62): -// bytes4 selector = bytes4(_internalData[0:4]); -// $.target = address(0); -// $.callType = CallType.wrap(bytes1(0x00)); -// $.hook = IHook(address(0)); -// -// The writer unconditionally clears all three fields. Note: the writer -// ignores _module and _installSuccess. As long as _internalData.length >= 4, -// the call succeeds; shorter internalData causes a calldata-slice revert -// (covered by the `!reverted` guard). -// =========================================================================== -rule uninstallSelectorClearsTarget( - env e, - address module, - bytes internalData, - bool installSuccess -) { - // Writer reads _internalData[0:4] => length >= 4. Shorter data reverts - // via calldata-slice OOB; restrict to the well-defined case so the - // selector projection is sound. - require internalData.length >= 4; - +rule uninstallSelectorClearsTarget(env e, address module, bytes internalData, bool installSuccess) { bytes4 selector = harness_internalDataSelector(internalData); - harness_uninstallSelector@withrevert(e, module, internalData, installSuccess); bool reverted = lastReverted; - - address postTarget = harness_selectorTarget(selector); - address postHook = harness_selectorHook(selector); - bytes1 postCT = harness_selectorCallType(selector); - - assert !reverted => - postTarget == 0 && postHook == 0 && postCT == to_bytes1(0), - "uninstallSelector failed to clear selector config"; -} - -// =========================================================================== -// RULE 5 -- _installHook: post-install, _hookEnabled(hook) == true. -// -// Writer source (HookManager.sol:36): -// if (_internalData.length == 0) { -// require(_installSuccess, ModuleInstallFailed()); -// } -// _hookStorage().enabled[_hook] = true; -// -// The writer unconditionally sets enabled[_hook] = true after passing the -// install-success guard (which only fires when internalData is empty). -// Post-state _hookEnabled(hook) (which reads the same mapping) must be true. -// =========================================================================== -rule installHookPostEnabled( - env e, - address hookAddr, - bytes internalData, - bool installSuccess -) { - harness_installHook@withrevert(e, hookAddr, internalData, installSuccess); - bool reverted = lastReverted; - - assert !reverted => harness_hookEnabled(hookAddr), - "installHook did not enable the hook"; -} - -// =========================================================================== -// RULE 6 -- _uninstallHook: post-uninstall, _hookEnabled(hook) == false. -// -// Writer source (HookManager.sol:45): -// _hookStorage().enabled[_hook] = false; -// -// Unconditional clear. Writer ignores _internalData and _installSuccess. -// =========================================================================== -rule uninstallHookPostDisabled( - env e, - address hookAddr, - bytes internalData, - bool installSuccess -) { - harness_uninstallHook@withrevert(e, hookAddr, internalData, installSuccess); - bool reverted = lastReverted; - - assert !reverted => !harness_hookEnabled(hookAddr), - "uninstallHook did not disable the hook"; -} - -// =========================================================================== -// SANITY RULES -- ensure each writer is reachable (not vacuously reverting). -// If a sanity rule is unsatisfiable, the corresponding rule is vacuous. -// =========================================================================== - -rule sanityInstallExecutorReaches(env e, address executor, bytes internalData, bool installSuccess) { - harness_installExecutor@withrevert(e, executor, internalData, installSuccess); - satisfy !lastReverted; -} - -rule sanityUninstallExecutorReaches(env e, address executor, bytes internalData, bool installSuccess) { - harness_uninstallExecutor@withrevert(e, executor, internalData, installSuccess); - satisfy !lastReverted; -} - -rule sanityInstallSelectorReaches(env e, address module, bytes internalData, bool installSuccess) { - harness_installSelector@withrevert(e, module, internalData, installSuccess); - satisfy !lastReverted; -} - -rule sanityUninstallSelectorReaches(env e, address module, bytes internalData, bool installSuccess) { - harness_uninstallSelector@withrevert(e, module, internalData, installSuccess); - satisfy !lastReverted; -} - -rule sanityInstallHookReaches(env e, address hookAddr, bytes internalData, bool installSuccess) { - harness_installHook@withrevert(e, hookAddr, internalData, installSuccess); - satisfy !lastReverted; -} - -rule sanityUninstallHookReaches(env e, address hookAddr, bytes internalData, bool installSuccess) { - harness_uninstallHook@withrevert(e, hookAddr, internalData, installSuccess); - satisfy !lastReverted; + assert !reverted => harness_selectorTarget(selector) == 0, + "successful selector uninstall must clear target"; } diff --git a/certora/specs/PhaseCWriterLocal.spec b/certora/specs/PhaseCWriterLocal.spec index b5430f2e..2ba7535e 100644 --- a/certora/specs/PhaseCWriterLocal.spec +++ b/certora/specs/PhaseCWriterLocal.spec @@ -8,7 +8,7 @@ * * For any vId != $.root: * NOT ( _allowedSelector(vId, executeUserOp.selector) - * AND vInfo[vId].hook == HOOK_MODULE_INSTALLED_NO_HOOK ) + * AND vInfo[vId].installed && vInfo[vId].scopedExecutionHook == address(0) ) * * jointly enforced by: * - commit 0921b25 -- `_grantAccess` rejects executeUserOp.selector for @@ -42,7 +42,7 @@ * * _grantAccess -- pure storage writes, no external calls * * _setRoot(vId) -- pure storage writes * * _uninstallValidation -- single storage write - * * _initializeValidation -- calls _hookEnabled (view-only) and + * * _initializeValidation -- updates validation state and calls * _grantAccess (covered by Rule #1) * * The conjunction of the four writer-local rules implies the global @@ -50,12 +50,12 @@ * - $.allowed[*][*] is written ONLY by _grantAccess. * - $.vInfo[*].nonce is incremented ONLY by _grantAccess, _setRoot * (rotation), and _initializeValidation (empty-data path). - * - $.vInfo[*].hook is written ONLY by _uninstallValidation and + * - $.vInfo[*].installed is written ONLY by _uninstallValidation and * _initializeValidation. * - $.root is written ONLY by _setRoot. * Verified by manual grep over src/ on 2026-05-21. No other code path * touches those slots. Constructor / initialize establishes the base - * state where allowed[*][*] == 0 and vInfo[*].hook == 0 universally, + * state where allowed[*][*] == 0 and vInfo[*].installed == 0 universally, * trivially satisfying the property. * * VICTIM-vId FRAMING @@ -91,13 +91,11 @@ methods { // Read-only state accessors. function harness_vInfoNonce(bytes21) external returns (uint32) envfree; - function harness_vInfoHook(bytes21) external returns (address) envfree; + function harness_vInfoInstalled(bytes21) external returns (bool) envfree; + function harness_vInfoScopedExecutionHook(bytes21) external returns (address) envfree; function harness_allowedNonce(bytes21, bytes4) external returns (uint32) envfree; function harness_allowedSelector(bytes21, bytes4) external returns (bool) envfree; function harness_root() external returns (bytes21) envfree; - - function harness_HOOK_NOT_INSTALLED() external returns (address) envfree; - function harness_HOOK_INSTALLED_NO_HOOK() external returns (address) envfree; function harness_executeUserOpSelector() external returns (bytes4) envfree; // The four writer wrappers (Phase C Round 2 harness additions). @@ -124,21 +122,12 @@ methods { internal returns (bytes32) => CONSTANT; function Lib4337.intersectValidationData(uint256, uint256) internal returns (uint256) => NONDET; - // _hookEnabled is called only from _initializeValidation's non-empty - // path to gate hook acceptance. It is a pure view function over - // HookStorage (a separate namespaced slot); it cannot write - // ValidationStorage. The concrete implementation lives in HookManager; - // the call in ValidationManager dispatches to it via virtual override. - // Without a NONDET summary the inlining may add unnecessary search; - // with NONDET both branches (hook accepted / rejected) are explored. - // IHook is an interface type; CVL requires the underlying EVM type (address). - function HookManager._hookEnabled(address) internal returns (bool) => NONDET; } // --------------------------------------------------------------------------- // Predicate: the "fast-path bypass conjunction" for victimVid. // isBypassable(v) == _allowedSelector(v, executeUserOp.selector) -// AND vInfo[v].hook == HOOK_MODULE_INSTALLED_NO_HOOK +// AND vInfo[v].installed && vInfo[v].scopedExecutionHook == address(0) // // The global property says: for victimVid != $.root, NOT isBypassable(victimVid). // Each writer-local rule says: any call to that writer that started from a @@ -147,7 +136,7 @@ methods { // --------------------------------------------------------------------------- definition isBypassable(bytes21 v) returns bool = harness_allowedSelector(v, harness_executeUserOpSelector()) - && harness_vInfoHook(v) == harness_HOOK_INSTALLED_NO_HOOK(); + && (harness_vInfoInstalled(v) && harness_vInfoScopedExecutionHook(v) == 0); // --------------------------------------------------------------------------- // Storage-shape invariant: `allowed[v][sel] <= vInfo[v].nonce` for every @@ -279,16 +268,16 @@ rule setRootPreservesNonBypass( // RULE 3 -- _uninstallValidation preserves non-bypass for non-root vIds. // // _uninstallValidation writes: -// $.vInfo[targetVid].hook = HOOK_MODULE_NOT_INSTALLED +// $.vInfo[targetVid].installed = false // // The function reverts if targetVid == $.root (CannotUninstallRoot), so a // successful call leaves $.root unchanged. // // Effect on isBypassable(victimVid): -// * If victimVid == targetVid: post-state hook is HOOK_MODULE_NOT_INSTALLED, -// which is not HOOK_MODULE_INSTALLED_NO_HOOK, so the conjunction's +// * If victimVid == targetVid: post-state hook is installed == false, +// which is not a zero scopedExecutionHook, so the conjunction's // second conjunct is false. Property holds. -// * If victimVid != targetVid: vInfo[victimVid].hook, +// * If victimVid != targetVid: vInfo[victimVid].installed, // allowed[victimVid][*], and vInfo[victimVid].nonce are all unchanged. // Property holds by pre-condition. // =========================================================================== @@ -321,7 +310,7 @@ rule uninstallValidationPreservesNonBypass( // _initializeValidation has two branches based on _internalData.length: // // (A) Empty data: -// $.vInfo[targetVid].hook = HOOK_MODULE_INSTALLED_NO_HOOK +// $.vInfo[targetVid].scopedExecutionHook = address(0) // $.vInfo[targetVid].nonce += 1 // The nonce bump (commits 9f9471c, ce185f6) ensures that any // allowed[targetVid][sel] entries from a prior incarnation become @@ -330,7 +319,7 @@ rule uninstallValidationPreservesNonBypass( // holds for targetVid. // // (B) Non-empty data: -// $.vInfo[targetVid].hook = (parsed hook from first 20 bytes, +// $.vInfo[targetVid].scopedExecutionHook = (parsed validation-scoped execution hook, // possibly remapped to INSTALLED_NO_HOOK) // then calls _grantAccess(targetVid, remaining selectors) // @@ -342,7 +331,7 @@ rule uninstallValidationPreservesNonBypass( // In both branches, vInfo[victimVid] for victimVid != targetVid is // untouched. So the property holds for any victimVid != $.root. // -// The function also reverts if vInfo[targetVid].hook is already non-zero +// The function also reverts if vInfo[targetVid].installed is already true // (`OccupiedValidationId`), which restricts the writer to fresh slots. // =========================================================================== rule initializeValidationPreservesNonBypass( diff --git a/certora/specs/SetRootLifo.spec b/certora/specs/SetRootLifo.spec index 3a19a9ae..b356f723 100644 --- a/certora/specs/SetRootLifo.spec +++ b/certora/specs/SetRootLifo.spec @@ -8,7 +8,7 @@ * current root is a VALIDATION_TYPE_PERMISSION must, after the call: * - vInfo[oldRoot].policies.length == 0 * - vInfo[oldRoot].signer == address(0) - * - vInfo[oldRoot].hook == HOOK_MODULE_NOT_INSTALLED + * - !vInfo[oldRoot].installed * * The Kernel.sol implementation (lines 343-385) walks the policies array in * LIFO order (`for (i = policies.length; i > 0; i--)`) calling @@ -47,7 +47,8 @@ methods { // Harness accessors used by the rule. function harness_vInfoNonce(bytes21) external returns (uint32) envfree; - function harness_vInfoHook(bytes21) external returns (address) envfree; + function harness_vInfoInstalled(bytes21) external returns (bool) envfree; + function harness_vInfoScopedExecutionHook(bytes21) external returns (address) envfree; function harness_vInfoSigner(bytes21) external returns (address) envfree; function harness_vInfoPoliciesLength(bytes21) external returns (uint256) envfree; function harness_vInfoPolicyAt(bytes21, uint256) external returns (address) envfree; @@ -57,9 +58,6 @@ methods { function harness_VT_VALIDATOR() external returns (bytes1) envfree; function harness_VT_PERMISSION() external returns (bytes1) envfree; - function harness_HOOK_NOT_INSTALLED() external returns (address) envfree; - function harness_HOOK_INSTALLED_NO_HOOK() external returns (address) envfree; - // Disambiguate the two `setRoot` overloads on Kernel.sol so the rule can // call the one taking (Install[], bool, bytes). The other overload takes // a single ValidationId argument. @@ -99,7 +97,7 @@ methods { // Post (on success): // - vInfo[oldRoot].policies.length == 0 // - vInfo[oldRoot].signer == 0 -// - vInfo[oldRoot].hook == HOOK_MODULE_NOT_INSTALLED +// - !vInfo[oldRoot].installed // // The `loop_iter=3` config bounds the symbolic unrolling at 3 iterations. // `optimistic_loop=true` axiomatises termination beyond that bound. The @@ -118,7 +116,7 @@ rule setRootClearsOldPermissionState( // The current root must be an installed permission-type ValidationId. require oldRoot != to_bytes21(0); require harness_getType(oldRoot) == harness_VT_PERMISSION(); - require harness_vInfoHook(oldRoot) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(oldRoot); // Permission has at least one policy installed. require harness_vInfoPoliciesLength(oldRoot) > 0; @@ -146,8 +144,10 @@ rule setRootClearsOldPermissionState( "old permission policies.length not cleared"; assert !reverted => harness_vInfoSigner(oldRoot) == 0, "old permission signer not zeroed"; - assert !reverted => harness_vInfoHook(oldRoot) == harness_HOOK_NOT_INSTALLED(), - "old permission hook not zeroed"; + assert !reverted => !harness_vInfoInstalled(oldRoot), + "old permission remains installed"; + assert !reverted => harness_vInfoScopedExecutionHook(oldRoot) == 0, + "old validation scoped execution hook not zeroed"; } // -------------------------------------------------------------------------- @@ -164,7 +164,7 @@ rule sanitySetRootReachesSuccess( bytes21 oldRoot = harness_root(); require oldRoot != to_bytes21(0); require harness_getType(oldRoot) == harness_VT_PERMISSION(); - require harness_vInfoHook(oldRoot) != harness_HOOK_NOT_INSTALLED(); + require harness_vInfoInstalled(oldRoot); require harness_vInfoPoliciesLength(oldRoot) > 0; require removeCurrent; require pkg.length == 1; diff --git a/certora/specs/SystemComposition.spec b/certora/specs/SystemComposition.spec index a1abe353..661f15de 100644 --- a/certora/specs/SystemComposition.spec +++ b/certora/specs/SystemComposition.spec @@ -48,8 +48,7 @@ * * 1. Structural invariant from commits 0921b25 + ce185f6 * For any non-root vId, NOT( _allowedSelector(vId, - * executeUserOp.selector) AND vInfo[vId].hook == - * HOOK_MODULE_INSTALLED_NO_HOOK ). + * executeUserOp.selector) AND vInfo[vId].installed AND vInfo[vId].scopedExecutionHook == address(0) ). * Established by `_grantAccess` rejecting the executeUserOp grant * for non-root vIds and by `_setRoot` bumping the old root's nonce * on rotation. This invariant rules out the fast-path branch @@ -193,7 +192,8 @@ methods { // Harness storage accessors. function harness_vInfoNonce(bytes21) external returns (uint32) envfree; - function harness_vInfoHook(bytes21) external returns (address) envfree; + function harness_vInfoInstalled(bytes21) external returns (bool) envfree; + function harness_vInfoScopedExecutionHook(bytes21) external returns (address) envfree; function harness_allowedNonce(bytes21, bytes4) external returns (uint32) envfree; function harness_allowedSelector(bytes21, bytes4) external returns (bool) envfree; function harness_root() external returns (bytes21) envfree; @@ -205,8 +205,6 @@ methods { function harness_VT_ROOT() external returns (bytes1) envfree; function harness_VT_VALIDATOR() external returns (bytes1) envfree; function harness_VT_PERMISSION() external returns (bytes1) envfree; - function harness_HOOK_NOT_INSTALLED() external returns (address) envfree; - function harness_HOOK_INSTALLED_NO_HOOK() external returns (address) envfree; function harness_executeUserOpSelector() external returns (bytes4) envfree; function harness_isEnableMode(uint256) external returns (bool) envfree; function harness_isReplayableMode(uint256) external returns (bool) envfree; @@ -242,10 +240,6 @@ methods { internal returns (bytes32) => CONSTANT; function Lib4337.intersectValidationData(uint256, uint256) internal returns (uint256) => NONDET; - // _hookEnabled is read in the non-empty branch of _initializeValidation, - // which is not reachable from validateUserOp / executeUserOp on the non- - // enable path. Summarise anyway for consistency with PhaseCWriterLocal. - function HookManager._hookEnabled(address) internal returns (bool) => NONDET; } // --------------------------------------------------------------------------- @@ -264,7 +258,7 @@ methods { invariant nonRootCannotBypassFastPathWithExecuteUserOp(bytes21 vId) vId != harness_root() => !(harness_allowedSelector(vId, harness_executeUserOpSelector()) - && harness_vInfoHook(vId) == harness_HOOK_INSTALLED_NO_HOOK()); + && (harness_vInfoInstalled(vId) && harness_vInfoScopedExecutionHook(vId) == 0)); // --------------------------------------------------------------------------- // Storage-shape hypothesis: `allowed[v][sel] <= vInfo[v].nonce`. See diff --git a/snapshots/KernelFactoryTest.json b/snapshots/KernelFactoryTest.json index 46864c38..463ea067 100644 --- a/snapshots/KernelFactoryTest.json +++ b/snapshots/KernelFactoryTest.json @@ -1,3 +1,3 @@ { - "Mock - deploy()": "208556" + "Mock - deploy()": "208042" } \ No newline at end of file diff --git a/snapshots/KernelImmutableECDSATest.json b/snapshots/KernelImmutableECDSATest.json index db4cb760..5daa5f38 100644 --- a/snapshots/KernelImmutableECDSATest.json +++ b/snapshots/KernelImmutableECDSATest.json @@ -1,5 +1,5 @@ { - "Install - 3": "252850", - "Root - foo()": "104042", + "Install - 3": "251757", + "Root - foo()": "103920", "Simple - foo()": "100208" } \ No newline at end of file diff --git a/snapshots/KernelTest.json b/snapshots/KernelTest.json index 7146bff8..6331794e 100644 --- a/snapshots/KernelTest.json +++ b/snapshots/KernelTest.json @@ -1,5 +1,5 @@ { - "Install - 3": "256993", - "Root - foo()": "133925", + "Install - 3": "255728", + "Root - foo()": "133732", "Simple - foo()": "100208" } \ No newline at end of file diff --git a/test/Kernel.t.sol b/test/Kernel.t.sol index 65560f43..ee8c4362 100644 --- a/test/Kernel.t.sol +++ b/test/Kernel.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import {EntryPointLib} from "./utils/EntryPointLib.sol"; import {KernelUUPS} from "src/KernelUUPS.sol"; import {KernelFactory} from "src/KernelFactory.sol"; -import {KernelUUPS} from "src/KernelUUPS.sol"; import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; import {Install} from "src/types/Structs.sol"; import {MockFallback} from "./mock/MockFallback.sol"; @@ -15,7 +14,6 @@ import {MockCallee} from "./mock/MockCallee.sol"; import {NotImplemented} from "src/types/Error.sol"; import {InvalidInitialization} from "src/types/Error.sol"; import {InvalidSelector} from "src/types/Error.sol"; -import {Install} from "src/types/Structs.sol"; import {ERC1967_IMPLEMENTATION_SLOT} from "src/types/Constants.sol"; import {KernelUserOpTest} from "./KernelUserOpTest.sol"; import {KernelERC1271Test} from "./KernelERC1271Test.sol"; @@ -23,7 +21,6 @@ import {KernelExecutorTest} from "./KernelExecutorTest.sol"; import {KernelValidatorTest} from "./KernelValidatorTest.sol"; import {KernelExecuteTest} from "./KernelExecuteTest.sol"; import {KernelSelectorTest} from "./KernelSelectorTest.sol"; -import {KernelHookTest} from "./KernelHookTest.sol"; import {ChainAgnosticHashHelper} from "./utils/ChainAgnosticHashHelper.sol"; import {PermissionId} from "src/types/Types.sol"; @@ -33,8 +30,7 @@ contract KernelTest is KernelExecutorTest, KernelValidatorTest, KernelExecuteTest, - KernelSelectorTest, - KernelHookTest + KernelSelectorTest { KernelUUPS uups; @@ -134,9 +130,10 @@ contract KernelTest is assertTrue(kernel.supportsModule(1)); assertTrue(kernel.supportsModule(2)); assertTrue(kernel.supportsModule(3)); - assertTrue(kernel.supportsModule(4)); + assertFalse(kernel.supportsModule(4)); assertTrue(kernel.supportsModule(5)); assertTrue(kernel.supportsModule(6)); assertFalse(kernel.supportsModule(7)); + assertTrue(kernel.supportsModule(11)); } } diff --git a/test/KernelExecutorTest.sol b/test/KernelExecutorTest.sol index fb35d7e4..52912be9 100644 --- a/test/KernelExecutorTest.sol +++ b/test/KernelExecutorTest.sol @@ -5,7 +5,13 @@ import {Call} from "src/types/Structs.sol"; import {MockExecutor} from "./mock/MockExecutor.sol"; import {MockCallee} from "./mock/MockCallee.sol"; import {KernelTestBase} from "./KernelTestBase.sol"; -import {Unauthorized} from "src/types/Error.sol"; +import {Unauthorized, InvalidDataLength, ScopedExecutionHookStillInstalled} from "src/types/Error.sol"; +import {SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE} from "src/types/Constants.sol"; +import { + executorScopedExecutionHookId, + getScopedExecutionHookScope, + getScopedExecutionHookExecutor +} from "src/lib/Utils.sol"; abstract contract KernelExecutorTest is KernelTestBase { function test_execute_from_executor_fail_not_executor() external { @@ -21,31 +27,65 @@ abstract contract KernelExecutorTest is KernelTestBase { assertTrue(kernel.supportsModule(2)); address newEx = address(new MockExecutor()); kernel.installModule(2, newEx, abi.encode(hex"deadbeef", "")); - assertEq(address(kernel.executorConfig(newEx).hook), address(1)); + assertTrue(kernel.executorConfig(newEx).installed); assertTrue(kernel.isModuleInstalled(2, newEx, hex"")); } function test_install_executor_oninstall_fail() external unitTest { address newEx = makeAddr("New Executor"); kernel.installModule(2, newEx, abi.encode(hex"", "")); - assertEq(address(kernel.executorConfig(newEx).hook), address(1)); + assertTrue(kernel.executorConfig(newEx).installed); assertTrue(kernel.isModuleInstalled(2, newEx, hex"")); } + function test_install_executor_rejects_legacy_hook_data() external unitTest { + MockExecutor newEx = new MockExecutor(); + vm.expectRevert(InvalidDataLength.selector); + kernel.installModule(2, address(newEx), abi.encode(hex"", abi.encodePacked(address(1)))); + } + function test_uninstall_executor_onuninstall_success() external unitTest { address newEx = address(new MockExecutor()); kernel.installModule(2, newEx, abi.encode(hex"deadbeef", "")); - assertEq(address(kernel.executorConfig(newEx).hook), address(1)); + assertTrue(kernel.executorConfig(newEx).installed); kernel.uninstallModule(2, newEx, abi.encode(hex"", hex"")); - assertEq(address(kernel.executorConfig(newEx).hook), address(0)); + assertFalse(kernel.executorConfig(newEx).installed); } function test_uninstall_executor_onuninstall_fail() external unitTest { address newEx = makeAddr("New Executor"); kernel.installModule(2, newEx, abi.encode(hex"", "")); - assertEq(address(kernel.executorConfig(newEx).hook), address(1)); + assertTrue(kernel.executorConfig(newEx).installed); kernel.uninstallModule(2, newEx, abi.encode(hex"", hex"")); - assertEq(address(kernel.executorConfig(newEx).hook), address(0)); + assertFalse(kernel.executorConfig(newEx).installed); + } + + function test_executor_scoped_execution_hook() external { + bytes memory hookContext = abi.encodePacked(SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE, bytes20(executor)); + vm.startPrank(address(ep)); + kernel.installModule(11, address(hook), abi.encode(hex"deadbeef", hookContext)); + assertTrue(kernel.isModuleInstalled(11, address(hook), hookContext)); + assertEq(address(kernel.executorConfig(executor).scopedExecutionHook), address(hook)); + vm.stopPrank(); + + vm.prank(executor); + kernel.executeFromExecutor( + bytes32(0), abi.encodePacked(address(callee), uint256(0), abi.encodeWithSelector(MockCallee.foo.selector)) + ); + bytes32 expectedId = executorScopedExecutionHookId(executor); + assertEq(getScopedExecutionHookScope(expectedId), SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE); + assertEq(getScopedExecutionHookExecutor(expectedId), executor); + assertEq(hook.preCheckId(address(kernel)), expectedId); + assertEq(hook.postCheckId(address(kernel)), expectedId); + assertEq(callee.bar(), 1); + + vm.startPrank(address(ep)); + vm.expectRevert(ScopedExecutionHookStillInstalled.selector); + kernel.uninstallModule(2, executor, abi.encode(hex"", hex"")); + kernel.uninstallModule(11, address(hook), abi.encode(hex"", hookContext)); + kernel.uninstallModule(2, executor, abi.encode(hex"", hex"")); + assertFalse(kernel.executorConfig(executor).installed); + vm.stopPrank(); } function test_execute_from_executor() external unitTestExecutor { diff --git a/test/KernelHookTest.sol b/test/KernelHookTest.sol deleted file mode 100644 index bcb4765e..00000000 --- a/test/KernelHookTest.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity ^0.8.0; - -import {KernelTestBase} from "./KernelTestBase.sol"; - -abstract contract KernelHookTest is KernelTestBase { - function test_install_hook() external unitTest { - assertTrue(kernel.supportsModule(4)); - kernel.installModule(4, address(hook), abi.encode(hex"", "")); - assertTrue(kernel.isModuleInstalled(4, address(hook), hex"")); - } - - function test_uninstall_hook() external unitTest { - kernel.installModule(4, address(hook), abi.encode(hex"", "")); - kernel.uninstallModule(4, address(hook), abi.encode(hex"", "")); - } -} diff --git a/test/KernelSelectorTest.sol b/test/KernelSelectorTest.sol index c462c78d..7232218f 100644 --- a/test/KernelSelectorTest.sol +++ b/test/KernelSelectorTest.sol @@ -1,9 +1,18 @@ pragma solidity ^0.8.0; import {MockFallback} from "./mock/MockFallback.sol"; +import {Kernel} from "src/Kernel.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {CallType} from "src/types/Types.sol"; import {KernelTestBase} from "./KernelTestBase.sol"; -import {InvalidSelector} from "src/types/Error.sol"; +import {InvalidSelector, InvalidDataLength, ScopedExecutionHookStillInstalled} from "src/types/Error.sol"; +import {SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE} from "src/types/Constants.sol"; +import { + selectorScopedExecutionHookId, + getScopedExecutionHookScope, + getScopedExecutionHookSelector +} from "src/lib/Utils.sol"; import {SelectorConfig} from "src/types/Structs.sol"; abstract contract KernelSelectorTest is KernelTestBase { @@ -13,9 +22,7 @@ abstract contract KernelSelectorTest is KernelTestBase { kernel.installModule( 3, address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00), address(1)) - ) + abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00))) ); vm.stopPrank(); vm.startPrank(newCaller); @@ -27,39 +34,42 @@ abstract contract KernelSelectorTest is KernelTestBase { assertEq(caller, newCaller); SelectorConfig memory c = kernel.selectorConfig(MockFallback.fallbackFunction.selector); assertEq(address(c.target), address(mockFallback)); - assertEq(address(c.hook), address(1)); assertTrue(c.callType == CallType.wrap(bytes1(0x00))); assertTrue( kernel.isModuleInstalled(3, address(mockFallback), abi.encodePacked(MockFallback.fallbackFunction.selector)) ); } - function test_install_selector_call_withhook() external unitTest { - kernel.installModule(4, address(hook), abi.encode(hex"", "")); + function test_selector_scoped_execution_hook() external unitTest { + bytes4 selector = MockFallback.fallbackFunction.selector; + bytes memory hookContext = abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, selector); kernel.installModule( - 3, - address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00), address(hook)) - ) + 3, address(mockFallback), abi.encode(hex"deadbeef", abi.encodePacked(selector, bytes1(0x00))) ); - vm.expectEmit(address(mockFallback)); - emit MockFallback.Foobar(); + kernel.installModule(11, address(hook), abi.encode(hex"deadbeef", hookContext)); + uint256 res = MockFallback(address(kernel)).fallbackFunction(10); + bytes32 expectedId = selectorScopedExecutionHookId(selector); + assertEq(getScopedExecutionHookScope(expectedId), SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE); + assertEq(getScopedExecutionHookSelector(expectedId), selector); assertEq(res, 100); - SelectorConfig memory c = kernel.selectorConfig(MockFallback.fallbackFunction.selector); - assertEq(address(c.target), address(mockFallback)); - assertEq(address(c.hook), address(hook)); - assertTrue(c.callType == CallType.wrap(bytes1(0x00))); + assertEq(hook.preCheckId(address(kernel)), expectedId); + assertEq(hook.postCheckId(address(kernel)), expectedId); + assertTrue(kernel.isModuleInstalled(11, address(hook), hookContext)); + assertEq(address(kernel.selectorConfig(selector).scopedExecutionHook), address(hook)); + + vm.expectRevert(ScopedExecutionHookStillInstalled.selector); + kernel.uninstallModule(3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector))); + kernel.uninstallModule(11, address(hook), abi.encode(hex"", hookContext)); + kernel.uninstallModule(3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector))); + assertEq(kernel.selectorConfig(selector).target, address(0)); } function test_uninstall_selector_call() external unitTest { kernel.installModule( 3, address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00), address(1)) - ) + abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00))) ); vm.expectEmit(address(mockFallback)); emit MockFallback.Foobar(); @@ -67,14 +77,12 @@ abstract contract KernelSelectorTest is KernelTestBase { assertEq(res, 100); SelectorConfig memory c = kernel.selectorConfig(MockFallback.fallbackFunction.selector); assertEq(address(c.target), address(mockFallback)); - assertEq(address(c.hook), address(1)); assertTrue(c.callType == CallType.wrap(bytes1(0x00))); kernel.uninstallModule( 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(MockFallback.fallbackFunction.selector)) ); c = kernel.selectorConfig(MockFallback.fallbackFunction.selector); assertEq(address(c.target), address(0)); - assertEq(address(c.hook), address(0)); assertTrue(c.callType == CallType.wrap(bytes1(0x00))); } @@ -82,9 +90,7 @@ abstract contract KernelSelectorTest is KernelTestBase { kernel.installModule( 3, address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00), address(1)) - ) + abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00))) ); vm.expectRevert(MockFallback.Limit.selector, address(mockFallback)); MockFallback(address(kernel)).fallbackFunction(100); @@ -94,9 +100,7 @@ abstract contract KernelSelectorTest is KernelTestBase { kernel.installModule( 3, address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0xff), address(1)) - ) + abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0xff))) ); vm.expectEmit(address(kernel)); emit MockFallback.Foobar(); @@ -108,21 +112,51 @@ abstract contract KernelSelectorTest is KernelTestBase { kernel.installModule( 3, address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0xff), address(1)) - ) + abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0xff))) ); vm.expectRevert(MockFallback.Limit.selector, address(kernel)); MockFallback(address(kernel)).fallbackFunction(100); } + function test_install_selector_rejects_trailing_legacy_hook_data() external unitTest { + vm.expectRevert(InvalidDataLength.selector); + kernel.installModule( + 3, + address(mockFallback), + abi.encode(hex"", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00), address(1))) + ); + } + + function test_install_selector_native_routes_take_precedence() external unitTest { + bytes4[4] memory selectors = [ + Kernel.supportsModule.selector, + IERC721Receiver.onERC721Received.selector, + IERC1155Receiver.onERC1155Received.selector, + IERC1155Receiver.onERC1155BatchReceived.selector + ]; + for (uint256 i; i < selectors.length; i++) { + kernel.installModule( + 3, address(mockFallback), abi.encode(hex"deadbeef", abi.encodePacked(selectors[i], bytes1(0x00))) + ); + assertEq(kernel.selectorConfig(selectors[i]).target, address(mockFallback)); + } + + bytes memory hookContext = + abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, IERC721Receiver.onERC721Received.selector); + kernel.installModule(11, address(hook), abi.encode(hex"deadbeef", hookContext)); + + assertTrue(kernel.supportsModule(3)); + bytes4 result = IERC721Receiver(address(kernel)).onERC721Received(address(this), address(this), 1, ""); + assertEq(result, IERC721Receiver.onERC721Received.selector); + assertEq(hook.preCheckId(address(kernel)), bytes32(0)); + assertEq(hook.postCheckId(address(kernel)), bytes32(0)); + } + function test_install_selector_invalid_selector() external unitTest { kernel.installModule( 3, address(mockFallback), - abi.encode( - hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0xff), address(1)) - ) + abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0xff))) ); vm.expectRevert(InvalidSelector.selector, address(kernel)); MockFallback(address(kernel)).getData(); diff --git a/test/KernelTestBase.sol b/test/KernelTestBase.sol index f83ec459..d1b16ef3 100644 --- a/test/KernelTestBase.sol +++ b/test/KernelTestBase.sol @@ -148,10 +148,7 @@ abstract contract KernelTestBase is Test { ) internal returns (bytes memory sig) { Install[] memory packages = new Install[](1); packages[0] = Install({ - moduleType: 1, - module: address(newValidator), - moduleData: hex"", - internalData: abi.encodePacked(address(0), selector) + moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: abi.encodePacked(selector) }); sig = abi.encode( uint256(0), packages, enableSig(nonce, enableSuccess, replayable, packages, signEnable), userOpSig @@ -174,7 +171,7 @@ abstract contract KernelTestBase is Test { moduleType: 6, module: address(signer), moduleData: hex"", - internalData: abi.encodePacked(permissionId, address(0), selector) + internalData: abi.encodePacked(permissionId, selector) }); sig = abi.encode( diff --git a/test/KernelUserOpTest.sol b/test/KernelUserOpTest.sol index 1729e7cc..2ee50e34 100644 --- a/test/KernelUserOpTest.sol +++ b/test/KernelUserOpTest.sol @@ -7,7 +7,7 @@ import {MockCallee} from "./mock/MockCallee.sol"; import {KernelTestBase} from "./KernelTestBase.sol"; import {PermissionId} from "src/types/Types.sol"; import {ValidationId} from "src/types/Types.sol"; -import {InvalidVid} from "src/types/Error.sol"; +import {InvalidVid, UnauthorizedCallData} from "src/types/Error.sol"; import {permissionToIdentifier, validatorToIdentifier} from "src/lib/Utils.sol"; import {SimpleAccount} from "account-abstraction/accounts/SimpleAccount.sol"; import {SimpleAccountFactory} from "account-abstraction/accounts/SimpleAccountFactory.sol"; @@ -16,6 +16,39 @@ import {LibBytes} from "solady/utils/LibBytes.sol"; abstract contract KernelUserOpTest is KernelTestBase { error Result(uint256 gas); + function _rootUserOpWithCallData(bytes memory callData) internal view returns (PackedUserOperation memory) { + return PackedUserOperation({ + sender: address(kernel), + nonce: encodeNonce(false, false, false, bytes1(0), bytes20(0)), + initCode: hex"", + callData: callData, + accountGasLimits: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))), + preVerificationGas: 0, + gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), + paymasterAndData: hex"", + signature: hex"" + }); + } + + function test_validateuserop_rejects_direct_validateuserop_calldata() external entryPointTest { + PackedUserOperation memory op = _rootUserOpWithCallData(abi.encodePacked(Kernel.validateUserOp.selector)); + + vm.startPrank(address(ep)); + vm.expectRevert(UnauthorizedCallData.selector); + kernel.validateUserOp(op, bytes32(0), 0); + vm.stopPrank(); + } + + function test_validateuserop_rejects_wrapped_validateuserop_calldata() external entryPointTest { + PackedUserOperation memory op = + _rootUserOpWithCallData(abi.encodePacked(Kernel.executeUserOp.selector, Kernel.validateUserOp.selector)); + + vm.startPrank(address(ep)); + vm.expectRevert(UnauthorizedCallData.selector); + kernel.validateUserOp(op, bytes32(0), 0); + vm.stopPrank(); + } + function estimateUserOpGasLimit(PackedUserOperation memory op) internal returns (uint128, uint128) { try this.simulateEntrypointCall(op) {} catch (bytes memory err) { @@ -246,81 +279,7 @@ abstract contract KernelUserOpTest is KernelTestBase { ep.handleOps(ops, beneficiary); vm.stopPrank(); assertEq(callee.bar(), 1); - assertEq(kernel.validationInfo(validatorToIdentifier(newValidator)).hook, address(1)); - } - - function test_userop_validator_hook_failed_prehook() external entryPointTest { - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", "")); - kernel.installModule( - 1, - address(newValidator), - abi.encode(hex"deadbeef", abi.encodePacked(address(hook), kernel.execute.selector)) - ); - vm.stopPrank(); - hook.setRevertOnPreHook(true); - - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = PackedUserOperation({ - sender: address(kernel), - nonce: encodeNonce(false, false, false, bytes1(0x01), bytes20(address(newValidator))), - initCode: hex"", - callData: abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))), - preVerificationGas: 1000000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - ops[0].signature = _validatorSignUserOp(ops[0], true, false); - vm.startPrank(beneficiary, beneficiary); - ep.handleOps(ops, beneficiary); - vm.stopPrank(); - assertEq(callee.bar(), 0); - } - - function test_userop_validator_hook_failed_posthook() external entryPointTest { - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", "")); - kernel.installModule( - 1, - address(newValidator), - abi.encode(hex"deadbeef", abi.encodePacked(address(hook), kernel.execute.selector)) - ); - vm.stopPrank(); - hook.setRevertOnPostHook(true); - - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = PackedUserOperation({ - sender: address(kernel), - nonce: encodeNonce(false, false, false, bytes1(0x01), bytes20(address(newValidator))), - initCode: hex"", - callData: abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))), - preVerificationGas: 1000000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - ops[0].signature = _validatorSignUserOp(ops[0], true, false); - vm.startPrank(beneficiary, beneficiary); - ep.handleOps(ops, beneficiary); - vm.stopPrank(); - assertEq(callee.bar(), 0); + assertTrue(kernel.validationInfo(validatorToIdentifier(newValidator)).installed); } function test_userop_validator_aa24_enable_fail_wrong_signature() external entryPointTest { @@ -357,7 +316,7 @@ abstract contract KernelUserOpTest is KernelTestBase { // arbitrary modules without root approval. function test_userop_enable_failed_root_sig_does_not_install() external entryPointTest { // newValidator is not installed yet. - assertEq(kernel.validationInfo(validatorToIdentifier(newValidator)).hook, address(0)); + assertFalse(kernel.validationInfo(validatorToIdentifier(newValidator)).installed); PackedUserOperation memory op = PackedUserOperation({ sender: address(kernel), @@ -387,9 +346,8 @@ abstract contract KernelUserOpTest is KernelTestBase { // Failed root signature must surface as validation failure... assertEq(uint160(validationData), 1, "H-01: failed root sig must return SIG_VALIDATION_FAILED"); // ...and the module must NOT have been installed. - assertEq( - kernel.validationInfo(validatorToIdentifier(newValidator)).hook, - address(0), + assertFalse( + kernel.validationInfo(validatorToIdentifier(newValidator)).installed, "H-01: module installed despite failed root signature" ); } diff --git a/test/KernelValidatorTest.sol b/test/KernelValidatorTest.sol index dc915e94..95efd7db 100644 --- a/test/KernelValidatorTest.sol +++ b/test/KernelValidatorTest.sol @@ -6,19 +6,37 @@ import {ValidationId, PermissionId} from "src/types/Types.sol"; import {MockPolicy} from "./mock/MockPolicy.sol"; import {MockSigner} from "./mock/MockSigner.sol"; import {MockCallee} from "./mock/MockCallee.sol"; +import {MockValidator} from "./mock/MockValidator.sol"; import {KernelTestBase} from "./KernelTestBase.sol"; import { InvalidRootValidation, InvalidNonce, - NotInstalled, UnauthorizedCallData, - InvalidPermissionUninstallOrder + InvalidPermissionUninstallOrder, + ScopedExecutionHookStillInstalled, + ScopedExecutionHookAlreadyInstalled, + InvalidScopedExecutionHookTarget, + InvalidDataLength, + InvalidPermissionId, + OccupiedValidationId, + NotImplemented } from "src/types/Error.sol"; -import {ERC1271_MAGICVALUE} from "src/types/Constants.sol"; -import {permissionToIdentifier, validatorToIdentifier} from "src/lib/Utils.sol"; +import {ERC1271_MAGICVALUE, SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE} from "src/types/Constants.sol"; +import { + permissionToIdentifier, + validatorToIdentifier, + validationScopedExecutionHookId, + getScopedExecutionHookScope, + getScopedExecutionHookValidationId +} from "src/lib/Utils.sol"; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; +import {IModule} from "src/interfaces/IERC7579Modules.sol"; abstract contract KernelValidatorTest is KernelTestBase { + function _validationScopedExecutionHookContext(ValidationId vId) internal pure returns (bytes memory) { + return abi.encodePacked(SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, ValidationId.unwrap(vId)); + } + function caller() external view returns (address) { return msg.sender; } @@ -117,6 +135,12 @@ abstract contract KernelValidatorTest is KernelTestBase { vm.stopPrank(); if (useHook && success) { assertTrue(hook.preHookData(address(kernel)).length != 0); + ValidationId vId = permissionToIdentifier(permissionId); + bytes32 expectedId = validationScopedExecutionHookId(vId); + assertEq(getScopedExecutionHookScope(expectedId), SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE); + assertEq(ValidationId.unwrap(getScopedExecutionHookValidationId(expectedId)), ValidationId.unwrap(vId)); + assertEq(hook.preCheckId(address(kernel)), expectedId); + assertEq(hook.postCheckId(address(kernel)), expectedId); } } @@ -235,6 +259,13 @@ abstract contract KernelValidatorTest is KernelTestBase { assertFalse(kernel.root() == permissionToIdentifier(permissionId)); kernel.setRoot(packages, false, hex""); assertTrue(kernel.root() == permissionToIdentifier(permissionId)); + kernel.installModule( + 11, + address(hook), + abi.encode( + bytes("hook install"), _validationScopedExecutionHookContext(permissionToIdentifier(permissionId)) + ) + ); packages[0] = Install({ moduleType: 5, module: address(policy), internalData: abi.encodePacked(hex"efefefef"), moduleData: hex"" @@ -249,9 +280,15 @@ abstract contract KernelValidatorTest is KernelTestBase { moduleType: 6, module: address(signer), internalData: abi.encodePacked(hex"efefefef"), moduleData: hex"" }); - bytes[] memory empty = new bytes[](3); + bytes[] memory uninstallData = new bytes[](4); + uninstallData[0] = hex"a1"; + uninstallData[1] = hex"a2"; + uninstallData[2] = hex"51"; + uninstallData[3] = hex"42"; + vm.expectCall(address(signer), abi.encodeWithSelector(IModule.onUninstall.selector, hex"51")); + vm.expectCall(address(hook), abi.encodeWithSelector(IModule.onUninstall.selector, hex"42")); - kernel.setRoot(packages, true, abi.encode(empty)); + kernel.setRoot(packages, true, abi.encode(uninstallData)); assertTrue(kernel.root() == permissionToIdentifier(PermissionId.wrap(bytes4(0xefefefef)))); } @@ -275,12 +312,10 @@ abstract contract KernelValidatorTest is KernelTestBase { ValidationId vId = ValidationId.wrap(bytes21(abi.encodePacked(bytes1(0x01), bytes20(address(newValidator))))); kernel.installModule(1, address(newValidator), abi.encode(hex"deadbeef", hex"")); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); bytes4 ret = kernel.isValidSignature( keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true) - ) + abi.encodePacked(bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true)) ); assertEq(ret, ERC1271_MAGICVALUE); assertTrue(kernel.isModuleInstalled(1, address(newValidator), hex"")); @@ -290,15 +325,13 @@ abstract contract KernelValidatorTest is KernelTestBase { assertTrue(kernel.supportsModule(1)); ValidationId vId = ValidationId.wrap(bytes21(abi.encodePacked(bytes1(0x01), bytes20(address(newValidator))))); kernel.installModule( - 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(address(0), kernel.execute.selector)) + 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(kernel.execute.selector)) ); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); bytes4 ret = kernel.isValidSignature( keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true) - ) + abi.encodePacked(bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true)) ); assertEq(ret, ERC1271_MAGICVALUE); assertTrue(kernel.isModuleInstalled(1, address(newValidator), hex"")); @@ -310,17 +343,15 @@ abstract contract KernelValidatorTest is KernelTestBase { assertTrue(kernel.supportsModule(1)); ValidationId vId = ValidationId.wrap(bytes21(abi.encodePacked(bytes1(0x01), bytes20(address(newValidator))))); kernel.installModule( - 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(address(0), kernel.execute.selector)) + 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(kernel.execute.selector)) ); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); // Non-empty install bumps nonce once via _grantAccess. assertEq(vInfo.nonce, 1); bytes4 ret = kernel.isValidSignature( keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true) - ) + abi.encodePacked(bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true)) ); assertEq(ret, ERC1271_MAGICVALUE); assertTrue(kernel.isModuleInstalled(1, address(newValidator), hex"")); @@ -333,7 +364,7 @@ abstract contract KernelValidatorTest is KernelTestBase { // any stale `allowed[vId][sel]` entries from the prior incarnation. assertEq(vInfo.nonce, 1); kernel.installModule( - 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(address(0), kernel.setNonce.selector)) + 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(kernel.setNonce.selector)) ); vInfo = kernel.validationInfo(vId); // re-install with non-empty internalData bumps nonce once -> 2. @@ -350,15 +381,13 @@ abstract contract KernelValidatorTest is KernelTestBase { assertTrue(kernel.supportsModule(1)); ValidationId vId = ValidationId.wrap(bytes21(abi.encodePacked(bytes1(0x01), bytes20(address(newValidator))))); kernel.installModule( - 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(address(0), kernel.setNonce.selector)) + 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(kernel.setNonce.selector)) ); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); bytes4 ret = kernel.isValidSignature( keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true) - ) + abi.encodePacked(bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true)) ); assertEq(ret, ERC1271_MAGICVALUE); assertTrue(kernel.isModuleInstalled(1, address(newValidator), hex"")); @@ -366,49 +395,14 @@ abstract contract KernelValidatorTest is KernelTestBase { _sendUserOpValidator(false, false); } - function test_install_validator_with_hook() external unitTest { - assertTrue(kernel.supportsModule(4)); - kernel.installModule(4, address(hook), abi.encode(hex"", "")); - assertTrue(kernel.supportsModule(1)); - ValidationId vId = ValidationId.wrap(bytes21(abi.encodePacked(bytes1(0x01), bytes20(address(newValidator))))); - kernel.installModule( - 1, - address(newValidator), - abi.encode(hex"deadbeef", abi.encodePacked(address(hook), kernel.execute.selector)) - ); - ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(hook)); - bytes4 ret = kernel.isValidSignature( - keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x01), newValidator, _validatorSignHash(keccak256("Hello world"), true) - ) - ); - assertEq(ret, ERC1271_MAGICVALUE); - assertTrue(kernel.isModuleInstalled(1, address(newValidator), hex"")); - - _sendUserOpValidator(true, true); - } - - function test_install_validator_with_hook_notinstalled() external unitTest { - assertTrue(kernel.supportsModule(4)); - assertTrue(kernel.supportsModule(1)); - vm.expectRevert(NotInstalled.selector); - kernel.installModule( - 1, - address(newValidator), - abi.encode(hex"deadbeef", abi.encodePacked(address(hook), kernel.execute.selector)) - ); - } - function test_uninstall_validator() external unitTest { ValidationId vId = ValidationId.wrap(bytes21(abi.encodePacked(bytes1(0x01), bytes20(address(newValidator))))); kernel.installModule(1, address(newValidator), abi.encode(hex"deadbeef", hex"")); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); kernel.uninstallModule(1, address(newValidator), abi.encode(hex"deadbeef", hex"")); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); } /// forge-config: default.isolate = true @@ -417,7 +411,7 @@ abstract contract KernelValidatorTest is KernelTestBase { assertTrue(kernel.supportsModule(6)); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); assertFalse(kernel.isModuleInstalled(5, address(policy), abi.encodePacked(permissionId))); assertFalse(kernel.isModuleInstalled(6, address(signer), abi.encodePacked(permissionId))); Install[] memory pkgs = new Install[](2); @@ -436,9 +430,7 @@ abstract contract KernelValidatorTest is KernelTestBase { kernel.installModule(pkgs); bytes4 ret = kernel.isValidSignature( keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x02), permissionId, _permissionSignHash(keccak256("Hello world"), true) - ) + abi.encodePacked(bytes1(0x02), permissionId, _permissionSignHash(keccak256("Hello world"), true)) ); assertEq(ret, ERC1271_MAGICVALUE); assertTrue(kernel.isModuleInstalled(5, address(policy), abi.encodePacked(permissionId))); @@ -447,16 +439,16 @@ abstract contract KernelValidatorTest is KernelTestBase { /// forge-config: default.isolate = true function test_install_permission_with_hook() external unitTest { - assertTrue(kernel.supportsModule(4)); - kernel.installModule(4, address(hook), abi.encode(hex"", "")); + assertFalse(kernel.supportsModule(4)); + assertTrue(kernel.supportsModule(11)); assertTrue(kernel.supportsModule(5)); assertTrue(kernel.supportsModule(6)); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertFalse(vInfo.installed); assertFalse(kernel.isModuleInstalled(5, address(policy), abi.encodePacked(permissionId))); assertFalse(kernel.isModuleInstalled(6, address(signer), abi.encodePacked(permissionId))); - Install[] memory pkgs = new Install[](2); + Install[] memory pkgs = new Install[](3); pkgs[0] = Install({ moduleType: 5, module: address(policy), @@ -467,14 +459,21 @@ abstract contract KernelValidatorTest is KernelTestBase { moduleType: 6, module: address(signer), moduleData: hex"deadbeef", - internalData: abi.encodePacked(permissionId, hook, kernel.execute.selector) + internalData: abi.encodePacked(permissionId, kernel.execute.selector) + }); + pkgs[2] = Install({ + moduleType: 11, + module: address(hook), + moduleData: hex"deadbeef", + internalData: _validationScopedExecutionHookContext(vId) }); kernel.installModule(pkgs); + vInfo = kernel.validationInfo(vId); + assertEq(address(vInfo.scopedExecutionHook), address(hook)); + assertTrue(kernel.isModuleInstalled(11, address(hook), _validationScopedExecutionHookContext(vId))); bytes4 ret = kernel.isValidSignature( keccak256("Hello world"), - abi.encodePacked( - bytes1(0x00), bytes1(0x02), permissionId, _permissionSignHash(keccak256("Hello world"), true) - ) + abi.encodePacked(bytes1(0x02), permissionId, _permissionSignHash(keccak256("Hello world"), true)) ); assertEq(ret, ERC1271_MAGICVALUE); assertTrue(kernel.isModuleInstalled(5, address(policy), abi.encodePacked(permissionId))); @@ -484,42 +483,17 @@ abstract contract KernelValidatorTest is KernelTestBase { } /// forge-config: default.isolate = true - function test_install_permission_with_hook_notinstalled() external unitTest { - assertTrue(kernel.supportsModule(4)); - assertTrue(kernel.supportsModule(5)); - assertTrue(kernel.supportsModule(6)); - ValidationId vId = permissionToIdentifier(permissionId); - ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); - assertFalse(kernel.isModuleInstalled(5, address(policy), abi.encodePacked(permissionId))); - assertFalse(kernel.isModuleInstalled(6, address(signer), abi.encodePacked(permissionId))); - vm.expectRevert(NotInstalled.selector); - Install[] memory pkgs = new Install[](2); - pkgs[0] = Install({ - moduleType: 5, - module: address(policy), - moduleData: hex"deadbeef", - internalData: abi.encodePacked(permissionId) - }); - pkgs[1] = Install({ - moduleType: 6, - module: address(signer), - moduleData: hex"deadbeef", - internalData: abi.encodePacked(permissionId, hook, kernel.execute.selector) - }); - kernel.installModule(pkgs); - } function test_install_policy() external unitTest { assertTrue(kernel.supportsModule(5)); MockPolicy mock = new MockPolicy(); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); kernel.installModule(5, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); // it should return address(0) as signer is not installed properly - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); assertTrue(kernel.isModuleInstalled(5, address(mock), abi.encodePacked(permissionId))); } @@ -527,13 +501,13 @@ abstract contract KernelValidatorTest is KernelTestBase { MockPolicy mock = new MockPolicy(); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); kernel.installModule(5, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); kernel.uninstallModule(5, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); } function test_install_signer() external unitTest { @@ -541,10 +515,10 @@ abstract contract KernelValidatorTest is KernelTestBase { MockSigner mock = new MockSigner(); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); kernel.installModule(6, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); assertTrue(kernel.isModuleInstalled(6, address(mock), abi.encodePacked(permissionId))); } @@ -560,18 +534,18 @@ abstract contract KernelValidatorTest is KernelTestBase { moduleType: 6, module: address(signer), moduleData: hex"deadbeef", - internalData: abi.encodePacked(permissionId, address(0), kernel.execute.selector) + internalData: abi.encodePacked(permissionId, kernel.execute.selector) }); kernel.installModule(pkgs); MockPolicy mock = new MockPolicy(); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); assertEq(vInfo.policies.length, 1); assertEq(vInfo.policies[0], address(policy)); kernel.installModule(5, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); assertEq(vInfo.policies.length, 2); assertEq(vInfo.policies[0], address(policy)); assertEq(vInfo.policies[1], address(mock)); @@ -582,17 +556,125 @@ abstract contract KernelValidatorTest is KernelTestBase { MockSigner mock = new MockSigner(); ValidationId vId = permissionToIdentifier(permissionId); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); kernel.installModule(6, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(1)); + assertTrue(vInfo.installed); assertTrue(vInfo.signer == address(mock)); kernel.uninstallModule(6, address(mock), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook == address(0)); + assertTrue(!vInfo.installed); assertTrue(vInfo.signer == address(0)); } + function test_execution_hook_requires_completed_permission() external unitTest { + vm.expectRevert(InvalidScopedExecutionHookTarget.selector); + kernel.installModule( + 11, + address(hook), + abi.encode(hex"deadbeef", _validationScopedExecutionHookContext(permissionToIdentifier(permissionId))) + ); + } + + function test_execution_hook_permission_lifecycle_and_uninstall_order() external unitTest { + ValidationId vId = permissionToIdentifier(permissionId); + bytes memory hookContext = _validationScopedExecutionHookContext(vId); + kernel.installModule(6, address(signer), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); + kernel.installModule(11, address(hook), abi.encode(hex"deadbeef", hookContext)); + + ValidationInfo memory info = kernel.validationInfo(vId); + assertTrue(info.installed); + assertEq(address(info.scopedExecutionHook), address(hook)); + assertTrue(kernel.isModuleInstalled(11, address(hook), hookContext)); + + vm.expectRevert(ScopedExecutionHookAlreadyInstalled.selector); + kernel.installModule(11, address(hook), abi.encode(hex"deadbeef", hookContext)); + + vm.expectRevert(ScopedExecutionHookStillInstalled.selector); + kernel.uninstallModule(6, address(signer), abi.encode(hex"", abi.encodePacked(permissionId))); + + kernel.uninstallModule(11, address(hook), abi.encode(hex"", hookContext)); + assertFalse(kernel.isModuleInstalled(11, address(hook), hookContext)); + assertEq(address(kernel.validationInfo(vId).scopedExecutionHook), address(0)); + + kernel.uninstallModule(6, address(signer), abi.encode(hex"", abi.encodePacked(permissionId))); + assertFalse(kernel.validationInfo(permissionToIdentifier(permissionId)).installed); + } + + function test_validator_scoped_execution_hook() external unitTest { + ValidationId vId = validatorToIdentifier(newValidator); + kernel.installModule( + 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(kernel.execute.selector)) + ); + bytes memory hookContext = _validationScopedExecutionHookContext(vId); + kernel.installModule(11, address(hook), abi.encode(hex"deadbeef", hookContext)); + + assertTrue(kernel.isModuleInstalled(11, address(hook), hookContext)); + assertEq(address(kernel.validationInfo(vId).scopedExecutionHook), address(hook)); + + _sendUserOpValidator(true, true); + bytes32 expectedId = validationScopedExecutionHookId(vId); + assertEq(getScopedExecutionHookScope(expectedId), SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE); + assertEq(ValidationId.unwrap(getScopedExecutionHookValidationId(expectedId)), ValidationId.unwrap(vId)); + assertEq(hook.preCheckId(address(kernel)), expectedId); + assertEq(hook.postCheckId(address(kernel)), expectedId); + + vm.expectRevert(ScopedExecutionHookStillInstalled.selector); + kernel.uninstallModule(1, address(newValidator), abi.encode(hex"", hex"")); + + kernel.uninstallModule(11, address(hook), abi.encode(hex"", hookContext)); + kernel.uninstallModule(1, address(newValidator), abi.encode(hex"", hex"")); + assertFalse(kernel.validationInfo(vId).installed); + } + + function test_set_root_removes_validator_execution_hook_before_validator() external unitTest { + ValidationId oldRoot = validatorToIdentifier(newValidator); + kernel.installModule( + 1, address(newValidator), abi.encode(hex"deadbeef", abi.encodePacked(kernel.execute.selector)) + ); + kernel.installModule( + 11, address(hook), abi.encode(hex"deadbeef", _validationScopedExecutionHookContext(oldRoot)) + ); + kernel.setRoot(oldRoot); + + MockValidator replacement = new MockValidator(); + Install[] memory packages = new Install[](1); + packages[0] = Install({ + moduleType: 1, + module: address(replacement), + moduleData: hex"deadbeef", + internalData: abi.encodePacked(kernel.execute.selector) + }); + bytes[] memory uninstallData = new bytes[](2); + uninstallData[0] = hex"aaaa"; + uninstallData[1] = hex"bbbb"; + kernel.setRoot(packages, true, abi.encode(uninstallData)); + + assertEq(ValidationId.unwrap(kernel.root()), ValidationId.unwrap(validatorToIdentifier(replacement))); + assertFalse(kernel.validationInfo(oldRoot).installed); + assertEq(address(kernel.validationInfo(oldRoot).scopedExecutionHook), address(0)); + } + + function test_execution_hook_rejects_invalid_scope_and_length() external unitTest { + vm.expectRevert(InvalidScopedExecutionHookTarget.selector); + kernel.installModule(11, address(hook), abi.encode(hex"", abi.encodePacked(bytes1(0xff), bytes4(0)))); + + vm.expectRevert(InvalidDataLength.selector); + kernel.installModule( + 11, + address(hook), + abi.encode( + hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, PermissionId.unwrap(permissionId)) + ) + ); + } + + function test_generic_hook_type_is_unsupported() external unitTest { + assertFalse(kernel.supportsModule(4)); + vm.expectRevert(NotImplemented.selector); + kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); + } + function test_uninstall_policy_not_last_reverts() external unitTest { MockPolicy policy2 = new MockPolicy(); kernel.installModule(5, address(policy), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); diff --git a/test/btt/Kernel.branchCoverage.t.sol b/test/btt/Kernel.branchCoverage.t.sol index f0627377..da5cbbd3 100644 --- a/test/btt/Kernel.branchCoverage.t.sol +++ b/test/btt/Kernel.branchCoverage.t.sol @@ -31,7 +31,6 @@ import { InstallSignatureVerificationFailed, InvalidPermissionInstall, PermissionInstallNotFinished, - NotInstalled, OccupiedValidationId } from "src/types/Error.sol"; @@ -82,9 +81,8 @@ abstract contract Kernel_branchCoverage is BTTModifiers { assertEq(validationData, 0, "Enable mode with replayable enable sig should succeed"); // Verify the validator was installed - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).installed, "Validator should be installed via replayable enable" ); } @@ -161,43 +159,6 @@ abstract contract Kernel_branchCoverage is BTTModifiers { /// @notice Tests _processUserOp when selector is allowed but hook!=address(1) and callData /// uses executeUserOp wrapper — the hook storage path - /// @dev Covers _setValidationHook + _allowedSelector branch with hook set - function test_processUserOp_validatorWithHookAndAllowedSelector() external { - vm.stopPrank(); - vm.startPrank(address(ep)); - - // Install hook - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - - // Install validator with hook and allowed selector - kernel.installModule( - 1, address(newValidator), abi.encode(hex"", abi.encodePacked(address(hook), Kernel.execute.selector)) - ); - - PackedUserOperation memory op = PackedUserOperation({ - sender: address(kernel), - nonce: encodeNonce(false, false, false, bytes1(0x01), bytes20(address(newValidator))), - initCode: hex"", - callData: abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(1000000), uint128(1000000))), - preVerificationGas: 0, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - op.signature = _validatorSignUserOp(op, true, false); - bytes32 userOpHash = ep.getUserOpHash(op); - - uint256 validationData = kernel.validateUserOp(op, userOpHash, 0); - assertEq(validationData, 0, "Validator with hook and allowed selector should succeed"); - } /// @notice Tests permission validation where selector is directly allowed and hook=address(1) /// @dev Covers the no-op branch for permission type with allowed selector @@ -408,7 +369,7 @@ abstract contract Kernel_branchCoverage is BTTModifiers { kernel.setRoot(packages, false, hex""); ValidationId expectedRoot = permissionToIdentifier(testPermId); - assertEq(kernel.validationInfo(expectedRoot).hook, address(1), "Root should be set from policy's permissionId"); + assertTrue(kernel.validationInfo(expectedRoot).installed, "Root should be set from policy's permissionId"); } /// @notice Tests _setRoot(Install) with MODULE_TYPE_SIGNER as first package @@ -429,7 +390,7 @@ abstract contract Kernel_branchCoverage is BTTModifiers { kernel.setRoot(packages, false, hex""); ValidationId expectedRoot = permissionToIdentifier(testPermId); - assertEq(kernel.validationInfo(expectedRoot).hook, address(1), "Root should be set from signer's permissionId"); + assertTrue(kernel.validationInfo(expectedRoot).installed, "Root should be set from signer's permissionId"); } /// @notice Tests _setRoot(ValidationId) with an invalid type (not validator, not permission) @@ -707,51 +668,15 @@ abstract contract Kernel_branchCoverage is BTTModifiers { kernel.installModule(1, address(testValidator), abi.encode(hex"", hex"")); // Validator should be installed but with no allowed selectors - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(testValidator)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(testValidator)))).installed, "Validator with empty internalData should have hook=address(1)" ); } /// @notice Tests _initializeValidation when hook is address(0) in internalData - /// @dev Covers hook == HOOK_MODULE_NOT_INSTALLED => HOOK_MODULE_INSTALLED_NO_HOOK mapping - function test_initializeValidation_hookAddress0MapsToAddress1() external { - vm.stopPrank(); - vm.startPrank(address(ep)); - - MockValidator testValidator = new MockValidator(); - // internalData starts with address(0) as hook - kernel.installModule( - 1, address(testValidator), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) - ); - - // Hook should be set to address(1) (HOOK_MODULE_INSTALLED_NO_HOOK) - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(testValidator)))).hook, - address(1), - "Hook address(0) should map to address(1)" - ); - } /// @notice Tests _initializeValidation when hook is address(1) in internalData - /// @dev Covers hook == HOOK_MODULE_INSTALLED_NO_HOOK case - function test_initializeValidation_hookAddress1Stays() external { - vm.stopPrank(); - vm.startPrank(address(ep)); - - MockValidator testValidator = new MockValidator(); - // internalData starts with address(1) - kernel.installModule( - 1, address(testValidator), abi.encode(hex"", abi.encodePacked(address(1), Kernel.execute.selector)) - ); - - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(testValidator)))).hook, - address(1), - "Hook address(1) should stay address(1)" - ); - } /// @notice Tests that OccupiedValidationId is thrown when trying to install a validator twice /// @dev Covers the require in _initializeValidation @@ -917,7 +842,7 @@ abstract contract Kernel_branchCoverage is BTTModifiers { moduleType: 1, module: address(newValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); bytes memory enableSignature = enableSig(5, true, false, packages, _rootSignHash); @@ -987,21 +912,18 @@ abstract contract Kernel_branchCoverage is BTTModifiers { assertTrue(kernel.supportsModule(1), "Should support type 1 (validator)"); assertTrue(kernel.supportsModule(2), "Should support type 2 (executor)"); assertTrue(kernel.supportsModule(3), "Should support type 3 (fallback)"); - assertTrue(kernel.supportsModule(4), "Should support type 4 (hook)"); + assertFalse(kernel.supportsModule(4), "Should not support generic hook type 4"); + assertTrue(kernel.supportsModule(11), "Should support scoped execution hook type 11"); assertTrue(kernel.supportsModule(5), "Should support type 5 (policy)"); assertTrue(kernel.supportsModule(6), "Should support type 6 (signer)"); assertFalse(kernel.supportsModule(0), "Should NOT support type 0"); assertFalse(kernel.supportsModule(7), "Should NOT support type 7"); } - /// @notice Tests isModuleInstalled for MODULE_TYPE_HOOK + /// @notice Tests isModuleInstalled for unsupported generic hook type 4 function test_isModuleInstalled_hookType() external { - vm.stopPrank(); - vm.startPrank(address(ep)); - - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - - assertTrue(kernel.isModuleInstalled(4, address(hook), ""), "Hook should be installed"); + vm.expectRevert(); + kernel.isModuleInstalled(4, address(hook), ""); } /// @notice Tests isModuleInstalled for unsupported module type @@ -1066,9 +988,7 @@ abstract contract Kernel_branchCoverage is BTTModifiers { vm.startPrank(address(ep)); bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x00)))); assertTrue( kernel.isModuleInstalled(3, address(mockFallback), abi.encodePacked(testSelector)), @@ -1080,7 +1000,7 @@ abstract contract Kernel_branchCoverage is BTTModifiers { function test_validationInfo_returnsData() external view { // Root validator should have hook=address(1) and no policies ValidationId rootVid = validatorToIdentifier(IValidator(address(rootValidator))); - assertEq(kernel.validationInfo(rootVid).hook, address(1), "Root validator should have hook=address(1)"); + assertTrue(kernel.validationInfo(rootVid).installed, "Root validator should have hook=address(1)"); } /// @notice Tests root() view function diff --git a/test/btt/Kernel.executeFromExecutor.t.sol b/test/btt/Kernel.executeFromExecutor.t.sol index af1881a1..29b2ec83 100644 --- a/test/btt/Kernel.executeFromExecutor.t.sol +++ b/test/btt/Kernel.executeFromExecutor.t.sol @@ -5,7 +5,6 @@ import {BTTModifiers} from "./BTTModifiers.sol"; import {Unauthorized, InvalidExecType, InvalidCallType} from "src/types/Error.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; import {MockCallee} from "../mock/MockCallee.sol"; -import {MockHook} from "../mock/MockHook.sol"; import {MockAction} from "../mock/MockAction.sol"; import {LibERC7579} from "solady/accounts/LibERC7579.sol"; import {Call} from "src/types/Structs.sol"; @@ -17,10 +16,6 @@ abstract contract Kernel_executeFromExecutor is BTTModifiers { bytes1 internal _executorCallType; bytes1 internal _executorExecType; - // State variables for hook tests - set by modifiers, used by tests - MockHook internal _hookContract; - MockExecutor internal _executorWithHook; - function _setupExecutorTests() internal { testExecutor = new MockExecutor(); vm.stopPrank(); @@ -52,117 +47,6 @@ abstract contract Kernel_executeFromExecutor is BTTModifiers { ); } - // Hook setup helper - creates and installs executor with hook - function _setupExecutorWithHook() internal { - _hookContract = new MockHook(); - _executorWithHook = new MockExecutor(); - vm.startPrank(address(ep)); - // Install the hook module first so it's recognized as a valid hook - kernel.installModule(4, address(_hookContract), abi.encode(hex"", hex"")); - kernel.installModule(2, address(_executorWithHook), abi.encode(hex"", abi.encodePacked(address(_hookContract)))); - vm.stopPrank(); - } - - modifier givenTheExecutorHasAHookConfigured() { - // Set up executor with hook - tests use _hookContract and _executorWithHook - _setupExecutorWithHook(); - _; - } - - function test_GivenTheExecutorHasAHookConfigured() - external - whenTheCallerIsAnInstalledExecutor - givenTheExecutorHasAHookConfigured - { - // it should call preHook on the executor hook before execution - _executorWithHook.executeViaKernel( - kernel, LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_DEFAULT, address(callee), 0, MockCallee.foo.selector - ); - - assertTrue(_hookContract.preHookCalled(), "preHook should be called"); - } - - function test_GivenPreHookReverts() external whenTheCallerIsAnInstalledExecutor givenTheExecutorHasAHookConfigured { - // it should propagate the revert - // Configure the hook to revert on preHook - _hookContract.setRevertOnPreHook(true); - - vm.expectRevert(MockHook.PreHookReverted.selector); - _executorWithHook.executeViaKernel( - kernel, LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_DEFAULT, address(callee), 0, MockCallee.foo.selector - ); - } - - modifier givenPreHookSucceeds() { - // Ensure hook does not revert on preHook (default behavior, but explicit) - _hookContract.setRevertOnPreHook(false); - _; - } - - function test_GivenPreHookSucceeds() - external - whenTheCallerIsAnInstalledExecutor - givenTheExecutorHasAHookConfigured - givenPreHookSucceeds - { - // it should execute the requested operations - // it should call postHook on the executor hook after execution - _executorWithHook.executeViaKernel( - kernel, LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_DEFAULT, address(callee), 0, MockCallee.foo.selector - ); - - assertTrue(_hookContract.postHookCalled(), "postHook should be called"); - assertEq(callee.bar(), 1, "Callee should be called"); - } - - function test_GivenPostHookReverts() - external - whenTheCallerIsAnInstalledExecutor - givenTheExecutorHasAHookConfigured - givenPreHookSucceeds - { - // it should propagate the revert - // Configure the hook to revert on postHook - _hookContract.setRevertOnPostHook(true); - - vm.expectRevert(MockHook.PostHookReverted.selector); - _executorWithHook.executeViaKernel( - kernel, LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_DEFAULT, address(callee), 0, MockCallee.foo.selector - ); - } - - function test_GivenPostHookSucceeds() - external - whenTheCallerIsAnInstalledExecutor - givenTheExecutorHasAHookConfigured - givenPreHookSucceeds - { - // it should return the execution results - bytes[] memory results = _executorWithHook.executeViaKernel( - kernel, LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_DEFAULT, address(callee), 0, MockCallee.foo.selector - ); - - assertEq(results.length, 1, "Should return one result"); - } - - function test_GivenTheExecutorHasHookSetToAddress1() external whenTheCallerIsAnInstalledExecutor { - // it should skip preHook and postHook calls - // it should execute the requested operations directly - MockExecutor executorNoHook = new MockExecutor(); - - vm.startPrank(address(ep)); - // address(1) means skip hooks - kernel.installModule(2, address(executorNoHook), abi.encode(hex"", abi.encodePacked(address(1)))); - vm.stopPrank(); - - uint256 barBefore = callee.bar(); - executorNoHook.executeViaKernel( - kernel, LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_DEFAULT, address(callee), 0, MockCallee.foo.selector - ); - - assertEq(callee.bar(), barBefore + 1, "Callee should be called directly"); - } - modifier givenTheExecutionModeCallTypeIsSINGLE() { _executorCallType = LibERC7579.CALLTYPE_SINGLE; _; diff --git a/test/btt/Kernel.executeUserOp.t.sol b/test/btt/Kernel.executeUserOp.t.sol index 6d4b2403..383a77b4 100644 --- a/test/btt/Kernel.executeUserOp.t.sol +++ b/test/btt/Kernel.executeUserOp.t.sol @@ -10,7 +10,7 @@ import {Call} from "src/types/Structs.sol"; import {MockCallee} from "../mock/MockCallee.sol"; abstract contract Kernel_executeUserOp is BTTModifiers { - bool internal _validationHookSet; + bool internal _validationScopedExecutionHookSet; bool internal _transientHookSet; bool internal _innerExecutionSucceeds; @@ -33,7 +33,7 @@ abstract contract Kernel_executeUserOp is BTTModifiers { } modifier givenTheValidationHookIsSet() { - _validationHookSet = true; + _validationScopedExecutionHookSet = true; _innerExecutionSucceeds = true; _; } @@ -86,7 +86,7 @@ abstract contract Kernel_executeUserOp is BTTModifiers { } modifier givenNoValidationHookIsSet() { - _validationHookSet = false; + _validationScopedExecutionHookSet = false; _innerExecutionSucceeds = true; _; } @@ -136,7 +136,7 @@ abstract contract Kernel_executeUserOp is BTTModifiers { } modifier givenAValidationHookIsSetInTransientStorage() { - _validationHookSet = true; + _validationScopedExecutionHookSet = true; _transientHookSet = true; _innerExecutionSucceeds = true; _; diff --git a/test/btt/Kernel.fallback.t.sol b/test/btt/Kernel.fallback.t.sol index 66288963..236af917 100644 --- a/test/btt/Kernel.fallback.t.sol +++ b/test/btt/Kernel.fallback.t.sol @@ -2,22 +2,15 @@ pragma solidity ^0.8.0; import {BTTModifiers} from "./BTTModifiers.sol"; -import {Kernel} from "src/Kernel.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import {InvalidSelector, InvalidCallType} from "src/types/Error.sol"; +import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL} from "src/types/Constants.sol"; import {MockFallback} from "../mock/MockFallback.sol"; -import {MockHook} from "../mock/MockHook.sol"; +/// @notice Fallback routing tests after removal of generic fallback hooks. abstract contract Kernel_fallback is BTTModifiers { - bool internal _customSelector; - address internal _hookAddress; - bytes1 internal _callType; - bool internal _preHookSucceeds; - bool internal _targetCallSucceeds; - function test_WhenTheSelectorIsOnERC721Received() external { - // it should return the selector as magic value bytes4 selector = IERC721Receiver.onERC721Received.selector; (bool success, bytes memory result) = address(kernel).call(abi.encodeWithSelector(selector, address(this), address(this), 1, "")); @@ -26,7 +19,6 @@ abstract contract Kernel_fallback is BTTModifiers { } function test_WhenTheSelectorIsOnERC1155Received() external { - // it should return the selector as magic value bytes4 selector = IERC1155Receiver.onERC1155Received.selector; (bool success, bytes memory result) = address(kernel).call(abi.encodeWithSelector(selector, address(this), address(this), 1, 1, "")); @@ -35,7 +27,6 @@ abstract contract Kernel_fallback is BTTModifiers { } function test_WhenTheSelectorIsOnERC1155BatchReceived() external { - // it should return the selector as magic value bytes4 selector = IERC1155Receiver.onERC1155BatchReceived.selector; uint256[] memory ids = new uint256[](1); uint256[] memory amounts = new uint256[](1); @@ -45,982 +36,44 @@ abstract contract Kernel_fallback is BTTModifiers { assertEq(abi.decode(result, (bytes4)), selector, "Should return magic value"); } - modifier whenTheSelectorIsACustomRegisteredSelector() { - _customSelector = true; - _; - } - - function test_GivenTheSelectorIsNotRegistered() external whenTheSelectorIsACustomRegisteredSelector { - // it should revert with InvalidSelector error + function test_GivenTheSelectorIsNotRegistered() external { bytes4 unregisteredSelector = bytes4(keccak256("unregistered()")); vm.expectRevert(InvalidSelector.selector); (bool success,) = address(kernel).call(abi.encodeWithSelector(unregisteredSelector)); - // expectRevert handles the revert check - } - - /*////////////////////////////////////////////////////////////// - HOOK = ADDRESS(0) BRANCH - //////////////////////////////////////////////////////////////*/ - - modifier givenTheSelectorIsRegisteredButHookIsAddress0() { - _hookAddress = address(0); - _; - } - - function test_WhenTheCallerIsNotTheEntryPoint() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - { - // it should revert with InvalidSelector error - vm.stopPrank(); - vm.startPrank(address(ep)); - - // Register fallback with hook=address(0) - bytes4 testSelector = MockFallback.getData.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - vm.stopPrank(); - - // Call from non-EntryPoint - address randomCaller = makeAddr("randomCaller"); - vm.startPrank(randomCaller); - vm.expectRevert(InvalidSelector.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - modifier whenTheCallerIsTheEntryPoint() { - vm.stopPrank(); - vm.startPrank(address(ep)); - _; - } - - modifier givenCallTypeIsCALL() { - _callType = bytes1(0x00); - _; - } - - function test_GivenCallTypeIsCALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - givenCallTypeIsCALL - { - // it should call the target with msgdata plus msgsender - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Call should succeed"); - } - - function test_WhenTheTargetCallSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - givenCallTypeIsCALL - { - // it should return the call result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success, bytes memory result) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Call should succeed and return result"); - } - - function test_WhenTheTargetCallReverts() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - givenCallTypeIsCALL - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - modifier givenCallTypeIsDELEGATECALL() { - _callType = bytes1(0xff); - _; - } - - function test_GivenCallTypeIsDELEGATECALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - givenCallTypeIsDELEGATECALL - { - // it should delegatecall the target with msgdata - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Delegatecall should succeed"); - } - - function test_WhenTheDelegatecallSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - givenCallTypeIsDELEGATECALL - { - // it should return the delegatecall result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Delegatecall should return result"); - } - - function test_WhenTheDelegatecallReverts() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - givenCallTypeIsDELEGATECALL - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsUnsupported() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredButHookIsAddress0 - whenTheCallerIsTheEntryPoint - { - // it should revert with InvalidCallType error - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x02), address(0))) - ); - - vm.expectRevert(InvalidCallType.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - /*////////////////////////////////////////////////////////////// - HOOK = ADDRESS(1) BRANCH - //////////////////////////////////////////////////////////////*/ - - modifier givenTheSelectorIsRegisteredWithHookSetToAddress1() { - _hookAddress = address(1); - _; - } - - function test_GivenTheSelectorIsRegisteredWithHookSetToAddress1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - { - // it should skip preHook and postHook - // it should allow any caller - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - vm.stopPrank(); - - // Call from any address should work - address anyCaller = makeAddr("anyCaller"); - vm.startPrank(anyCaller); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Any caller should succeed with hook=address(1)"); - } - - function test_GivenCallTypeIsCALL_GivenCallTypeIsCALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsCALL - { - // it should call the target with msgdata plus msgsender - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should call target with CALL type"); - } - - function test_WhenTheTargetCallSucceeds_GivenCallTypeIsCALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsCALL - { - // it should return the call result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return call result on success"); - } - - function test_WhenTheTargetCallReverts_GivenCallTypeIsCALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsCALL - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsDELEGATECALL_GivenCallTypeIsDELEGATECALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsDELEGATECALL - { - // it should delegatecall the target with msgdata - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should delegatecall target"); - } - - function test_WhenTheDelegatecallSucceeds_GivenCallTypeIsDELEGATECALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsDELEGATECALL - { - // it should return the delegatecall result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return delegatecall result"); - } - - function test_WhenTheDelegatecallReverts_GivenCallTypeIsDELEGATECALL() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsDELEGATECALL - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsUnsupported_GivenTheSelectorIsRegisteredWithHookSetToAddress1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - { - // it should revert with InvalidCallType error - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - // Using 0x02 as unsupported call type - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x02), address(1))) - ); - - vm.expectRevert(InvalidCallType.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsCALL_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsCALL - { - // it should call the target with msgdata plus msgsender - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Call should succeed"); - } - - function test_WhenTheTargetCallSucceeds_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsCALL - { - // it should return the call result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return call result"); - } - - function test_WhenTheTargetCallReverts_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsCALL - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsDELEGATECALL_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsDELEGATECALL - { - // it should delegatecall the target with msgdata - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Delegatecall should succeed"); - } - - function test_WhenTheDelegatecallSucceeds_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsDELEGATECALL - { - // it should return the delegatecall result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return delegatecall result"); - } - - function test_WhenTheDelegatecallReverts_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - givenCallTypeIsDELEGATECALL - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsUnsupported_Hook1() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithHookSetToAddress1 - { - // it should revert with InvalidCallType error - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x02), address(1))) - ); - - vm.expectRevert(InvalidCallType.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - /*////////////////////////////////////////////////////////////// - HOOK = CONTRACT BRANCH - //////////////////////////////////////////////////////////////*/ - - modifier givenTheSelectorIsRegisteredWithAHookContract() { - _hookAddress = address(hook); - _; - } - - function test_GivenTheSelectorIsRegisteredWithAHookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - { - // it should call preHook on the hook with msgdata - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - _installFallback(testSelector); - _preHookSucceeds = true; - _configureHook(); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Call should succeed"); - assertTrue(hook.preHookCalled(), "preHook should be called"); - } - - function test_GivenPreHookReverts() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - _installFallback(testSelector); - _preHookSucceeds = false; - _configureHook(); - vm.expectRevert(MockHook.PreHookReverted.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - modifier givenPreHookSucceeds() { - _preHookSucceeds = true; - _; - } - - function test_GivenCallTypeIsCALL_GivenCallTypeIsCALL_GivenPreHookSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - { - // it should call the target with msgdata plus msgsender - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - _installFallback(testSelector); - _configureHook(); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should call target with msgdata plus msgsender"); - } - - modifier whenTheTargetCallSucceeds() { - _targetCallSucceeds = true; - _; - } - - function test_WhenTheTargetCallSucceeds_WhenTheTargetCallSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - whenTheTargetCallSucceeds - { - // it should call postHook with the context from preHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = _targetSelector(); - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.resetState(); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Call should succeed"); - assertTrue(hook.postHookCalled(), "postHook should be called with context"); - } - - function test_WhenTheTargetCallSucceeds_HookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - whenTheTargetCallSucceeds - { - // it should call postHook with the context from preHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.resetState(); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Call should succeed"); - assertTrue(hook.postHookCalled(), "postHook should be called"); - } - - function test_GivenPostHookReverts() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - whenTheTargetCallSucceeds - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.setRevertOnPostHook(true); - vm.expectRevert(MockHook.PostHookReverted.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenPostHookSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - whenTheTargetCallSucceeds - { - // it should return the call result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return call result"); - } - - function test_WhenTheTargetCallReverts_GivenCallTypeIsCALL_GivenPreHookSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - { - // it should propagate the revert and skip postHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.resetState(); - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsDELEGATECALL_GivenCallTypeIsDELEGATECALL_GivenPreHookSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - { - // it should delegatecall the target with msgdata - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should delegatecall target with msgdata"); - } - - function test_WhenTheTargetCallReverts_HookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsCALL - { - // it should propagate the revert and skip postHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.resetState(); - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsDELEGATECALL_HookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - { - // it should delegatecall the target with msgdata - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Delegatecall should succeed"); - } - - modifier whenTheDelegatecallSucceeds() { - _targetCallSucceeds = true; - _; - } - - function test_WhenTheDelegatecallSucceeds_WhenTheDelegatecallSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - whenTheDelegatecallSucceeds - { - // it should call postHook with the context from preHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = _targetSelector(); - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.resetState(); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Delegatecall should succeed"); - assertTrue(hook.postHookCalled(), "postHook should be called with context"); - } - - function test_GivenPostHookReverts_WhenTheDelegatecallSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - whenTheDelegatecallSucceeds - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.setRevertOnPostHook(true); - vm.expectRevert(MockHook.PostHookReverted.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); + success; } - function test_GivenPostHookSucceeds_WhenTheDelegatecallSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - whenTheDelegatecallSucceeds - { - // it should return the delegatecall result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; + function test_WhenSingleCallFallbackIsInstalled() external { + vm.prank(address(ep)); kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) + 3, + address(mockFallback), + abi.encode(hex"", abi.encodePacked(MockFallback.testFunction.selector, CALLTYPE_SINGLE)) ); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return delegatecall result"); + assertEq(MockFallback(address(kernel)).testFunction(), 42); } - function test_WhenTheDelegatecallReverts_GivenCallTypeIsDELEGATECALL_GivenPreHookSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - { - // it should propagate the revert and skip postHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; + function test_WhenDelegatecallFallbackIsInstalled() external { + vm.prank(address(ep)); kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) + 3, + address(mockFallback), + abi.encode(hex"", abi.encodePacked(MockFallback.fallbackFunction.selector, CALLTYPE_DELEGATECALL)) ); - hook.resetState(); - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); + assertEq(MockFallback(address(kernel)).fallbackFunction(5), 25); } - function test_GivenCallTypeIsUnsupported_GivenPreHookSucceeds() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - { - // it should revert with InvalidCallType error - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; + function test_WhenFallbackCallTypeIsInvalid() external { + vm.prank(address(ep)); kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x02), address(hook))) + 3, + address(mockFallback), + abi.encode(hex"", abi.encodePacked(MockFallback.testFunction.selector, bytes1(0x02))) ); vm.expectRevert(InvalidCallType.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_WhenTheDelegatecallSucceeds_HookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - whenTheDelegatecallSucceeds - { - // it should call postHook with the context from preHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.resetState(); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Delegatecall should succeed"); - assertTrue(hook.postHookCalled(), "postHook should be called"); - } - - function test_GivenPostHookReverts_Delegatecall() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - whenTheDelegatecallSucceeds - { - // it should propagate the revert - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - hook.setRevertOnPostHook(true); - vm.expectRevert(MockHook.PostHookReverted.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenPostHookSucceeds_Delegatecall() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - whenTheDelegatecallSucceeds - { - // it should return the delegatecall result - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.testFunction.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - assertTrue(success, "Should return delegatecall result"); - } - - function test_WhenTheDelegatecallReverts_HookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - givenCallTypeIsDELEGATECALL - { - // it should propagate the revert and skip postHook - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.forceRevert.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, _callType, _hookAddress)) - ); - - vm.expectRevert(MockFallback.FallbackRevert.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - function test_GivenCallTypeIsUnsupported_HookContract() - external - whenTheSelectorIsACustomRegisteredSelector - givenTheSelectorIsRegisteredWithAHookContract - givenPreHookSucceeds - { - // it should revert with InvalidCallType error - vm.stopPrank(); - vm.startPrank(address(ep)); - - bytes4 testSelector = MockFallback.getCaller.selector; - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSelector, bytes1(0x02), address(hook))) - ); - - vm.expectRevert(InvalidCallType.selector); - (bool success,) = address(kernel).call(abi.encodeWithSelector(testSelector)); - } - - /*////////////////////////////////////////////////////////////// - HELPER FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function _installFallback(bytes4 selector) internal { - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector, _callType, _hookAddress)) - ); - } - - function _configureHook() internal { - hook.resetState(); - hook.setRevertOnPreHook(!_preHookSucceeds); - } - - function _targetSelector() internal view returns (bytes4) { - if (_targetCallSucceeds) { - return _callType == bytes1(0xff) ? MockFallback.testFunction.selector : MockFallback.getCaller.selector; - } - return MockFallback.forceRevert.selector; + MockFallback(address(kernel)).testFunction(); } } diff --git a/test/btt/Kernel.initialize.t.sol b/test/btt/Kernel.initialize.t.sol index 28a5fb79..1000a996 100644 --- a/test/btt/Kernel.initialize.t.sol +++ b/test/btt/Kernel.initialize.t.sol @@ -11,7 +11,6 @@ import {MockValidator} from "../mock/MockValidator.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; -import {MockHook} from "../mock/MockHook.sol"; import {IValidator} from "src/interfaces/IERC7579Modules.sol"; import {PermissionId} from "src/types/Types.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; @@ -96,9 +95,8 @@ abstract contract Kernel_initialize is BTTModifiers { assertTrue(newKernel.isModuleInstalled(1, address(mockValidator), ""), "Validator should be installed"); // Check root is set (hook should be address(1) for root) - assertEq( - newKernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(1), + assertTrue( + newKernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).installed, "Validator should be set as root" ); } @@ -139,10 +137,8 @@ abstract contract Kernel_initialize is BTTModifiers { ); // Permission should be set as root (hook should be address(1)) - assertEq( - newKernel.validationInfo(permissionToIdentifier(testPermId)).hook, - address(1), - "Permission should be set as root" + assertTrue( + newKernel.validationInfo(permissionToIdentifier(testPermId)).installed, "Permission should be set as root" ); } @@ -183,25 +179,21 @@ abstract contract Kernel_initialize is BTTModifiers { factory.deploy(packages, 1005); } - function test_GivenSubsequentPackagesContainExecutorsHooksOrFallbacks() + function test_GivenSubsequentPackagesContainExecutors() external givenTheAccountHasNotBeenInitialized whenPackagesArrayHasOneOrMoreElements { MockValidator mockValidator = new MockValidator(); MockExecutor mockExecutor = new MockExecutor(); - MockHook mockHook = new MockHook(); - Install[] memory packages = new Install[](3); + Install[] memory packages = new Install[](2); packages[0] = Install({moduleType: 1, module: address(mockValidator), moduleData: hex"", internalData: hex""}); packages[1] = Install({moduleType: 2, module: address(mockExecutor), moduleData: hex"", internalData: hex""}); - packages[2] = Install({moduleType: 4, module: address(mockHook), moduleData: hex"", internalData: hex""}); Kernel newKernel = Kernel(payable(factory.deploy(packages, 1004))); - // All modules should be installed assertTrue(newKernel.isModuleInstalled(1, address(mockValidator), ""), "Validator should be installed"); assertTrue(newKernel.isModuleInstalled(2, address(mockExecutor), ""), "Executor should be installed"); - assertTrue(newKernel.isModuleInstalled(4, address(mockHook), ""), "Hook should be installed"); } } diff --git a/test/btt/Kernel.installModule.t.sol b/test/btt/Kernel.installModule.t.sol index 4fddf439..3f32987e 100644 --- a/test/btt/Kernel.installModule.t.sol +++ b/test/btt/Kernel.installModule.t.sol @@ -15,16 +15,10 @@ import {MockRevertingFallback} from "../mock/MockRevertingFallback.sol"; import {MockRevertingHook} from "../mock/MockRevertingHook.sol"; import {MockRevertingPolicy} from "../mock/MockRevertingPolicy.sol"; import {MockRevertingSigner} from "../mock/MockRevertingSigner.sol"; -import {IValidator, IExecutor, IHook} from "src/interfaces/IERC7579Modules.sol"; +import {IValidator, IExecutor} from "src/interfaces/IERC7579Modules.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; import {PermissionId} from "src/types/Types.sol"; -import { - Unauthorized, - NotImplemented, - OccupiedValidationId, - ModuleInstallFailed, - NotInstalled -} from "src/types/Error.sol"; +import {Unauthorized, NotImplemented, OccupiedValidationId, ModuleInstallFailed} from "src/types/Error.sol"; import {Install} from "src/types/Structs.sol"; /// @title Kernel.installModule BTT Tests @@ -100,9 +94,8 @@ abstract contract Kernel_installModule is BTTModifiers { kernel.installModule(1, address(mockValidator), abi.encode(hex"", hex"")); // Verify validator is installed (hook should be address(1) by default) - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).installed, "Validator should be installed with hook=address(1)" ); } @@ -112,73 +105,11 @@ abstract contract Kernel_installModule is BTTModifiers { _; } - function test_GivenTheHookIsAddress0OrAddress1() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsValidator - givenTheValidatorIsNotInstalled - givenInternalDataContainsAHookAddress - { - // it should set the hook accordingly - MockValidator mockValidator = new MockValidator(); - - // Test with address(1) - explicit no-hook marker - kernel.installModule(1, address(mockValidator), abi.encode(hex"", abi.encodePacked(address(1)))); - - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(1), - "Validator hook should be address(1)" - ); - } - modifier givenTheHookIsAContract() { _hookIsContract = true; _; } - function test_GivenTheHookIsEnabled() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsValidator - givenTheValidatorIsNotInstalled - givenInternalDataContainsAHookAddress - givenTheHookIsAContract - { - // it should associate the hook with this validator - MockValidator mockValidator = new MockValidator(); - MockHook mockHook = new MockHook(); - - // Install hook first - kernel.installModule(4, address(mockHook), abi.encode(hex"", hex"")); - - // Install validator with the hook - kernel.installModule(1, address(mockValidator), abi.encode(hex"", abi.encodePacked(address(mockHook)))); - - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(mockHook), - "Validator should have the custom hook" - ); - } - - function test_GivenTheHookIsNotEnabled() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsValidator - givenTheValidatorIsNotInstalled - givenInternalDataContainsAHookAddress - givenTheHookIsAContract - { - // it should revert with NotInstalled error - MockValidator mockValidator = new MockValidator(); - MockHook mockHook = new MockHook(); - - // Do NOT install hook first - try to use an uninstalled hook - vm.expectRevert(NotInstalled.selector); - kernel.installModule(1, address(mockValidator), abi.encode(hex"", abi.encodePacked(address(mockHook)))); - } - modifier givenModuleTypeIsExecutor() override { _; } @@ -208,45 +139,6 @@ abstract contract Kernel_installModule is BTTModifiers { assertTrue(kernel.isModuleInstalled(2, address(mockExecutor), ""), "Executor should be installed"); } - function test_GivenTheHookIsEnabled_GivenInternalDataContainsAHookAddress() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsExecutor - givenInternalDataContainsAHookAddress - { - // it should associate the hook with this executor - MockExecutor mockExecutor = new MockExecutor(); - MockHook mockHook = new MockHook(); - - // Install hook first so it's enabled - kernel.installModule(4, address(mockHook), abi.encode(hex"", hex"")); - - // Install executor with the enabled hook - kernel.installModule(2, address(mockExecutor), abi.encode(hex"", abi.encodePacked(address(mockHook)))); - - assertTrue(kernel.isModuleInstalled(2, address(mockExecutor), ""), "Executor should be installed"); - assertEq( - address(kernel.executorConfig(address(mockExecutor)).hook), - address(mockHook), - "Executor should have the custom hook" - ); - } - - function test_GivenTheHookIsNotEnabled_GivenInternalDataContainsAHookAddress() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsExecutor - givenInternalDataContainsAHookAddress - { - // it should revert with NotInstalled error - MockExecutor mockExecutor = new MockExecutor(); - MockHook mockHook = new MockHook(); - - // Do NOT install hook first - try to use an uninstalled hook - vm.expectRevert(NotInstalled.selector); - kernel.installModule(2, address(mockExecutor), abi.encode(hex"", abi.encodePacked(address(mockHook)))); - } - modifier givenModuleTypeIsFallback() override { _; } @@ -262,9 +154,7 @@ abstract contract Kernel_installModule is BTTModifiers { bytes4 selector = bytes4(keccak256("customFunction()")); // Install first fallback - kernel.installModule( - 3, address(mockFallback1), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(mockFallback1), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00)))); assertTrue( kernel.isModuleInstalled(3, address(mockFallback1), abi.encodePacked(selector)), @@ -272,9 +162,7 @@ abstract contract Kernel_installModule is BTTModifiers { ); // Overwrite with second fallback - kernel.installModule( - 3, address(mockFallback2), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(mockFallback2), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00)))); assertTrue( kernel.isModuleInstalled(3, address(mockFallback2), abi.encodePacked(selector)), @@ -299,9 +187,7 @@ abstract contract Kernel_installModule is BTTModifiers { // callType 0x00 = CALL (not DELEGATECALL) vm.expectRevert(ModuleInstallFailed.selector); - kernel.installModule( - 3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00)))); } function test_WhenOnInstallRevertsAndCallTypeIsDELEGATECALL() @@ -315,9 +201,7 @@ abstract contract Kernel_installModule is BTTModifiers { bytes4 selector = bytes4(keccak256("customFunction()")); // callType 0xff = DELEGATECALL - kernel.installModule( - 3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0xff), address(1))) - ); + kernel.installModule(3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0xff)))); assertTrue( kernel.isModuleInstalled(3, address(revertingFallback), abi.encodePacked(selector)), @@ -338,9 +222,7 @@ abstract contract Kernel_installModule is BTTModifiers { MockFallback mockFallback = new MockFallback(); bytes4 selector = bytes4(keccak256("customFunction()")); - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00)))); assertTrue( kernel.isModuleInstalled(3, address(mockFallback), abi.encodePacked(selector)), @@ -352,30 +234,6 @@ abstract contract Kernel_installModule is BTTModifiers { _; } - function test_WhenInternalDataIsEmptyAndOnInstallReverts() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsHook - { - // it should revert with ModuleInstallFailed error - MockRevertingHook revertingHook = new MockRevertingHook(); - - // Empty internalData = "" means onInstall revert will cause failure - vm.expectRevert(ModuleInstallFailed.selector); - kernel.installModule(4, address(revertingHook), abi.encode(hex"", "")); - } - - function test_WhenInternalDataIsNon_empty() external whenTheCallerIsTheEntryPointOrSelf givenModuleTypeIsHook { - // it should enable the hook even if onInstall reverts - // it should emit ModuleInstalled event - MockRevertingHook revertingHook = new MockRevertingHook(); - - // Non-empty internalData means hook is installed even if onInstall reverts - kernel.installModule(4, address(revertingHook), abi.encode(hex"", "non-empty")); - - assertTrue(kernel.isModuleInstalled(4, address(revertingHook), ""), "Hook should be enabled"); - } - modifier givenModuleTypeIsPolicy() override { _; } @@ -509,25 +367,6 @@ abstract contract Kernel_installModule is BTTModifiers { kernel.installModule(6, address(revertingSigner), abi.encode(hex"", abi.encodePacked(testPermId))); } - function test_GivenTheHookAddressInInternalDataIsInvalid() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsPolicy - { - // Policy internalData hook bytes are ignored (hook is set via signer install), - // so policy install should succeed regardless of extra bytes in internalData - MockPolicy mockPolicy = new MockPolicy(); - PermissionId testPermId = PermissionId.wrap(bytes4(keccak256("testPolicy"))); - MockHook uninstalledHook = new MockHook(); - - kernel.installModule( - 5, address(mockPolicy), abi.encode(hex"", abi.encodePacked(testPermId, address(uninstalledHook))) - ); - assertTrue( - kernel.isModuleInstalled(5, address(mockPolicy), abi.encodePacked(testPermId)), "Policy should be installed" - ); - } - function test_GivenThePermissionIdChangesDuringAMulti_packageInstall_GivenModuleTypeIsSigner() external whenTheCallerIsTheEntryPointOrSelf @@ -562,11 +401,8 @@ abstract contract Kernel_installModule is BTTModifiers { kernel.installModule(5, address(mockPolicy), abi.encode(hex"", abi.encodePacked(testPermId))); kernel.installModule(6, address(mockSigner), abi.encode(hex"", abi.encodePacked(testPermId))); - // Permission should now be usable (hook set to address(1)) - assertEq( - kernel.validationInfo(permissionToIdentifier(testPermId)).hook, - address(1), - "Permission hook should be set to address(1)" + assertTrue( + kernel.validationInfo(permissionToIdentifier(testPermId)).installed, "Permission should be installed" ); } @@ -632,64 +468,6 @@ abstract contract Kernel_installModule is BTTModifiers { bool internal _useInstallArrayOverload; bool internal _internalDataNonEmpty; - function test_GivenTheExecutorHookIsEnabled() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsExecutor - { - // it should associate the hook with this executor - MockExecutor mockExecutor = new MockExecutor(); - MockHook mockHook = new MockHook(); - - // Install hook first so it's enabled - kernel.installModule(4, address(mockHook), abi.encode(hex"", hex"")); - - // Install executor with the enabled hook - kernel.installModule(2, address(mockExecutor), abi.encode(hex"", abi.encodePacked(address(mockHook)))); - - assertTrue(kernel.isModuleInstalled(2, address(mockExecutor), ""), "Executor should be installed"); - assertEq( - address(kernel.executorConfig(address(mockExecutor)).hook), - address(mockHook), - "Executor should have the custom hook" - ); - } - - function test_GivenTheExecutorHookIsNotEnabled() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsExecutor - { - // it should revert with NotInstalled error - MockExecutor mockExecutor = new MockExecutor(); - MockHook mockHook = new MockHook(); - - // Do NOT install hook first - try to use an uninstalled hook - vm.expectRevert(NotInstalled.selector); - kernel.installModule(2, address(mockExecutor), abi.encode(hex"", abi.encodePacked(address(mockHook)))); - } - - function test_GivenTheHookAddressInInternalDataIsInvalid_GivenModuleTypeIsSigner() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsSigner - { - // it should revert with NotInstalled error - MockPolicy mockPolicy = new MockPolicy(); - MockSigner mockSigner = new MockSigner(); - PermissionId testPermId = PermissionId.wrap(bytes4(keccak256("testSigner"))); - MockHook uninstalledHook = new MockHook(); - - // Install policy first - kernel.installModule(5, address(mockPolicy), abi.encode(hex"", abi.encodePacked(testPermId))); - - // Try to install signer with uninstalled hook - vm.expectRevert(NotInstalled.selector); - kernel.installModule( - 6, address(mockSigner), abi.encode(hex"", abi.encodePacked(testPermId, address(uninstalledHook))) - ); - } - function test_WhenOnInstallReverts_Executor() external whenTheCallerIsTheEntryPointOrSelf @@ -730,9 +508,7 @@ abstract contract Kernel_installModule is BTTModifiers { bytes4 selector = bytes4(keccak256("anotherFunction()")); vm.expectRevert(ModuleInstallFailed.selector); - kernel.installModule( - 3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00)))); } function test_WhenOnInstallRevertsAndCallTypeIsDelegatecall() @@ -745,9 +521,7 @@ abstract contract Kernel_installModule is BTTModifiers { MockRevertingFallback revertingFallback = new MockRevertingFallback(); bytes4 selector = bytes4(keccak256("anotherFunction()")); - kernel.installModule( - 3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0xff), address(1))) - ); + kernel.installModule(3, address(revertingFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0xff)))); assertTrue( kernel.isModuleInstalled(3, address(revertingFallback), abi.encodePacked(selector)), @@ -768,9 +542,7 @@ abstract contract Kernel_installModule is BTTModifiers { MockFallback mockFallback = new MockFallback(); bytes4 selector = bytes4(keccak256("anotherFunction()")); - kernel.installModule( - 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00), address(1))) - ); + kernel.installModule(3, address(mockFallback), abi.encode(hex"", abi.encodePacked(selector, bytes1(0x00)))); assertTrue( kernel.isModuleInstalled(3, address(mockFallback), abi.encodePacked(selector)), @@ -783,23 +555,6 @@ abstract contract Kernel_installModule is BTTModifiers { _; } - function test_WhenInternalDataIsNonEmpty() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsHook - whenInternalDataIsNonEmpty - { - // it should enable the hook even if onInstall reverts - // it should emit ModuleInstalled event - MockRevertingHook revertingHook = new MockRevertingHook(); - - kernel.installModule(4, address(revertingHook), abi.encode(hex"", "some-data")); - - assertTrue( - kernel.isModuleInstalled(4, address(revertingHook), ""), "Hook should be enabled with non-empty data" - ); - } - function test_GivenModuleTypeIsPolicy_ParsesPermissionId() external whenTheCallerIsTheEntryPointOrSelf @@ -826,23 +581,6 @@ abstract contract Kernel_installModule is BTTModifiers { kernel.installModule(5, address(revertingPolicy), abi.encode(hex"", abi.encodePacked(testPermId))); } - function test_GivenTheHookAddressInInternalDataIsInvalid_Policy() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsPolicy - { - // Policy internalData hook bytes are ignored (hook is set via signer install), - // so policy install should succeed regardless of extra bytes in internalData - MockPolicy mockPolicy = new MockPolicy(); - PermissionId testPermId = PermissionId.wrap(bytes4(keccak256("invalidHookPolicy"))); - address fakeHook = makeAddr("fakeHook"); - - kernel.installModule(5, address(mockPolicy), abi.encode(hex"", abi.encodePacked(testPermId, fakeHook))); - assertTrue( - kernel.isModuleInstalled(5, address(mockPolicy), abi.encodePacked(testPermId)), "Policy should be installed" - ); - } - function test_GivenThePermissionIdChangesDuringMultiPackageInstall_Policy() external whenTheCallerIsTheEntryPointOrSelf @@ -905,23 +643,6 @@ abstract contract Kernel_installModule is BTTModifiers { kernel.installModule(6, address(revertingSigner), abi.encode(hex"", abi.encodePacked(testPermId))); } - function test_GivenTheHookAddressInInternalDataIsInvalid_Signer() - external - whenTheCallerIsTheEntryPointOrSelf - givenModuleTypeIsSigner - { - // it should revert with NotInstalled error - MockPolicy mockPolicy = new MockPolicy(); - MockSigner mockSigner = new MockSigner(); - PermissionId testPermId = PermissionId.wrap(bytes4(keccak256("invalidHookSigner"))); - address fakeHook = makeAddr("fakeHook"); - - kernel.installModule(5, address(mockPolicy), abi.encode(hex"", abi.encodePacked(testPermId))); - - vm.expectRevert(NotInstalled.selector); - kernel.installModule(6, address(mockSigner), abi.encode(hex"", abi.encodePacked(testPermId, fakeHook))); - } - function test_GivenThePermissionIdChangesDuringMultiPackageInstall_Signer() external whenTheCallerIsTheEntryPointOrSelf @@ -955,8 +676,8 @@ abstract contract Kernel_installModule is BTTModifiers { assertTrue( kernel.isModuleInstalled(6, address(mockSigner), abi.encodePacked(testPermId)), "Signer should be installed" ); - assertEq( - kernel.validationInfo(permissionToIdentifier(testPermId)).hook, address(1), "Permission should be activated" + assertTrue( + kernel.validationInfo(permissionToIdentifier(testPermId)).installed, "Permission should be activated" ); } } diff --git a/test/btt/Kernel.installModuleWithSignature.t.sol b/test/btt/Kernel.installModuleWithSignature.t.sol index ccbcf88c..840bcaa0 100644 --- a/test/btt/Kernel.installModuleWithSignature.t.sol +++ b/test/btt/Kernel.installModuleWithSignature.t.sol @@ -192,22 +192,15 @@ abstract contract Kernel_installModuleWithSignature is BTTModifiers { } function test_GivenPackagesContainAValidator() external { - // it should install the validator with its hook and allowed selectors - // it should NOT automatically set it as root + // it should install the validator with allowed selectors without changing root MockValidator testValidator = new MockValidator(); - MockHook testHook = new MockHook(); - - // Install hook first via entrypoint - vm.startPrank(address(ep)); - kernel.installModule(4, address(testHook), abi.encode(hex"", hex"")); - vm.stopPrank(); Install[] memory packages = new Install[](1); packages[0] = Install({ moduleType: 1, module: address(testValidator), moduleData: hex"", - internalData: abi.encodePacked(address(testHook), bytes4(keccak256("execute(bytes32,bytes)"))) + internalData: abi.encodePacked(bytes4(keccak256("execute(bytes32,bytes)"))) }); bytes32 digest = KernelHelper.installDigest(address(kernel), false, 0, packages); @@ -218,11 +211,9 @@ abstract contract Kernel_installModuleWithSignature is BTTModifiers { // Validator should be installed assertTrue(kernel.isModuleInstalled(1, address(testValidator), ""), "Validator should be installed"); - // Verify the hook was set - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(testValidator)))).hook, - address(testHook), - "Validator should have the hook set" + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(testValidator)))).installed, + "Validator should be marked installed" ); } @@ -251,7 +242,7 @@ abstract contract Kernel_installModuleWithSignature is BTTModifiers { moduleType: 3, module: address(testFallback), moduleData: hex"", - internalData: abi.encodePacked(selector, bytes1(0x00), address(1)) + internalData: abi.encodePacked(selector, bytes1(0x00)) }); bytes32 digest = KernelHelper.installDigest(address(kernel), false, 0, packages); @@ -294,19 +285,4 @@ abstract contract Kernel_installModuleWithSignature is BTTModifiers { "Signer should be installed for permissionId" ); } - - function test_GivenPackagesContainAHook() external { - // it should enable the hook - MockHook testHook = new MockHook(); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 4, module: address(testHook), moduleData: hex"", internalData: hex""}); - - bytes32 digest = KernelHelper.installDigest(address(kernel), false, 0, packages); - bytes memory signature = _rootSignHash(digest, true); - - kernel.installModule(false, 0, packages, signature); - - assertTrue(kernel.isModuleInstalled(4, address(testHook), ""), "Hook should be enabled"); - } } diff --git a/test/btt/Kernel.isModuleInstalled.t.sol b/test/btt/Kernel.isModuleInstalled.t.sol index f21c2507..be8d195f 100644 --- a/test/btt/Kernel.isModuleInstalled.t.sol +++ b/test/btt/Kernel.isModuleInstalled.t.sol @@ -9,6 +9,8 @@ import {MockHook} from "../mock/MockHook.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; import {NotImplemented} from "src/types/Error.sol"; +import {SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE} from "src/types/Constants.sol"; +import {permissionToIdentifier} from "src/lib/Utils.sol"; /// @title Kernel.isModuleInstalled BTT Tests /// @notice Tests for isModuleInstalled following Branching Tree Technique @@ -79,7 +81,7 @@ abstract contract Kernel_isModuleInstalled is BTTModifiers { bytes4 selector = bytes4(keccak256("customFunction()")); // Install fallback with selector - bytes memory internalData = abi.encodePacked(selector, bytes1(0x00), address(1)); + bytes memory internalData = abi.encodePacked(selector, bytes1(0x00)); vm.prank(address(ep)); kernel.installModule(3, address(mockFallback), abi.encode(hex"", internalData)); @@ -100,25 +102,37 @@ abstract contract Kernel_isModuleInstalled is BTTModifiers { } /*////////////////////////////////////////////////////////////// - HOOK (TYPE 4) TESTS + EXECUTION HOOK (TYPE 11) TESTS //////////////////////////////////////////////////////////////*/ - modifier givenModuleTypeIdIs4Hook() { - _moduleTypeId = 4; - _; - } - - function test_GivenTheHookIsEnabled() external givenModuleTypeIdIs4Hook { + function test_GivenTheScopedExecutionHookIsInstalled() external { + MockSigner mockSigner = new MockSigner(); MockHook mockHook = new MockHook(); - vm.prank(address(ep)); - kernel.installModule(4, address(mockHook), abi.encode(hex"", "")); + bytes memory context = + abi.encodePacked(SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, permissionToIdentifier(permissionId)); + vm.startPrank(address(ep)); + kernel.installModule(6, address(mockSigner), abi.encode(hex"", abi.encodePacked(permissionId))); + kernel.installModule(11, address(mockHook), abi.encode(hex"", context)); + vm.stopPrank(); - assertTrue(kernel.isModuleInstalled(4, address(mockHook), ""), "Enabled hook should return true"); + assertTrue( + kernel.isModuleInstalled(11, address(mockHook), context), + "Installed scoped execution hook should return true" + ); } - function test_GivenTheHookIsNotEnabled() external givenModuleTypeIdIs4Hook { + function test_GivenTheScopedExecutionHookIsNotInstalled() external { MockHook mockHook = new MockHook(); - assertFalse(kernel.isModuleInstalled(4, address(mockHook), ""), "Not enabled hook should return false"); + bytes memory context = + abi.encodePacked(SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, permissionToIdentifier(permissionId)); + assertFalse( + kernel.isModuleInstalled(11, address(mockHook), context), + "Uninstalled scoped execution hook should return false" + ); + assertFalse( + kernel.isModuleInstalled(11, address(mockHook), hex""), + "Empty scoped execution hook context should return false" + ); } /*////////////////////////////////////////////////////////////// @@ -192,6 +206,8 @@ abstract contract Kernel_isModuleInstalled is BTTModifiers { //////////////////////////////////////////////////////////////*/ function test_GivenModuleTypeIdIsUnsupported() external { + vm.expectRevert(NotImplemented.selector); + kernel.isModuleInstalled(4, address(0x123), ""); vm.expectRevert(NotImplemented.selector); kernel.isModuleInstalled(7, address(0x123), ""); } diff --git a/test/btt/Kernel.setRoot.t.sol b/test/btt/Kernel.setRoot.t.sol index 76dfaa3d..fa2f12e5 100644 --- a/test/btt/Kernel.setRoot.t.sol +++ b/test/btt/Kernel.setRoot.t.sol @@ -89,7 +89,7 @@ abstract contract Kernel_setRoot is BTTModifiers { _callSetRoot(vId, new Install[](0), hex""); // Verify the root was changed - assertEq(kernel.validationInfo(vId).hook, address(1), "New validator should be root"); + assertTrue(kernel.validationInfo(vId).installed, "New validator should be root"); } function test_GivenVIdCorrespondsToAnInstalledPermission() @@ -110,7 +110,7 @@ abstract contract Kernel_setRoot is BTTModifiers { _callSetRoot(vId, new Install[](0), hex""); // Verify the root was changed - assertEq(kernel.validationInfo(vId).hook, address(1), "Permission should be root"); + assertTrue(kernel.validationInfo(vId).installed, "Permission should be root"); } /*////////////////////////////////////////////////////////////// @@ -154,9 +154,8 @@ abstract contract Kernel_setRoot is BTTModifiers { assertTrue(kernel.isModuleInstalled(1, address(newRoot), ""), "New root should be installed"); // New root should be the active root - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(newRoot)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(newRoot)))).installed, "New validator should be root" ); } @@ -184,9 +183,8 @@ abstract contract Kernel_setRoot is BTTModifiers { // New root should be installed and active assertTrue(kernel.isModuleInstalled(1, address(newRoot), ""), "New root should be installed"); - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(newRoot)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(newRoot)))).installed, "New validator should be root" ); } @@ -310,9 +308,8 @@ abstract contract Kernel_setRoot is BTTModifiers { // New root should be installed and active assertTrue(kernel.isModuleInstalled(1, address(newRoot), ""), "New root should be installed"); - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(newRoot)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(newRoot)))).installed, "New validator should be root" ); } @@ -380,11 +377,10 @@ abstract contract Kernel_setRoot is BTTModifiers { // Set the root storage directly (first slot in ValidationStorage struct is the root) vm.store(address(kernel), validationStorageSlot, bytes32(malformedRoot)); - // Also need to set the vInfo[malformedRoot].hook to address(1) so it's considered "installed" - // vInfo mapping slot = keccak256(abi.encode(malformedRoot, slot + 1)) - // ValidationInfo first field is hook (address) + // Mark vInfo[malformedRoot].installed=true. ValidationInfo packs nonce in + // the low 4 bytes and installed in the following byte. bytes32 vInfoSlot = keccak256(abi.encode(bytes32(malformedRoot), bytes32(uint256(validationStorageSlot) + 1))); - vm.store(address(kernel), vInfoSlot, bytes32(uint256(uint160(address(1))))); + vm.store(address(kernel), vInfoSlot, bytes32(uint256(1) << 32)); // Now try to replace root with removeCurrent=true MockValidator newRoot = new MockValidator(); @@ -444,7 +440,7 @@ abstract contract Kernel_setRoot is BTTModifiers { // Root should be set to the permission derived from testPermId ValidationId expectedRoot = permissionToIdentifier(testPermId); - assertEq(kernel.validationInfo(expectedRoot).hook, address(1), "Permission should be installed and set as root"); + assertTrue(kernel.validationInfo(expectedRoot).installed, "Permission should be installed and set as root"); } function test_GivenTheFirstPackageModuleTypeIsSIGNER() @@ -469,9 +465,8 @@ abstract contract Kernel_setRoot is BTTModifiers { // Root should be set to the permission derived from testPermId ValidationId expectedRoot = permissionToIdentifier(testPermId); - assertEq( - kernel.validationInfo(expectedRoot).hook, - address(1), + assertTrue( + kernel.validationInfo(expectedRoot).installed, "Permission (signer-only) should be installed and set as root" ); } diff --git a/test/btt/Kernel.supportsModule.t.sol b/test/btt/Kernel.supportsModule.t.sol index 47cb94aa..7da07c38 100644 --- a/test/btt/Kernel.supportsModule.t.sol +++ b/test/btt/Kernel.supportsModule.t.sol @@ -24,7 +24,7 @@ abstract contract Kernel_supportsModule is BTTModifiers { } function test_GivenModuleTypeIdIs4Hook() external { - assertTrue(kernel.supportsModule(4), "Should support Hook (type 4)"); + assertFalse(kernel.supportsModule(4), "Should not support generic Hook (type 4)"); } function test_GivenModuleTypeIdIs5Policy() external { @@ -35,9 +35,15 @@ abstract contract Kernel_supportsModule is BTTModifiers { assertTrue(kernel.supportsModule(6), "Should support Signer (type 6)"); } - function test_GivenModuleTypeIdIs7OrGreater() external { + function test_GivenModuleTypeIdIs11ScopedExecutionHook() external { + assertTrue(kernel.supportsModule(11), "Should support ScopedExecutionHook (type 11)"); + } + + function test_GivenUnsupportedModuleType() external { assertFalse(kernel.supportsModule(7), "Should not support moduleType 7"); assertFalse(kernel.supportsModule(8), "Should not support moduleType 8"); + assertFalse(kernel.supportsModule(10), "Should not support moduleType 10"); + assertFalse(kernel.supportsModule(12), "Should not support moduleType 12"); assertFalse(kernel.supportsModule(100), "Should not support moduleType 100"); } } diff --git a/test/btt/Kernel.uninstallModule.t.sol b/test/btt/Kernel.uninstallModule.t.sol index 19368e4e..e8e14058 100644 --- a/test/btt/Kernel.uninstallModule.t.sol +++ b/test/btt/Kernel.uninstallModule.t.sol @@ -49,9 +49,8 @@ abstract contract Kernel_uninstallModule is BTTModifiers { kernel.installModule(1, address(mockValidator), abi.encode(hex"", hex"")); // Verify it's installed - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).installed, "Validator should be installed" ); @@ -59,9 +58,8 @@ abstract contract Kernel_uninstallModule is BTTModifiers { kernel.uninstallModule(1, address(mockValidator), abi.encode(hex"", hex"")); // Verify hook state is cleared - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(0), + assertFalse( + kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).installed, "Validator hook should be cleared" ); } @@ -78,9 +76,8 @@ abstract contract Kernel_uninstallModule is BTTModifiers { kernel.uninstallModule(1, address(mockValidator), abi.encode(hex"", hex"")); // Verify it remains uninstalled (hook is still address(0)) - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(0), + assertFalse( + kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).installed, "Validator should remain uninstalled" ); } @@ -96,9 +93,8 @@ abstract contract Kernel_uninstallModule is BTTModifiers { kernel.uninstallModule(1, address(mockValidator), abi.encode(hex"", hex"")); // Verify the validator is no longer installed (hook is cleared) - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).hook, - address(0), + assertFalse( + kernel.validationInfo(validatorToIdentifier(IValidator(address(mockValidator)))).installed, "Validator hook should be cleared" ); } @@ -145,7 +141,7 @@ abstract contract Kernel_uninstallModule is BTTModifiers { // it should clear selector config for the selector MockFallback mockFallback = new MockFallback(); bytes4 selector = bytes4(keccak256("testFallback()")); - bytes memory internalData = abi.encodePacked(selector, bytes1(0x00), address(1)); + bytes memory internalData = abi.encodePacked(selector, bytes1(0x00)); kernel.installModule(3, address(mockFallback), abi.encode(hex"", internalData)); @@ -172,20 +168,6 @@ abstract contract Kernel_uninstallModule is BTTModifiers { _; } - function test_GivenModuleTypeIsHookUninstall() - external - whenTheCallerIsTheEntryPointOrSelfUninstall - givenModuleTypeIsHookUninstall - { - MockHook mockHook = new MockHook(); - kernel.installModule(4, address(mockHook), abi.encode(hex"", "")); - - kernel.uninstallModule(4, address(mockHook), abi.encode(hex"", "")); - - // Verify the hook is no longer enabled - assertFalse(kernel.isModuleInstalled(4, address(mockHook), ""), "Hook should be disabled"); - } - modifier givenModuleTypeIsPolicyUninstall() { _uninstallModuleTypeId = 5; _; @@ -339,10 +321,8 @@ abstract contract Kernel_uninstallModule is BTTModifiers { kernel.isModuleInstalled(6, address(mockSigner), abi.encodePacked(testPermId)), "Signer should be uninstalled" ); - assertEq( - kernel.validationInfo(permissionToIdentifier(testPermId)).hook, - address(0), - "Permission hook should be cleared" + assertFalse( + kernel.validationInfo(permissionToIdentifier(testPermId)).installed, "Permission should be uninstalled" ); } diff --git a/test/btt/Kernel.validateUserOp.t.sol b/test/btt/Kernel.validateUserOp.t.sol index 2cdb4287..8bb4865c 100644 --- a/test/btt/Kernel.validateUserOp.t.sol +++ b/test/btt/Kernel.validateUserOp.t.sol @@ -15,7 +15,6 @@ import {PermissionId} from "src/types/Types.sol"; import { Unauthorized, UnauthorizedCallData, - InvalidValidator, InvalidPermissionId, InvalidNonce, InvalidVid, @@ -95,7 +94,7 @@ abstract contract Kernel_validateUserOp is BTTModifiers { moduleType: 1, module: address(newValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); op2.signature = abi.encode( uint256(0), @@ -149,9 +148,8 @@ abstract contract Kernel_validateUserOp is BTTModifiers { uint256 validationData = kernel.validateUserOp(op, userOpHash, 0); // Verify the validator was installed - assertEq( - kernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).hook, - address(1), + assertTrue( + kernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).installed, "Validator should be installed" ); assertEq(validationData, 0, "Enable mode with valid signature should return 0"); @@ -281,9 +279,7 @@ abstract contract Kernel_validateUserOp is BTTModifiers { { // Deploy and install a misconfigured validator that returns empty data MockEmptyReturnValidator emptyValidator = new MockEmptyReturnValidator(); - kernel.installModule( - 1, address(emptyValidator), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) - ); + kernel.installModule(1, address(emptyValidator), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector))); PackedUserOperation memory op = PackedUserOperation({ sender: address(kernel), @@ -396,40 +392,6 @@ abstract contract Kernel_validateUserOp is BTTModifiers { kernel.validateUserOp(op, userOpHash, 0); } - function test_WhenTheCallDataUsesExecuteUserOpWrapper() - external - whenTheCallerIsTheEntryPointOrSelf - givenTheValidationTypeIsVALIDATOR - givenTheValidatorIsInstalled - givenTheCallDataSelectorIsInTheAllowedListAndHookIsNotAddress1 - { - // it should set the validation hook for later execution - // it should continue with signature validation - vm.stopPrank(); - vm.startPrank(address(ep)); - - // Install hook first - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - - _installValidatorWithSelectorPolicy(); - - PackedUserOperation memory op = _createUserOpWithValidatorValidation(); - op.callData = abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ); - op.signature = _validatorSignUserOp(op, true, false); - bytes32 userOpHash = ep.getUserOpHash(op); - - uint256 validationData = kernel.validateUserOp(op, userOpHash, 0); - - assertEq(validationData, 0, "Validation with executeUserOp wrapper and hook should succeed"); - } - modifier givenTheCallDataSelectorIsNotDirectlyAllowed() { _selectorAllowed = false; _selectorHook = address(0); @@ -531,41 +493,6 @@ abstract contract Kernel_validateUserOp is BTTModifiers { kernel.validateUserOp(op, userOpHash, 0); } - function test_GivenAValidationHookIsConfigured() - external - whenTheCallerIsTheEntryPointOrSelf - givenTheValidationTypeIsVALIDATOR - givenTheValidatorIsInstalled - { - // it should store the hook in transient storage for executeUserOp - vm.stopPrank(); - vm.startPrank(address(ep)); - - // Install hook first - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - - // Configure to allow execute selector with the hook - _selectorAllowed = true; - _selectorHook = address(hook); - _installValidatorWithSelectorPolicy(); - - PackedUserOperation memory op = _createUserOpWithValidatorValidation(); - op.callData = abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ); - op.signature = _validatorSignUserOp(op, true, false); - bytes32 userOpHash = ep.getUserOpHash(op); - - uint256 validationData = kernel.validateUserOp(op, userOpHash, 0); - - assertEq(validationData, 0, "Validator with hook configured should return 0"); - } - modifier givenTheValidationTypeIsPERMISSION() { _validationType = 2; _selectorAllowed = false; @@ -947,7 +874,7 @@ abstract contract Kernel_validateUserOp is BTTModifiers { moduleType: 1, module: address(newValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); op2.signature = abi.encode( uint256(0), @@ -1029,9 +956,7 @@ abstract contract Kernel_validateUserOp is BTTModifiers { givenValidationTypeIsValidator { // Manually install validator with execute selector allowed (don't use givenValidatorIsInstalled) - kernel.installModule( - 1, address(newValidator), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) - ); + kernel.installModule(1, address(newValidator), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector))); PackedUserOperation memory op = _createUserOpWithValidatorValidation(); op.callData = abi.encodePacked( @@ -1060,9 +985,7 @@ abstract contract Kernel_validateUserOp is BTTModifiers { givenValidationTypeIsValidator { // Manually install validator with execute selector allowed (don't use givenValidatorIsInstalled) - kernel.installModule( - 1, address(newValidator), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) - ); + kernel.installModule(1, address(newValidator), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector))); PackedUserOperation memory op = _createUserOpWithValidatorValidation(); op.callData = abi.encodePacked( @@ -1220,8 +1143,8 @@ abstract contract Kernel_validateUserOp is BTTModifiers { assertEq(validationData, 0, "Enable mode with valid signature should return 0"); // Verify validator was installed - assertEq( - kernel.validationInfo(validatorToIdentifier(newValidator)).hook, address(1), "Validator should be installed" + assertTrue( + kernel.validationInfo(validatorToIdentifier(newValidator)).installed, "Validator should be installed" ); } diff --git a/test/btt/KernelBTTFallback.t.sol b/test/btt/KernelBTTFallback.t.sol index 29cc9860..6b47e733 100644 --- a/test/btt/KernelBTTFallback.t.sol +++ b/test/btt/KernelBTTFallback.t.sol @@ -71,9 +71,5 @@ contract KernelBTT_Fallback_Test is Kernel_fallback { isMock = true; is7702 = false; isImmutable = false; - - // Install hook for hook tests - vm.prank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); } } diff --git a/test/btt/KernelFactory.t.sol b/test/btt/KernelFactory.t.sol index 7fb4fb5b..04b4e795 100644 --- a/test/btt/KernelFactory.t.sol +++ b/test/btt/KernelFactory.t.sol @@ -108,9 +108,8 @@ contract KernelFactory_Test is Test { Kernel account = factory.deploy(packages, 0); // Verify root validator is installed - assertEq( - account.validationInfo(validatorToIdentifier(IValidator(address(rootValidator)))).hook, - address(1), + assertTrue( + account.validationInfo(validatorToIdentifier(IValidator(address(rootValidator)))).installed, "Root validator should be installed" ); } diff --git a/test/btt/KernelUUPS.t.sol b/test/btt/KernelUUPS.t.sol index 42b4c3ba..3f4c1757 100644 --- a/test/btt/KernelUUPS.t.sol +++ b/test/btt/KernelUUPS.t.sol @@ -115,10 +115,9 @@ contract KernelUUPS_Test is Test { freshKernel.isModuleInstalled(1, address(newValidator), ""), "Validator should be installed after init" ); - // Verify it's set as root (hook should be address(1) = HOOK_MODULE_INSTALLED_NO_HOOK) - assertEq( - freshKernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).hook, - address(1), + // Verify it is installed and set as root + assertTrue( + freshKernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).installed, "Validator should be set as root" ); } @@ -159,9 +158,8 @@ contract KernelUUPS_Test is Test { // Verify the first package (validator) is installed as root assertTrue(freshKernel.isModuleInstalled(1, address(newValidator), ""), "First validator should be installed"); - assertEq( - freshKernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).hook, - address(1), + assertTrue( + freshKernel.validationInfo(validatorToIdentifier(IValidator(address(newValidator)))).installed, "First validator should be root" ); diff --git a/test/fuzz/KernelFuzz.t.sol b/test/fuzz/KernelFuzz.t.sol index abfc32cb..4c70962c 100644 --- a/test/fuzz/KernelFuzz.t.sol +++ b/test/fuzz/KernelFuzz.t.sol @@ -15,15 +15,12 @@ import { VALIDATION_TYPE_VALIDATOR, VALIDATION_TYPE_PERMISSION, VALIDATION_TYPE_ROOT, - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK, ERC1271_MAGICVALUE, ERC1271_INVALID, SIG_VALIDATION_FAILED_UINT, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, - MODULE_TYPE_HOOK, MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER } from "src/types/Constants.sol"; @@ -42,7 +39,6 @@ import { import {EntryPointLib} from "../utils/EntryPointLib.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; -import {MockHook} from "../mock/MockHook.sol"; import {MockFallback} from "../mock/MockFallback.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; @@ -55,7 +51,6 @@ contract KernelFuzz is Test { MockValidator private rootValidator; MockValidator private secondValidator; MockExecutor private executor; - MockHook private hook; MockFallback private fallbackModule; MockPolicy private policy; MockSigner private signer; @@ -72,14 +67,12 @@ contract KernelFuzz is Test { secondValidator = new MockValidator(); executor = new MockExecutor(); - hook = new MockHook(); fallbackModule = new MockFallback(); policy = new MockPolicy(); signer = new MockSigner(); - // Install hook, executor, and second validator + // Install executor and second validator vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); kernel.installModule(2, address(executor), abi.encode(hex"deadbeef", hex"")); kernel.installModule(1, address(secondValidator), abi.encode(hex"", hex"")); vm.stopPrank(); @@ -108,21 +101,10 @@ contract KernelFuzz is Test { assertTrue(kernel.isModuleInstalled(2, address(newExec), hex""), "new executor not installed"); } - /// @dev Installing a hook module never corrupts existing validators - function testFuzz_installHook_preservesValidators(bytes calldata moduleData) public { - assertTrue(kernel.isModuleInstalled(1, address(rootValidator), hex""), "root should be installed"); - assertTrue(kernel.isModuleInstalled(1, address(secondValidator), hex""), "second should be installed"); - MockHook newHook = new MockHook(); - vm.startPrank(address(ep)); - kernel.installModule(4, address(newHook), abi.encode(moduleData, hex"")); - vm.stopPrank(); - assertTrue(kernel.isModuleInstalled(1, address(rootValidator), hex""), "root corrupted"); - assertTrue(kernel.isModuleInstalled(1, address(secondValidator), hex""), "second corrupted"); - } - /// @dev Installing an invalid module type reverts function testFuzz_installModule_invalidType_reverts(uint256 moduleType) public { moduleType = bound(moduleType, 7, type(uint256).max); + vm.assume(moduleType != 11); MockValidator v = new MockValidator(); vm.startPrank(address(ep)); vm.expectRevert(NotImplemented.selector); @@ -176,70 +158,33 @@ contract KernelFuzz is Test { (success); } - /// @dev After installing a selector, calling it from EP succeeds (when hook is address(0)) - function testFuzz_fallback_installedSelector_fromEP_succeeds(uint256 selectorSeed) public { - bytes4 selector = MockFallback.testFunction.selector; - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(0)); - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - // Only EP can call selectors with hook == address(0) - vm.startPrank(address(ep)); - (bool success, bytes memory ret) = address(kernel).call(abi.encodePacked(selector)); - vm.stopPrank(); - assertTrue(success, "selector call from EP should succeed"); - } - - /// @dev After installing a selector with hook=address(0), non-EP callers get InvalidSelector - function testFuzz_fallback_hookZero_nonEP_reverts(address caller) public { - vm.assume(caller != address(ep)); - vm.assume(caller != address(kernel)); - - bytes4 selector = MockFallback.testFunction.selector; - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(0)); - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - vm.startPrank(caller); - vm.expectRevert(InvalidSelector.selector); - (bool success,) = address(kernel).call(abi.encodePacked(selector, bytes20(caller))); - (success); - vm.stopPrank(); - } - - /// @dev After installing a selector with a real hook, anyone can call it - function testFuzz_fallback_withHook_anyoneCalls(address caller) public { + /// @dev Installed fallback selectors are callable without a generic hook. + function testFuzz_fallback_installedSelector_anyoneCalls(address caller) public { vm.assume(caller != address(0)); bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(hook)); - vm.startPrank(address(ep)); + bytes memory internalData = abi.encodePacked(selector, callType); + vm.prank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - vm.startPrank(caller); - (bool success,) = address(kernel).call(abi.encodePacked(selector, bytes20(address(0x1234)))); - vm.stopPrank(); - assertTrue(success, "selector call with hook should succeed for anyone"); + vm.prank(caller); + (bool success,) = address(kernel).call(abi.encodePacked(selector)); + assertTrue(success, "installed selector should be callable"); } // ========= isValidSignature fuzz tests ========= /// @dev isValidSignature with root type and invalid sig returns ERC1271_INVALID function testFuzz_isValidSignature_invalidSig_returnsInvalid(bytes32 hash, bytes calldata sig) public view { - bytes memory fullSig = abi.encodePacked(bytes1(0), bytes1(0), sig); + bytes memory fullSig = abi.encodePacked(bytes1(0), sig); bytes4 ret = kernel.isValidSignature(hash, fullSig); assertEq(ret, ERC1271_INVALID, "should return invalid for arbitrary sig"); } /// @dev isValidSignature with invalid validation type (0x03) reverts function testFuzz_isValidSignature_invalidType_reverts(bytes32 hash, bytes calldata sig) public { - bytes memory fullSig = abi.encodePacked(bytes1(0), bytes1(0x03), sig); + bytes memory fullSig = abi.encodePacked(bytes1(0x03), sig); vm.expectRevert(InvalidValidationType.selector); kernel.isValidSignature(hash, fullSig); } @@ -249,7 +194,7 @@ contract KernelFuzz is Test { MockValidator uninstalled = new MockValidator(); bytes memory sig = hex"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefde"; - bytes memory fullSig = abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(uninstalled)), sig); + bytes memory fullSig = abi.encodePacked(bytes1(0x01), bytes20(address(uninstalled)), sig); vm.expectRevert(abi.encodeWithSelector(InvalidVid.selector, validatorToIdentifier(uninstalled))); kernel.isValidSignature(hash, fullSig); } @@ -434,10 +379,11 @@ contract KernelFuzz is Test { // ========= supportsModule fuzz tests ========= - /// @dev supportsModule returns true only for types 1-6 + /// @dev supportsModule returns true only for types 1, 2, 3, 5, 6, and 11 function testFuzz_supportsModule(uint256 moduleTypeId) public view { bool supported = kernel.supportsModule(moduleTypeId); - bool expected = moduleTypeId < 7 && moduleTypeId != 0; + bool expected = moduleTypeId == 1 || moduleTypeId == 2 || moduleTypeId == 3 || moduleTypeId == 5 + || moduleTypeId == 6 || moduleTypeId == 11; assertEq(supported, expected, "supportsModule mismatch"); } diff --git a/test/halmos/InitializeValidationHalmos.t.sol b/test/halmos/InitializeValidationHalmos.t.sol index c677e817..9989a50d 100644 --- a/test/halmos/InitializeValidationHalmos.t.sol +++ b/test/halmos/InitializeValidationHalmos.t.sol @@ -5,32 +5,11 @@ import {Test} from "forge-std/Test.sol"; import {SymTest} from "halmos-cheatcodes/SymTest.sol"; import {ValidationManager} from "src/core/ValidationManager.sol"; -import {IHook} from "src/interfaces/IERC7579Modules.sol"; import {ValidationStorage} from "src/types/Structs.sol"; import {ValidationId} from "src/types/Types.sol"; /// @notice Test harness exposing `_initializeValidation` and a nonce getter. -/// @dev The override of `_hookEnabled` lets us treat hook validity symbolically. contract InitializeValidationHarness is ValidationManager { - // Symbolic toggle used by the test to either allow any hook or simulate a - // disabled hook. Halmos can branch on this if the test exposes it. - bool public hookAlwaysEnabled; - - function setHookAlwaysEnabled(bool v) external { - hookAlwaysEnabled = v; - } - - function _hookEnabled( - IHook /*_hook*/ - ) - internal - view - override - returns (bool) - { - return hookAlwaysEnabled; - } - /// @notice Public wrapper around the internal `_initializeValidation`. function initializeValidation(ValidationId vId, bytes calldata _internalData) external { _initializeValidation(vId, _internalData); @@ -42,17 +21,10 @@ contract InitializeValidationHarness is ValidationManager { return $.vInfo[vId].nonce; } - /// @notice Direct read of `vInfo[vId].hook`. - function hookOf(ValidationId vId) external view returns (address) { + /// @notice Manually set installation state to satisfy the OccupiedValidationId precondition. + function sudoSetInstalled(ValidationId vId, bool installed) external { ValidationStorage storage $ = _validationStorage(); - return $.vInfo[vId].hook; - } - - /// @notice Manually clear hook to satisfy the OccupiedValidationId precondition - /// without going through full install/uninstall. Used by setUp only. - function sudoSetHook(ValidationId vId, address h) external { - ValidationStorage storage $ = _validationStorage(); - $.vInfo[vId].hook = h; + $.vInfo[vId].installed = installed; } /// @notice Manually seed the pre-state nonce so Halmos can explore arbitrary @@ -93,8 +65,8 @@ contract InitializeValidationHalmos is SymTest, Test { ValidationId vId = ValidationId.wrap(bytes21(svm.createBytes32("vId"))); uint32 preNonce = uint32(svm.createUint(32, "preNonce")); - // Precondition: slot is empty (not OccupiedValidationId). - harness.sudoSetHook(vId, address(0)); + // Precondition: validation is not installed. + harness.sudoSetInstalled(vId, false); harness.sudoSetNonce(vId, preNonce); // Avoid 32-bit overflow in the post-condition arithmetic. @@ -118,19 +90,14 @@ contract InitializeValidationHalmos is SymTest, Test { ValidationId vId = ValidationId.wrap(bytes21(svm.createBytes32("vId"))); uint32 preNonce = uint32(svm.createUint(32, "preNonce")); - // Precondition: slot is empty. - harness.sudoSetHook(vId, address(0)); + // Precondition: validation is not installed. + harness.sudoSetInstalled(vId, false); harness.sudoSetNonce(vId, preNonce); // Avoid 32-bit overflow. vm.assume(preNonce < type(uint32).max); - // Make the hook check pass for any non-sentinel hook address by - // enabling the symbolic-hook bypass. - harness.setHookAlwaysEnabled(true); - - // _internalData = 20-byte hook || 4-byte selector - bytes memory internalData = svm.createBytes(24, "internalData"); + bytes memory internalData = svm.createBytes(4, "internalData"); harness.initializeValidation(vId, internalData); uint32 postNonce = harness.nonceOf(vId); diff --git a/test/halmos/KernelAccessControlHalmos.t.sol b/test/halmos/KernelAccessControlHalmos.t.sol index 95355e98..b4233a2a 100644 --- a/test/halmos/KernelAccessControlHalmos.t.sol +++ b/test/halmos/KernelAccessControlHalmos.t.sol @@ -38,7 +38,7 @@ contract KernelAccessControlHalmos is SymTest, Test { address caller = address(uint160(uint256(keccak256("caller")))); vm.startPrank(caller); vm.expectRevert(Unauthorized.selector); - kernel.installModule(4, address(new MockExecutor()), abi.encode(hex"", hex"")); + kernel.installModule(2, address(new MockExecutor()), abi.encode(hex"", hex"")); vm.stopPrank(); } diff --git a/test/halmos/KernelFallbackHalmos.t.sol b/test/halmos/KernelFallbackHalmos.t.sol index ac1420bb..7502cc85 100644 --- a/test/halmos/KernelFallbackHalmos.t.sol +++ b/test/halmos/KernelFallbackHalmos.t.sol @@ -9,11 +9,10 @@ import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; import {KernelFactory} from "src/KernelFactory.sol"; import {Install, SelectorConfig} from "src/types/Structs.sol"; import {CallType} from "src/types/Types.sol"; -import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, HOOK_MODULE_NOT_INSTALLED} from "src/types/Constants.sol"; +import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL} from "src/types/Constants.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockFallback} from "../mock/MockFallback.sol"; -import {MockHook} from "../mock/MockHook.sol"; /// @title KernelFallbackHalmos /// @notice Halmos proofs that _fallback can never route to an uninstalled module @@ -21,7 +20,6 @@ contract KernelFallbackHalmos is SymTest, Test { Kernel private kernel; IEntryPoint private ep; MockFallback private fallbackModule; - MockHook private hook; function setUp() external { ep = EntryPointLib.deploy(); @@ -33,10 +31,6 @@ contract KernelFallbackHalmos is SymTest, Test { pkgs[0] = Install({moduleType: 1, module: address(rootValidator), moduleData: hex"", internalData: hex""}); kernel = factory.deploy(pkgs, 0); fallbackModule = new MockFallback(); - hook = new MockHook(); - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - vm.stopPrank(); } /// @notice Prove that calling a selector with no installed fallback always reverts @@ -54,7 +48,7 @@ contract KernelFallbackHalmos is SymTest, Test { function check_FallbackAfterUninstallReverts() external { bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(hook)); + bytes memory internalData = abi.encodePacked(selector, callType); // Install then uninstall vm.startPrank(address(ep)); @@ -74,7 +68,7 @@ contract KernelFallbackHalmos is SymTest, Test { function check_FallbackInstallSetsCorrectConfig() external { bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(hook)); + bytes memory internalData = abi.encodePacked(selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); @@ -85,28 +79,11 @@ contract KernelFallbackHalmos is SymTest, Test { assertEq(CallType.unwrap(cfg.callType), callType); } - /// @notice Prove that fallback with hook=0 and non-EP caller reverts - function check_FallbackHookZeroNonEPReverts() external { - bytes4 selector = MockFallback.testFunction.selector; - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(0)); - - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - address caller = address(0xBEEF); - vm.startPrank(caller); - (bool success,) = address(kernel).call(abi.encodePacked(selector, bytes20(caller))); - vm.stopPrank(); - assertFalse(success, "hook=0 with non-EP caller should revert"); - } - - /// @notice Prove fallback with hook=address(1) allows any caller - function check_FallbackNoHookAllowsAnyCaller() external { + /// @notice Prove an installed fallback allows any caller + function check_FallbackAllowsAnyCaller() external { bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(1)); + bytes memory internalData = abi.encodePacked(selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); diff --git a/test/halmos/KernelHookBracketingHalmos.t.sol b/test/halmos/KernelHookBracketingHalmos.t.sol deleted file mode 100644 index d5f7ec82..00000000 --- a/test/halmos/KernelHookBracketingHalmos.t.sol +++ /dev/null @@ -1,151 +0,0 @@ -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {SymTest} from "halmos-cheatcodes/SymTest.sol"; -import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; -import {Kernel} from "src/Kernel.sol"; -import {KernelUUPS} from "src/KernelUUPS.sol"; -import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; -import {KernelFactory} from "src/KernelFactory.sol"; -import {Install, SelectorConfig} from "src/types/Structs.sol"; -import {CallType} from "src/types/Types.sol"; -import {CALLTYPE_SINGLE} from "src/types/Constants.sol"; -import {EntryPointLib} from "../utils/EntryPointLib.sol"; -import {MockValidator} from "../mock/MockValidator.sol"; -import {MockFallback} from "../mock/MockFallback.sol"; -import {MockHook} from "../mock/MockHook.sol"; -import {MockExecutor} from "../mock/MockExecutor.sol"; -import {LibERC7579} from "solady/accounts/LibERC7579.sol"; - -/// @title KernelHookBracketingHalmos -/// @notice Halmos proofs that hook pre/post always bracket execution -contract KernelHookBracketingHalmos is SymTest, Test { - Kernel private kernel; - IEntryPoint private ep; - MockHook private hook; - MockFallback private fallbackModule; - MockExecutor private executor; - - function setUp() external { - ep = EntryPointLib.deploy(); - KernelUUPS uups = new KernelUUPS(ep); - KernelImmutableECDSA immutableEcdsa = new KernelImmutableECDSA(ep); - KernelFactory factory = new KernelFactory(uups, immutableEcdsa); - MockValidator rootValidator = new MockValidator(); - Install[] memory pkgs = new Install[](1); - pkgs[0] = Install({moduleType: 1, module: address(rootValidator), moduleData: hex"", internalData: hex""}); - kernel = factory.deploy(pkgs, 0); - hook = new MockHook(); - fallbackModule = new MockFallback(); - executor = new MockExecutor(); - - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - kernel.installModule(2, address(executor), abi.encode(hex"deadbeef", abi.encodePacked(address(hook)))); - vm.stopPrank(); - } - - /// @notice Prove that fallback with hook calls preCheck before execution - function check_FallbackHookPreCheckCalled() external { - bytes4 selector = MockFallback.testFunction.selector; - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(hook)); - - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - // Reset hook state - hook.resetState(); - assertFalse(hook.preHookCalled()); - assertFalse(hook.postHookCalled()); - - // Call the selector - address caller = address(0xBEEF); - vm.startPrank(caller); - (bool success,) = address(kernel).call(abi.encodePacked(selector, bytes20(address(0x1234)))); - vm.stopPrank(); - assertTrue(success); - - // Verify both pre and post hooks were called - assertTrue(hook.preHookCalled(), "preCheck not called"); - assertTrue(hook.postHookCalled(), "postCheck not called"); - } - - /// @notice Prove that if preCheck reverts, execution does not proceed and postCheck is not called - function check_FallbackHookPreCheckRevertBlocksExecution() external { - bytes4 selector = MockFallback.testFunction.selector; - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(hook)); - - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - // Set hook to revert on preCheck - hook.resetState(); - hook.setRevertOnPreHook(true); - - (bool success,) = address(kernel).call(abi.encodePacked(selector, bytes20(address(0x1234)))); - assertFalse(success, "call should revert when preCheck reverts"); - } - - /// @notice Prove that if postCheck reverts, the call reverts after execution - function check_FallbackHookPostCheckRevertRevertsCall() external { - bytes4 selector = MockFallback.testFunction.selector; - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(hook)); - - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - // Set hook to revert on postCheck - hook.resetState(); - hook.setRevertOnPostHook(true); - - (bool success,) = address(kernel).call(abi.encodePacked(selector, bytes20(address(0x1234)))); - assertFalse(success, "call should revert when postCheck reverts"); - } - - /// @notice Prove executor with hook calls pre and post - function check_ExecutorHookBracketing() external { - hook.resetState(); - assertFalse(hook.preHookCalled()); - assertFalse(hook.postHookCalled()); - - bytes32 mode = bytes32( - abi.encodePacked(LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_TRY, bytes4(0), bytes4(0), bytes22(0)) - ); - bytes memory executionData = abi.encodePacked(address(kernel), uint256(0), abi.encodeCall(Kernel.accountId, ())); - - vm.prank(address(executor)); - kernel.executeFromExecutor(mode, executionData); - - assertTrue(hook.preHookCalled(), "executor preCheck not called"); - assertTrue(hook.postHookCalled(), "executor postCheck not called"); - } - - /// @notice Prove executor with hook=address(1) does NOT call pre/post - function check_ExecutorNoHookSkipsChecks() external { - MockExecutor noHookExecutor = new MockExecutor(); - vm.startPrank(address(ep)); - // Install executor with no hook (empty internalData defaults to address(1)) - kernel.installModule(2, address(noHookExecutor), abi.encode(hex"deadbeef", hex"")); - vm.stopPrank(); - - hook.resetState(); - - bytes32 mode = bytes32( - abi.encodePacked(LibERC7579.CALLTYPE_SINGLE, LibERC7579.EXECTYPE_TRY, bytes4(0), bytes4(0), bytes22(0)) - ); - bytes memory executionData = abi.encodePacked(address(kernel), uint256(0), abi.encodeCall(Kernel.accountId, ())); - - vm.prank(address(noHookExecutor)); - kernel.executeFromExecutor(mode, executionData); - - // Since hook is address(1) (no hook), preCheck/postCheck should NOT be called on the hook module - assertFalse(hook.preHookCalled(), "pre hook should not be called for no-hook executor"); - assertFalse(hook.postHookCalled(), "post hook should not be called for no-hook executor"); - } -} diff --git a/test/halmos/KernelModuleIdempotencyHalmos.t.sol b/test/halmos/KernelModuleIdempotencyHalmos.t.sol index 0a7016b8..272a5dd2 100644 --- a/test/halmos/KernelModuleIdempotencyHalmos.t.sol +++ b/test/halmos/KernelModuleIdempotencyHalmos.t.sol @@ -10,7 +10,7 @@ import {KernelFactory} from "src/KernelFactory.sol"; import {Install, ValidationInfo, SelectorConfig, ExecutorConfig} from "src/types/Structs.sol"; import {ValidationId, CallType, PermissionId} from "src/types/Types.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; -import {CALLTYPE_SINGLE, HOOK_MODULE_NOT_INSTALLED, HOOK_MODULE_INSTALLED_NO_HOOK} from "src/types/Constants.sol"; +import {CALLTYPE_SINGLE} from "src/types/Constants.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; @@ -18,7 +18,6 @@ import {MockHook} from "../mock/MockHook.sol"; import {MockFallback} from "../mock/MockFallback.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; -import {IHook} from "src/interfaces/IERC7579Modules.sol"; /// @title KernelModuleIdempotencyHalmos /// @notice Halmos proofs that install+uninstall is idempotent for all module types @@ -67,19 +66,17 @@ contract KernelModuleIdempotencyHalmos is SymTest, Test { assertFalse(kernel.isModuleInstalled(2, address(e), hex"")); ExecutorConfig memory cfg = kernel.executorConfig(address(e)); - assertEq(address(cfg.hook), HOOK_MODULE_NOT_INSTALLED); + assertFalse(cfg.installed); } /// @notice Prove selector install+uninstall returns to not-installed state function check_SelectorInstallUninstallIdempotent() external { MockFallback f = new MockFallback(); - MockHook h = new MockHook(); bytes4 selector = MockFallback.testFunction.selector; vm.startPrank(address(ep)); - kernel.installModule(4, address(h), abi.encode(hex"", hex"")); bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - kernel.installModule(3, address(f), abi.encode(hex"deadbeef", abi.encodePacked(selector, callType, address(h)))); + kernel.installModule(3, address(f), abi.encode(hex"deadbeef", abi.encodePacked(selector, callType))); assertTrue(kernel.isModuleInstalled(3, address(f), abi.encodePacked(selector))); kernel.uninstallModule(3, address(f), abi.encode(hex"", abi.encodePacked(selector))); @@ -91,21 +88,6 @@ contract KernelModuleIdempotencyHalmos is SymTest, Test { assertEq(CallType.unwrap(cfg.callType), bytes1(0)); } - /// @notice Prove hook install+uninstall returns to not-installed state - function check_HookInstallUninstallIdempotent() external { - MockHook h = new MockHook(); - assertFalse(kernel.isModuleInstalled(4, address(h), hex"")); - - vm.startPrank(address(ep)); - kernel.installModule(4, address(h), abi.encode(hex"", hex"")); - assertTrue(kernel.isModuleInstalled(4, address(h), hex"")); - - kernel.uninstallModule(4, address(h), abi.encode(hex"", hex"")); - vm.stopPrank(); - - assertFalse(kernel.isModuleInstalled(4, address(h), hex"")); - } - /// @notice Prove permission (policy+signer) install+uninstall returns to not-installed state function check_PermissionInstallUninstallIdempotent() external { MockPolicy p = new MockPolicy(); @@ -135,7 +117,7 @@ contract KernelModuleIdempotencyHalmos is SymTest, Test { assertFalse(kernel.isModuleInstalled(6, address(s), abi.encodePacked(permId))); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertEq(vInfo.hook, HOOK_MODULE_NOT_INSTALLED); + assertFalse(vInfo.installed); assertEq(vInfo.signer, address(0)); assertEq(vInfo.policies.length, 0); } diff --git a/test/halmos/KernelSelectorHalmos.t.sol b/test/halmos/KernelSelectorHalmos.t.sol index 116dc17c..69981b8e 100644 --- a/test/halmos/KernelSelectorHalmos.t.sol +++ b/test/halmos/KernelSelectorHalmos.t.sol @@ -10,17 +10,14 @@ import {KernelFactory} from "src/KernelFactory.sol"; import {Install, SelectorConfig} from "src/types/Structs.sol"; import {CallType} from "src/types/Types.sol"; import {CALLTYPE_SINGLE} from "src/types/Constants.sol"; -import {InvalidSelector} from "src/types/Error.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockFallback} from "../mock/MockFallback.sol"; -import {MockHook} from "../mock/MockHook.sol"; contract KernelSelectorHalmos is SymTest, Test { Kernel private kernel; IEntryPoint private ep; MockFallback private fallbackModule; - MockHook private hook; function setUp() external { ep = EntryPointLib.deploy(); @@ -32,16 +29,12 @@ contract KernelSelectorHalmos is SymTest, Test { pkgs[0] = Install({moduleType: 1, module: address(rootValidator), moduleData: hex"", internalData: hex""}); kernel = factory.deploy(pkgs, 0); fallbackModule = new MockFallback(); - hook = new MockHook(); - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - vm.stopPrank(); } function checkSelectorInstallUninstall() external { bytes4 selector = bytes4(svm.createBytes(4, "selector")); bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(0)); + bytes memory internalData = abi.encodePacked(selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); @@ -64,7 +57,7 @@ contract KernelSelectorHalmos is SymTest, Test { function checkSelectorCallTypeAppendsSender() external { bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType, address(0)); + bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); vm.stopPrank(); @@ -82,7 +75,7 @@ contract KernelSelectorHalmos is SymTest, Test { function checkSelectorDelegatecallDoesNotAppendSender() external { bytes1 callType = bytes1(uint8(0xFF)); - bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType, address(0)); + bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); vm.stopPrank(); @@ -97,51 +90,4 @@ contract KernelSelectorHalmos is SymTest, Test { address decoded = abi.decode(ret, (address)); assertEq(decoded, payloadAddr); } - - function checkSelectorHookRevertsOnPreHook() external { - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType, address(hook)); - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - hook.setRevertOnPreHook(true); - bytes memory data = abi.encodePacked( - MockFallback.getCaller.selector, bytes20(address(0x1111111111111111111111111111111111111111)) - ); - vm.expectRevert(MockHook.PreHookReverted.selector); - (bool success,) = address(kernel).call(data); - (success); - } - - function checkSelectorHookRevertsOnPostHook() external { - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType, address(hook)); - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - hook.setRevertOnPostHook(true); - bytes memory data = abi.encodePacked( - MockFallback.getCaller.selector, bytes20(address(0x1111111111111111111111111111111111111111)) - ); - vm.expectRevert(MockHook.PostHookReverted.selector); - (bool success,) = address(kernel).call(data); - (success); - } - - function checkSelectorHookZeroRequiresEntryPoint() external { - bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(MockFallback.getCaller.selector, callType, address(0)); - vm.startPrank(address(ep)); - kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); - vm.stopPrank(); - - bytes memory data = abi.encodePacked( - MockFallback.getCaller.selector, bytes20(address(0x1111111111111111111111111111111111111111)) - ); - vm.expectRevert(InvalidSelector.selector); - (bool success,) = address(kernel).call(data); - (success); - } } diff --git a/test/halmos/KernelStorageSlotHalmos.t.sol b/test/halmos/KernelStorageSlotHalmos.t.sol index 435d10f2..e5b3a00a 100644 --- a/test/halmos/KernelStorageSlotHalmos.t.sol +++ b/test/halmos/KernelStorageSlotHalmos.t.sol @@ -6,7 +6,6 @@ import { SELECTOR_MANAGER_STORAGE_SLOT, MODULE_MANAGER_STORAGE_SLOT, EXECUTOR_MANAGER_STORAGE_SLOT, - HOOK_MANAGER_STORAGE_SLOT, VALIDATION_MANAGER_STORAGE_SLOT, ERC1967_IMPLEMENTATION_SLOT } from "src/types/Constants.sol"; @@ -14,19 +13,18 @@ import { /// @title KernelStorageSlotHalmos /// @notice Halmos proofs that ERC-7201 storage slots do not collide contract KernelStorageSlotHalmos is SymTest, Test { - /// @notice Prove all 6 storage slots are pairwise distinct + /// @notice Prove all storage slots are pairwise distinct function check_AllStorageSlotsDistinct() external pure { - bytes32[6] memory slots = [ + bytes32[5] memory slots = [ SELECTOR_MANAGER_STORAGE_SLOT, MODULE_MANAGER_STORAGE_SLOT, EXECUTOR_MANAGER_STORAGE_SLOT, - HOOK_MANAGER_STORAGE_SLOT, VALIDATION_MANAGER_STORAGE_SLOT, ERC1967_IMPLEMENTATION_SLOT ]; - for (uint256 i = 0; i < 6; i++) { - for (uint256 j = i + 1; j < 6; j++) { + for (uint256 i = 0; i < 5; i++) { + for (uint256 j = i + 1; j < 5; j++) { assertTrue(slots[i] != slots[j], "storage slot collision"); } } @@ -50,12 +48,6 @@ contract KernelStorageSlotHalmos is SymTest, Test { assertEq(EXECUTOR_MANAGER_STORAGE_SLOT, expected); } - /// @notice Prove HOOK_MANAGER_STORAGE_SLOT matches keccak256('kernel.v4.hook') - 1 - function check_HookManagerSlotDerivation() external pure { - bytes32 expected = bytes32(uint256(keccak256("kernel.v4.hook")) - 1); - assertEq(HOOK_MANAGER_STORAGE_SLOT, expected); - } - /// @notice Prove VALIDATION_MANAGER_STORAGE_SLOT matches keccak256('kernel.v4.validation') - 1 function check_ValidationManagerSlotDerivation() external pure { bytes32 expected = bytes32(uint256(keccak256("kernel.v4.validation")) - 1); diff --git a/test/halmos/TopLevelExecuteAcHalmos.t.sol b/test/halmos/TopLevelExecuteAcHalmos.t.sol index 23deb4b2..975cea08 100644 --- a/test/halmos/TopLevelExecuteAcHalmos.t.sol +++ b/test/halmos/TopLevelExecuteAcHalmos.t.sol @@ -12,11 +12,9 @@ import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; /// Properties under test: /// 1. `execute(bytes32, bytes)` is gated by `_onlyEntryPointOrSelf` — it MUST /// revert for every caller that is not `ENTRYPOINT` and not `address(this)`. -/// 2. `executeFromExecutor(bytes32, bytes)` is gated by the `executorHook` -/// modifier, which loads `_executorConfig(IExecutor(msg.sender)).hook` and -/// requires it to be non-zero. So the function MUST revert for every caller -/// whose executor config slot is still zero (i.e. anyone except an installed -/// executor module). +/// 2. `executeFromExecutor(bytes32, bytes)` requires the caller's executor +/// configuration to be installed. It MUST revert for every caller except an +/// installed executor module. /// /// We deploy `KernelUUPS` directly (no ERC1967 proxy) — neither `execute` nor /// `executeFromExecutor` carries Solady's `onlyProxy` modifier, so the @@ -32,10 +30,7 @@ contract TopLevelExecuteAcHalmos is SymTest, Test { entryPoint = makeAddr("EntryPoint"); kernel = new KernelUUPS(IEntryPoint(entryPoint)); installedExecutor = address(0xdeadbeef); - // Install one executor module so the success-path test for - // `executeFromExecutor` has a caller whose config hook is non-zero. - // `_installExecutor` defaults the hook to `address(1)` when no hook is - // supplied in `internalData`. + // Install one executor module for the executeFromExecutor success path. vm.startPrank(entryPoint); kernel.installModule(2, installedExecutor, abi.encode(hex"", "")); vm.stopPrank(); @@ -80,13 +75,10 @@ contract TopLevelExecuteAcHalmos is SymTest, Test { // --- executeFromExecutor(bytes32, bytes) ----------------------------------- - /// @notice Any caller whose executor config still holds the zero hook (i.e. - /// not installed as an executor module) must be rejected by the - /// `executorHook` modifier on `executeFromExecutor`. + /// @notice Any caller not installed as an executor module must be rejected. function checkExecuteFromExecutorRevertsForUninstalledExecutor() external { address caller = svm.createAddress("caller"); - // The only installed executor is `installedExecutor`. Every other address - // has `_executorConfig(...).hook == address(0)`, so it must be rejected. + // The only installed executor is `installedExecutor`. vm.assume(caller != installedExecutor); bytes memory executionData = _validExecutionData(); @@ -98,8 +90,7 @@ contract TopLevelExecuteAcHalmos is SymTest, Test { assertFalse(ok, "uninstalled executor must not be allowed to executeFromExecutor"); } - /// @notice An installed executor module must clear the `executorHook` gate - /// on `executeFromExecutor`. + /// @notice An installed executor module may call `executeFromExecutor`. function checkExecuteFromExecutorSucceedsForInstalledExecutor() external { bytes memory executionData = _validExecutionData(); bytes32 mode = bytes32(0); diff --git a/test/integration/KernelIntegration.t.sol b/test/integration/KernelIntegration.t.sol index 4bb034ba..83b640e5 100644 --- a/test/integration/KernelIntegration.t.sol +++ b/test/integration/KernelIntegration.t.sol @@ -412,59 +412,4 @@ contract KernelIntegrationTest is Test { // ----------------------------------------------------------------------- // 4.7 Executor with hook: full chain through EntryPoint // ----------------------------------------------------------------------- - - function test_executorWithHookThroughEntryPoint() public { - Kernel kernel = _deployKernel(); - - // Install MockHook (module type 4) first - MockHook hook = new MockHook(); - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"deadbeef", "")); - vm.stopPrank(); - - assertTrue(kernel.isModuleInstalled(4, address(hook), hex""), "hook should be installed"); - - // Install MockExecutor (module type 2) WITH the hook address in internalData - MockExecutor mockExecutor = new MockExecutor(); - vm.startPrank(address(ep)); - kernel.installModule(2, address(mockExecutor), abi.encode(hex"deadbeef", abi.encodePacked(address(hook)))); - vm.stopPrank(); - - assertTrue(kernel.isModuleInstalled(2, address(mockExecutor), hex""), "executor should be installed"); - assertFalse(hook.preHookCalled(), "preHook should not have been called yet"); - assertFalse(hook.postHookCalled(), "postHook should not have been called yet"); - - // Full chain: EP -> Kernel.execute -> MockExecutor.sudoDoExec - // -> Kernel.executeFromExecutor (executorHook fires) -> callee.foo() - bytes memory innerCallData = abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector); - bytes memory sudoDoExecCall = - abi.encodeWithSelector(MockExecutor.sudoDoExec.selector, address(kernel), bytes32(0), innerCallData); - - PackedUserOperation memory op = PackedUserOperation({ - sender: address(kernel), - nonce: _rootNonce(address(kernel)), - initCode: hex"", - callData: abi.encodeWithSelector( - Kernel.execute.selector, bytes32(0), abi.encodePacked(address(mockExecutor), uint256(0), sudoDoExecCall) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), - preVerificationGas: 1_000_000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - - rootValidator.sudoSetSuccess(true); - - PackedUserOperation[] memory opsArr = new PackedUserOperation[](1); - opsArr[0] = op; - _handleOps(opsArr); - - // Verify hook was invoked - assertTrue(hook.preHookCalled(), "hook preCheck should have been called"); - assertTrue(hook.postHookCalled(), "hook postCheck should have been called"); - - // Verify callee was called through the full chain - assertEq(callee.bar(), 1, "callee.foo() should have been called through executor+hook chain"); - } } diff --git a/test/integration/KernelIntegrationEdgeCases.t.sol b/test/integration/KernelIntegrationEdgeCases.t.sol index e5d1a581..b9538267 100644 --- a/test/integration/KernelIntegrationEdgeCases.t.sol +++ b/test/integration/KernelIntegrationEdgeCases.t.sol @@ -181,7 +181,7 @@ contract KernelIntegrationEdgeCasesTest is Test { moduleType: 1, module: address(newValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); // Root validator signs the install digest @@ -236,9 +236,9 @@ contract KernelIntegrationEdgeCasesTest is Test { // Install all 3 via entrypoint with execute selector access vm.startPrank(address(ep)); - kernel.installModule(1, address(v1), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector))); - kernel.installModule(1, address(v2), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector))); - kernel.installModule(1, address(v3), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector))); + kernel.installModule(1, address(v1), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector))); + kernel.installModule(1, address(v2), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector))); + kernel.installModule(1, address(v3), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector))); vm.stopPrank(); assertTrue(kernel.isModuleInstalled(1, address(v1), hex""), "v1 should be installed"); @@ -364,7 +364,7 @@ contract KernelIntegrationEdgeCasesTest is Test { IERC7579Account.installModule.selector, uint256(3), address(fb), - abi.encode(hex"deadbeef", abi.encodePacked(fbSelector, bytes1(0x00), bytes20(address(1)))) + abi.encode(hex"deadbeef", abi.encodePacked(fbSelector, bytes1(0x00))) ) ) ), @@ -384,7 +384,7 @@ contract KernelIntegrationEdgeCasesTest is Test { kernel.isModuleInstalled(3, address(fb), abi.encodePacked(fbSelector)), "fallback should be installed" ); - // Call the fallback externally (hook=address(1) means anyone can call) + // Call the installed fallback externally address alice = makeAddr("Alice"); vm.prank(alice); (bool success, bytes memory ret) = address(kernel).call(abi.encodeWithSelector(fbSelector, uint256(5))); @@ -441,99 +441,6 @@ contract KernelIntegrationEdgeCasesTest is Test { /// @notice Hook reverts mid-execution, verify state is fully rolled back /// (no partial effects from the execution). - function test_hookFailurePropagation() public { - Kernel kernel = _deployKernel(); - - // Install hook - MockHook hook = new MockHook(); - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"deadbeef", "")); - vm.stopPrank(); - - // Install a new validator WITH the hook - MockValidator hookedValidator = new MockValidator(); - vm.startPrank(address(ep)); - kernel.installModule( - 1, address(hookedValidator), abi.encode(hex"", abi.encodePacked(address(hook), Kernel.execute.selector)) - ); - vm.stopPrank(); - - // Verify initial state - assertEq(callee.bar(), 0, "callee.bar should start at 0"); - assertFalse(hook.preHookCalled(), "preHook should not have been called yet"); - assertFalse(hook.postHookCalled(), "postHook should not have been called yet"); - - // Set postHook to revert -- execution will succeed but postCheck will revert - // This causes the entire executeUserOp inner call to revert, rolling back callee.foo() - hook.setRevertOnPostHook(true); - - // Build UserOp with the hooked validator - // Must use executeUserOp as outer selector since the validator has a hook - uint256 nonce = - _encodeNonce(false, false, false, bytes1(0x01), bytes20(address(hookedValidator)), address(kernel)); - PackedUserOperation memory op = PackedUserOperation({ - sender: address(kernel), - nonce: nonce, - initCode: hex"", - callData: abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), - preVerificationGas: 1_000_000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - hookedValidator.sudoSetSuccess(true); - - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = op; - - // The EntryPoint will catch the inner revert -- UserOp validation passes, - // but the execution phase (executeUserOp) reverts, so no state changes from execute. - _handleOps(ops); - - // callee.foo() was rolled back because postHook reverted - assertEq(callee.bar(), 0, "callee.bar should still be 0 -- hook revert rolled back execution"); - - // Now fix the hook and try again -- should work - hook.setRevertOnPostHook(false); - hook.resetState(); - - uint256 nonce2 = - _encodeNonce(false, false, false, bytes1(0x01), bytes20(address(hookedValidator)), address(kernel)); - PackedUserOperation memory op2 = PackedUserOperation({ - sender: address(kernel), - nonce: nonce2, - initCode: hex"", - callData: abi.encodePacked( - Kernel.executeUserOp.selector, - abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), - preVerificationGas: 1_000_000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - hookedValidator.sudoSetSuccess(true); - - PackedUserOperation[] memory ops2 = new PackedUserOperation[](1); - ops2[0] = op2; - _handleOps(ops2); - - assertEq(callee.bar(), 1, "callee.bar should be 1 after hook fixed"); - } - // ----------------------------------------------------------------------- // Test 6: Cross-chain replay test // ----------------------------------------------------------------------- @@ -603,133 +510,4 @@ contract KernelIntegrationEdgeCasesTest is Test { /// @notice UUPS upgrade via UserOp, then verify all previously installed modules /// still function correctly. - function test_upgradeAndModulePersistence() public { - Kernel kernel = _deployKernel(); - rootValidator.sudoSetSuccess(true); - - // Install additional modules before upgrade - MockValidator extraValidator = new MockValidator(); - MockExecutor executor = new MockExecutor(); - MockHook hook = new MockHook(); - MockFallback fb = new MockFallback(); - bytes4 fbSelector = MockFallback.testFunction.selector; - - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"deadbeef", "")); - kernel.installModule( - 1, address(extraValidator), abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) - ); - kernel.installModule(2, address(executor), abi.encode(hex"deadbeef", "")); - kernel.installModule( - 3, address(fb), abi.encode(hex"deadbeef", abi.encodePacked(fbSelector, bytes1(0x00), bytes20(address(1)))) - ); - vm.stopPrank(); - - // Verify all modules are installed before upgrade - assertTrue(kernel.isModuleInstalled(1, address(rootValidator), hex""), "root validator before upgrade"); - assertTrue(kernel.isModuleInstalled(1, address(extraValidator), hex""), "extra validator before upgrade"); - assertTrue(kernel.isModuleInstalled(2, address(executor), hex""), "executor before upgrade"); - assertTrue(kernel.isModuleInstalled(4, address(hook), hex""), "hook before upgrade"); - assertTrue(kernel.isModuleInstalled(3, address(fb), abi.encodePacked(fbSelector)), "fallback before upgrade"); - - // Record current implementation - bytes32 implBefore = vm.load(address(kernel), ERC1967_IMPLEMENTATION_SLOT); - assertEq(address(uint160(uint256(implBefore))), address(uups), "initial impl should be uups"); - - // Deploy a new implementation and upgrade via UserOp - KernelUUPS newImpl = new KernelUUPS(ep); - { - uint256 nonce = _rootNonce(address(kernel)); - PackedUserOperation memory upgradeOp = PackedUserOperation({ - sender: address(kernel), - nonce: nonce, - initCode: hex"", - callData: abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked( - address(kernel), - uint256(0), - abi.encodeWithSelector(UUPSUpgradeable.upgradeToAndCall.selector, address(newImpl), hex"") - ) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), - preVerificationGas: 1_000_000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = upgradeOp; - _handleOps(ops); - } - - // Verify upgrade happened - bytes32 implAfter = vm.load(address(kernel), ERC1967_IMPLEMENTATION_SLOT); - assertEq(address(uint160(uint256(implAfter))), address(newImpl), "impl should be newImpl after upgrade"); - assertEq(kernel.accountId(), "kernel.v0.4", "accountId should still work"); - - // Verify ALL modules still installed after upgrade - assertTrue(kernel.isModuleInstalled(1, address(rootValidator), hex""), "root validator after upgrade"); - assertTrue(kernel.isModuleInstalled(1, address(extraValidator), hex""), "extra validator after upgrade"); - assertTrue(kernel.isModuleInstalled(2, address(executor), hex""), "executor after upgrade"); - assertTrue(kernel.isModuleInstalled(4, address(hook), hex""), "hook after upgrade"); - assertTrue(kernel.isModuleInstalled(3, address(fb), abi.encodePacked(fbSelector)), "fallback after upgrade"); - - // Verify root validator still works via UserOp - { - uint256 nonce = _rootNonce(address(kernel)); - PackedUserOperation memory op = _buildCallFooOp(address(kernel), nonce); - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = op; - _handleOps(ops); - } - assertEq(callee.bar(), 1, "root UserOp should work after upgrade"); - - // Verify extra validator still works - extraValidator.sudoSetSuccess(true); - { - uint256 nonce = - _encodeNonce(false, false, false, bytes1(0x01), bytes20(address(extraValidator)), address(kernel)); - PackedUserOperation memory op = _buildCallFooOp(address(kernel), nonce); - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = op; - _handleOps(ops); - } - assertEq(callee.bar(), 2, "extra validator UserOp should work after upgrade"); - - // Verify executor still works after upgrade - { - bytes memory innerCallData = abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector); - bytes memory sudoDoExecCall = - abi.encodeWithSelector(MockExecutor.sudoDoExec.selector, address(kernel), bytes32(0), innerCallData); - - uint256 nonce = _rootNonce(address(kernel)); - PackedUserOperation memory op = PackedUserOperation({ - sender: address(kernel), - nonce: nonce, - initCode: hex"", - callData: abi.encodeWithSelector( - Kernel.execute.selector, bytes32(0), abi.encodePacked(address(executor), uint256(0), sudoDoExecCall) - ), - accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), - preVerificationGas: 1_000_000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = op; - _handleOps(ops); - } - assertEq(callee.bar(), 3, "executor callback should work after upgrade"); - - // Verify fallback still works after upgrade - address alice = makeAddr("Alice"); - vm.prank(alice); - (bool success, bytes memory ret) = address(kernel).call(abi.encodeWithSelector(fbSelector)); - assertTrue(success, "fallback call should succeed after upgrade"); - uint256 fbResult = abi.decode(ret, (uint256)); - assertEq(fbResult, 42, "testFunction() should still return 42 after upgrade"); - } } diff --git a/test/invariant/KernelInvariant.t.sol b/test/invariant/KernelInvariant.t.sol index 75c61c84..cc2e8ed6 100644 --- a/test/invariant/KernelInvariant.t.sol +++ b/test/invariant/KernelInvariant.t.sol @@ -14,8 +14,7 @@ import { CALLTYPE_DELEGATECALL, CALLTYPE_SINGLE, VALIDATION_TYPE_VALIDATOR, - VALIDATION_TYPE_PERMISSION, - HOOK_MODULE_NOT_INSTALLED + VALIDATION_TYPE_PERMISSION } from "src/types/Constants.sol"; import {Unauthorized} from "src/types/Error.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; @@ -57,7 +56,7 @@ contract KernelInvariantHandler is Test { mapping(uint192 => uint64) public ghostNonce; uint64 public ghostValidNonceFrom; - // Ghost variables for executor hook enforcement + // Ghost variables for executor installation enforcement uint256 public uninstalledExecutorCallCount; uint256 public uninstalledExecutorRevertCount; @@ -128,19 +127,11 @@ contract KernelInvariantHandler is Test { } function installHook(uint256 index) external { - MockHook hook = hooks[index % hooks.length]; - vm.startPrank(address(ep)); - kernel.installModule(4, address(hook), abi.encode(hex"", hex"")); - vm.stopPrank(); - hookInstalled[address(hook)] = true; + return; } function uninstallHook(uint256 index) external { - MockHook hook = hooks[index % hooks.length]; - vm.startPrank(address(ep)); - kernel.uninstallModule(4, address(hook), abi.encode(hex"", hex"")); - vm.stopPrank(); - hookInstalled[address(hook)] = false; + return; } function validatorCount() external view returns (uint256) { @@ -183,7 +174,7 @@ contract KernelInvariantHandler is Test { bytes4 selector = selectors[selectorIndex % selectors.length]; MockFallback target = fallbacks[targetIndex % fallbacks.length]; bytes1 callType = delegatecall ? CallType.unwrap(CALLTYPE_DELEGATECALL) : CallType.unwrap(CALLTYPE_SINGLE); - bytes memory internalData = abi.encodePacked(selector, callType, address(0)); + bytes memory internalData = abi.encodePacked(selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(target), abi.encode(hex"deadbeef", internalData)); vm.stopPrank(); @@ -380,7 +371,7 @@ contract KernelInvariant is StdInvariant, Test { if (validator != address(rootValidator)) { ValidationId vId = validatorToIdentifier(MockValidator(validator)); bool installed = handler.validatorInstalled(validator); - assertEq(kernel.validationInfo(vId).hook != address(0), installed, "validator hook mismatch"); + assertEq(kernel.validationInfo(vId).installed, installed, "validator hook mismatch"); } } } @@ -395,17 +386,7 @@ contract KernelInvariant is StdInvariant, Test { "executor install state mismatch" ); bool installed = handler.executorInstalled(executor); - assertEq(address(kernel.executorConfig(executor).hook) != address(0), installed, "executor hook mismatch"); - } - } - - function invariant_hook_install_state_matches_handler() external { - uint256 count = handler.hookCount(); - for (uint256 i = 0; i < count; i++) { - address hook = address(handler.hooks(i)); - assertEq( - kernel.isModuleInstalled(4, hook, hex""), handler.hookInstalled(hook), "hook install state mismatch" - ); + assertEq(kernel.executorConfig(executor).installed, installed, "executor installation mismatch"); } } @@ -464,16 +445,16 @@ contract KernelInvariant is StdInvariant, Test { } // --- 3.5: Root-always-installed invariant --- - // If root != bytes21(0), then validationInfo(root).hook > address(0) + // If root != bytes21(0), then validationInfo(root).installed is true function invariant_root_always_installed() external view { ValidationId rootId = kernel.root(); if (ValidationId.unwrap(rootId) != bytes21(0)) { ValidationInfo memory vInfo = kernel.validationInfo(rootId); - assertTrue(vInfo.hook > address(0), "root validation hook is zero (not installed)"); + assertTrue(vInfo.installed, "root validation is not installed"); } } - // --- 3.6: Executor hook enforcement invariant --- + // --- 3.6: Executor installation enforcement invariant --- // Uninstalled executors always revert when calling executeFromExecutor function invariant_uninstalled_executor_always_reverts() external view { assertEq( @@ -483,56 +464,6 @@ contract KernelInvariant is StdInvariant, Test { ); } - // --- 3.7: Hook consistency invariant --- - // Every installed validator/executor with a real hook (> address(1)) must have that hook enabled - function invariant_hook_consistency() external { - // Check validators - uint256 vCount = handler.validatorCount(); - for (uint256 i = 0; i < vCount; i++) { - address validator = address(handler.validators(i)); - if (handler.validatorInstalled(validator)) { - ValidationId vId = validatorToIdentifier(MockValidator(validator)); - ValidationInfo memory vInfo = kernel.validationInfo(vId); - address hookAddr = vInfo.hook; - if (hookAddr > address(1)) { - assertTrue( - kernel.isModuleInstalled(4, hookAddr, hex""), "validator hook not installed as hook module" - ); - } - } - } - - // Check executors - uint256 eCount = handler.executorCount(); - for (uint256 i = 0; i < eCount; i++) { - address executor = address(handler.executors(i)); - if (handler.executorInstalled(executor)) { - address hookAddr = address(kernel.executorConfig(executor).hook); - if (hookAddr > address(1)) { - assertTrue( - kernel.isModuleInstalled(4, hookAddr, hex""), "executor hook not installed as hook module" - ); - } - } - } - - // Check selectors - uint256 sCount = handler.selectorCount(); - for (uint256 i = 0; i < sCount; i++) { - bytes4 selector = handler.selectors(i); - address target = handler.selectorTarget(selector); - if (target != address(0)) { - SelectorConfig memory cfg = kernel.selectorConfig(selector); - address hookAddr = address(cfg.hook); - if (hookAddr > address(1)) { - assertTrue( - kernel.isModuleInstalled(4, hookAddr, hex""), "selector hook not installed as hook module" - ); - } - } - } - } - // --- 3.8: Module installation symmetry invariant --- // Ghost variables track install/uninstall pairs; after uninstall, module must not be installed function invariant_module_install_symmetry() external { @@ -553,15 +484,6 @@ contract KernelInvariant is StdInvariant, Test { bool kernelInstalled = kernel.isModuleInstalled(2, executor, hex""); assertEq(ghostInstalled, kernelInstalled, "executor symmetry violated"); } - - // Hooks: same check - uint256 hCount = handler.hookCount(); - for (uint256 i = 0; i < hCount; i++) { - address hook = address(handler.hooks(i)); - bool ghostInstalled = handler.hookInstalled(hook); - bool kernelInstalled = kernel.isModuleInstalled(4, hook, hex""); - assertEq(ghostInstalled, kernelInstalled, "hook symmetry violated"); - } } // --- 3.9: Nonce monotonicity invariant --- @@ -594,16 +516,15 @@ contract KernelInvariant is StdInvariant, Test { // --- 3.11: Storage slot isolation invariant --- // ERC-7201 storage slots for different managers do not collide function invariant_storage_slot_isolation() external pure { - // All five storage slots must be distinct - bytes32[5] memory slots = [ + // All four active storage slots must be distinct. + bytes32[4] memory slots = [ bytes32(0x550d18e77e0b3e646dcc27a9961c73d7867a7c5f6c2c65424629353cdc97dcc0), // SELECTOR_MANAGER bytes32(0x9bc558e75ed0a57385e96d6b87fd2864d462eed29668be6fed742168fd90ab0f), // MODULE_MANAGER bytes32(0xc98f19fae81314cbf0302e1e3c0554f60c259fab8e2d5d392893489d40eb0045), // EXECUTOR_MANAGER - bytes32(0x5419def70c6ad54339f14ca6da31808409bec8ff0f178491c5b59f0d8276d4d3), // HOOK_MANAGER bytes32(0xded5d420c407eac3c615e6abe13ab4a0bd7173e5045ea543765b46f0df6e260c) // VALIDATION_MANAGER ]; - for (uint256 i = 0; i < 5; i++) { - for (uint256 j = i + 1; j < 5; j++) { + for (uint256 i = 0; i < 4; i++) { + for (uint256 j = i + 1; j < 4; j++) { assertTrue(slots[i] != slots[j], "storage slot collision detected"); } } diff --git a/test/mock/MockHook.sol b/test/mock/MockHook.sol index 76e0ab5f..32ca5f9e 100644 --- a/test/mock/MockHook.sol +++ b/test/mock/MockHook.sol @@ -1,15 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IHook} from "src/interfaces/IERC7579Modules.sol"; +import {IScopedExecutionHook} from "src/interfaces/IERC7579Modules.sol"; +import {MODULE_TYPE_SCOPED_EXECUTION_HOOK} from "src/types/Constants.sol"; -contract MockHook is IHook { +contract MockHook is IScopedExecutionHook { error PreHookReverted(); error PostHookReverted(); mapping(address => bytes) public data; mapping(address => bytes) public preHookData; mapping(address => bytes) public postHookData; + mapping(address => bytes32) public preCheckId; + mapping(address => bytes32) public postCheckId; bool public installCalled; // State for BTT testing @@ -28,14 +31,14 @@ contract MockHook is IHook { } function isModuleType(uint256 moduleTypeId) external pure override returns (bool) { - return moduleTypeId == 1; + return moduleTypeId == MODULE_TYPE_SCOPED_EXECUTION_HOOK; } function isInitialized(address smartAccount) external view override returns (bool) { return data[smartAccount].length > 0; } - function preCheck(address msgSender, uint256, bytes calldata msgData) + function preCheck(bytes32 id, address msgSender, uint256, bytes calldata msgData) external payable override @@ -45,15 +48,17 @@ contract MockHook is IHook { revert PreHookReverted(); } _preHookCalled = true; + preCheckId[msg.sender] = id; preHookData[msg.sender] = abi.encodePacked(msgSender, msgData); return data[msg.sender]; } - function postCheck(bytes calldata hookData) external payable override { + function postCheck(bytes32 id, bytes calldata hookData) external payable override { if (_revertOnPostHook) { revert PostHookReverted(); } _postHookCalled = true; + postCheckId[msg.sender] = id; postHookData[msg.sender] = hookData; } diff --git a/test/mock/MockRevertingHook.sol b/test/mock/MockRevertingHook.sol index 2d2f68c0..4853948c 100644 --- a/test/mock/MockRevertingHook.sol +++ b/test/mock/MockRevertingHook.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IHook} from "src/interfaces/IERC7579Modules.sol"; +import {IScopedExecutionHook} from "src/interfaces/IERC7579Modules.sol"; -contract MockRevertingHook is IHook { +contract MockRevertingHook is IScopedExecutionHook { error InstallFailed(); function onInstall(bytes calldata) external payable { @@ -13,16 +13,16 @@ contract MockRevertingHook is IHook { function onUninstall(bytes calldata) external payable {} function isModuleType(uint256 typeId) external pure returns (bool) { - return typeId == 4; + return typeId == 11; } function isInitialized(address) external pure returns (bool) { return false; } - function preCheck(address, uint256, bytes calldata) external payable returns (bytes memory) { + function preCheck(bytes32, address, uint256, bytes calldata) external payable returns (bytes memory) { return hex""; } - function postCheck(bytes calldata) external payable {} + function postCheck(bytes32, bytes calldata) external payable {} } diff --git a/test/mock/MockValidator.sol b/test/mock/MockValidator.sol index 9f7572c7..13393c59 100644 --- a/test/mock/MockValidator.sol +++ b/test/mock/MockValidator.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import {IValidator, IHook} from "src/interfaces/IERC7579Modules.sol"; +import {IValidator} from "src/interfaces/IERC7579Modules.sol"; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; /// @notice Mock validator that returns empty data (misconfigured validator) @@ -30,7 +30,7 @@ contract MockEmptyReturnValidator is IValidator { } } -contract MockValidator is IValidator, IHook { +contract MockValidator is IValidator { mapping(address => bool) public initialized; bool public success; uint256 public count; @@ -39,13 +39,8 @@ contract MockValidator is IValidator, IHook { mapping(address => bytes) public validatorData; mapping(bytes32 => bool) public validSig; - bool public isHook; uint256 public customValidationData; - function setHook(bool _isHook) external { - isHook = _isHook; - } - function sudoSetSuccess(bool _success) external { success = _success; } @@ -102,20 +97,4 @@ contract MockValidator is IValidator, IHook { return 0xffffffff; } } - - function validateSignatureWithDataWithSender(address, bytes32, bytes calldata, bytes calldata) - external - view - returns (bool) - { - return success; - } - - function preCheck(address, uint256, bytes calldata) external payable returns (bytes memory) { - return hex""; - } - - function postCheck(bytes calldata) external payable { - return; - } } diff --git a/test/unit/GasBenchmark.t.sol b/test/unit/GasBenchmark.t.sol index 741e8302..f2a71689 100644 --- a/test/unit/GasBenchmark.t.sol +++ b/test/unit/GasBenchmark.t.sol @@ -9,10 +9,9 @@ import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; import {KernelFactory} from "src/KernelFactory.sol"; import {MockFallback} from "../mock/MockFallback.sol"; import {MockValidator} from "../mock/MockValidator.sol"; -import {MockHook} from "../mock/MockHook.sol"; import {Install} from "src/types/Structs.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; -import {CALLTYPE_SINGLE, MODULE_TYPE_VALIDATOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK} from "src/types/Constants.sol"; +import {CALLTYPE_SINGLE, MODULE_TYPE_VALIDATOR, MODULE_TYPE_FALLBACK} from "src/types/Constants.sol"; /// @title Gas Benchmark Tests /// @notice Focused gas benchmarks for specific optimization paths @@ -22,7 +21,6 @@ contract GasBenchmarkTest is Test { Kernel kernel; MockValidator validator; MockFallback mockFallback; - MockHook mockHook; function setUp() public { ep = EntryPointLib.deploy(); @@ -32,29 +30,19 @@ contract GasBenchmarkTest is Test { factory = new KernelFactory(uups, immutableEcdsa); validator = new MockValidator(); mockFallback = new MockFallback(); - mockHook = new MockHook(); // Deploy kernel via factory Install[] memory packages = new Install[](1); packages[0] = Install({ - moduleType: MODULE_TYPE_VALIDATOR, - module: address(validator), - moduleData: "", - internalData: abi.encodePacked(address(0)) + moduleType: MODULE_TYPE_VALIDATOR, module: address(validator), moduleData: "", internalData: hex"" }); kernel = Kernel(payable(factory.deploy(packages, 0))); vm.deal(address(kernel), 10 ether); - // Install hook vm.startPrank(address(ep)); - kernel.installModule( - MODULE_TYPE_HOOK, address(mockHook), abi.encode(abi.encodePacked(hex""), abi.encodePacked(hex"")) - ); - // Install fallback with hook - // internalData format: selector(4) + callType(1) + hook(20) - bytes memory internalData = - abi.encodePacked(MockFallback.fallbackFunction.selector, CALLTYPE_SINGLE, address(mockHook)); + // internalData format: selector(4) + callType(1) + bytes memory internalData = abi.encodePacked(MockFallback.fallbackFunction.selector, CALLTYPE_SINGLE); kernel.installModule(MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", internalData)); vm.stopPrank(); } @@ -87,11 +75,11 @@ contract GasBenchmarkTest is Test { vm.stopPrank(); } - /// @notice Benchmark: fallback with hook exercises _fallback() hook check path - function test_gasBenchmark_fallbackWithHook() public { + /// @notice Benchmark: installed fallback routing + function test_gasBenchmark_fallback() public { uint256 gasBefore = gasleft(); MockFallback(address(kernel)).fallbackFunction(5); uint256 gasAfter = gasleft(); - emit log_named_uint("Gas used for fallback with hook", gasBefore - gasAfter); + emit log_named_uint("Gas used for fallback", gasBefore - gasAfter); } } diff --git a/test/unit/KernelCoverage.t.sol b/test/unit/KernelCoverage.t.sol index 247fc0c1..df9c7fb8 100644 --- a/test/unit/KernelCoverage.t.sol +++ b/test/unit/KernelCoverage.t.sol @@ -15,7 +15,6 @@ import {ValidationId, PermissionId, CallType} from "src/types/Types.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; -import {MockHook} from "../mock/MockHook.sol"; import {MockFallback} from "../mock/MockFallback.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; import {MockCallee} from "../mock/MockCallee.sol"; @@ -38,24 +37,18 @@ import { InvalidSignature, OccupiedValidationId, CannotUninstallRoot, - NotInstalled, NotExecutor, ModuleInstallFailed, PermissionInstallNotFinished, InvalidPermissionInstall, InstallSignatureVerificationFailed, - LastSignatureShouldBeSigner, - InvalidValidator, InvalidSigner, ImplementationNotDeployed } from "src/types/Error.sol"; import { - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, - MODULE_TYPE_HOOK, MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER, CALLTYPE_SINGLE, @@ -70,7 +63,7 @@ import { SELECTOR_MANAGER_STORAGE_SLOT } from "src/types/Constants.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; -import {IValidator, IHook, IExecutor} from "src/interfaces/IERC7579Modules.sol"; +import {IValidator, IExecutor} from "src/interfaces/IERC7579Modules.sol"; import {IERC7579Account} from "src/interfaces/IERC7579Account.sol"; import {LibERC7579} from "solady/accounts/LibERC7579.sol"; @@ -84,7 +77,6 @@ contract KernelCoverageTest is Test { MockValidator newValidator; MockPolicy policy; MockSigner signer; - MockHook hook; MockFallback mockFallback; MockExecutor mockExecutor; MockCallee callee; @@ -101,7 +93,6 @@ contract KernelCoverageTest is Test { newValidator = new MockValidator(); policy = new MockPolicy(); signer = new MockSigner(); - hook = new MockHook(); mockFallback = new MockFallback(); mockExecutor = new MockExecutor(); callee = new MockCallee(); @@ -130,10 +121,14 @@ contract KernelCoverageTest is Test { assertFalse(kernel.supportsModule(0), "Module type 0 should not be supported"); } - function test_supportsModule_WhenTypeIs1Through6_ShouldReturnTrue() public view { - for (uint256 i = 1; i <= 6; i++) { - assertTrue(kernel.supportsModule(i), "Module types 1-6 should be supported"); - } + function test_supportsModule_WhenTypeIsSupported_ShouldReturnTrue() public view { + assertTrue(kernel.supportsModule(1)); + assertTrue(kernel.supportsModule(2)); + assertTrue(kernel.supportsModule(3)); + assertFalse(kernel.supportsModule(4)); + assertTrue(kernel.supportsModule(5)); + assertTrue(kernel.supportsModule(6)); + assertTrue(kernel.supportsModule(11)); } function test_supportsModule_WhenTypeIs7_ShouldReturnFalse() public view { @@ -216,9 +211,7 @@ contract KernelCoverageTest is Test { bytes4 testSel = MockFallback.testFunction.selector; vm.prank(address(ep)); kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00), address(1))) + MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00))) ); assertTrue( kernel.isModuleInstalled(MODULE_TYPE_FALLBACK, address(mockFallback), abi.encodePacked(testSel)), @@ -235,16 +228,6 @@ contract KernelCoverageTest is Test { ); } - function test_isModuleInstalled_WhenHookInstalled_ShouldReturnTrue() public { - vm.prank(address(ep)); - kernel.installModule(MODULE_TYPE_HOOK, address(hook), abi.encode(hex"", hex"")); - assertTrue(kernel.isModuleInstalled(MODULE_TYPE_HOOK, address(hook), hex""), "Hook should be installed"); - } - - function test_isModuleInstalled_WhenHookNotInstalled_ShouldReturnFalse() public view { - assertFalse(kernel.isModuleInstalled(MODULE_TYPE_HOOK, address(hook), hex""), "Hook should not be installed"); - } - function test_isModuleInstalled_WhenPolicyInstalled_ShouldReturnTrue() public { // Install policy + signer for a permission Install[] memory pkgs = new Install[](2); @@ -381,7 +364,7 @@ contract KernelCoverageTest is Test { } // ========================================================================= - // InvalidRootValidation — setRoot with executor/hook type module + // InvalidRootValidation — setRoot with executor module // ========================================================================= function test_setRoot_WhenModuleTypeIsExecutor_ShouldRevertWithInvalidRootValidation() public { @@ -417,7 +400,7 @@ contract KernelCoverageTest is Test { // Verify old root is uninstalled ValidationId oldVid = validatorToIdentifier(IValidator(address(rootValidator))); ValidationInfo memory info = kernel.validationInfo(oldVid); - assertEq(info.hook, HOOK_MODULE_NOT_INSTALLED, "Old root validator should be uninstalled"); + assertFalse(info.installed, "Old root validator should be uninstalled"); // Verify new root is set ValidationId newRoot = kernel.root(); @@ -464,7 +447,7 @@ contract KernelCoverageTest is Test { kernel.setRoot(newPkgs, true, abi.encode(uninstallDataArr)); ValidationInfo memory info = kernel.validationInfo(permVid); - assertEq(info.hook, HOOK_MODULE_NOT_INSTALLED, "Old permission root should be uninstalled"); + assertFalse(info.installed, "Old permission root should be uninstalled"); vm.stopPrank(); } @@ -601,9 +584,7 @@ contract KernelCoverageTest is Test { // Install a validator with specific selectors allowed vm.prank(address(ep)); kernel.installModule( - MODULE_TYPE_VALIDATOR, - address(newValidator), - abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) + MODULE_TYPE_VALIDATOR, address(newValidator), abi.encode(hex"", abi.encodePacked(Kernel.execute.selector)) ); newValidator.sudoSetSuccess(true); @@ -690,30 +671,11 @@ contract KernelCoverageTest is Test { MockFallback(address(kernel)).testFunction(); } - function test_fallback_WhenHookNotInstalledAndCallerNotEntryPoint_ShouldRevertWithInvalidSelector() public { - // Install fallback with hook=address(0) (HOOK_MODULE_NOT_INSTALLED) - bytes4 testSel = MockFallback.testFunction.selector; - vm.prank(address(ep)); - kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00), address(0))) - ); - - // Non-entrypoint caller should be rejected - address random = makeAddr("Random"); - vm.prank(random); - vm.expectRevert(InvalidSelector.selector); - MockFallback(address(kernel)).testFunction(); - } - - function test_fallback_WhenHookNotInstalledAndCallerIsEntryPoint_ShouldSucceed() public { + function test_fallback_WhenInstalled_ShouldSucceed() public { bytes4 testSel = MockFallback.testFunction.selector; vm.prank(address(ep)); kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00), address(0))) + MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00))) ); // Entrypoint caller should succeed @@ -730,12 +692,10 @@ contract KernelCoverageTest is Test { bytes4 testSel = MockFallback.testFunction.selector; vm.prank(address(ep)); kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF), address(1))) + MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF))) ); - // Anyone can call (hook is address(1) = INSTALLED_NO_HOOK) + // Anyone can call an installed fallback uint256 result = MockFallback(address(kernel)).testFunction(); assertEq(result, 42, "Delegatecall fallback should return 42"); } @@ -897,28 +857,6 @@ contract KernelCoverageTest is Test { kernel.installModule(MODULE_TYPE_VALIDATOR, revertingValidator, abi.encode(hex"", hex"")); } - // ========================================================================= - // NotInstalled — validator/executor with uninstalled hook - // ========================================================================= - - function test_installValidator_WhenHookNotInstalled_ShouldRevertWithNotInstalled() public { - address fakeHook = makeAddr("FakeHook"); - vm.prank(address(ep)); - vm.expectRevert(NotInstalled.selector); - kernel.installModule( - MODULE_TYPE_VALIDATOR, - address(newValidator), - abi.encode(hex"", abi.encodePacked(fakeHook, Kernel.execute.selector)) - ); - } - - function test_installExecutor_WhenHookNotInstalled_ShouldRevertWithNotInstalled() public { - address fakeHook = makeAddr("FakeHook"); - vm.prank(address(ep)); - vm.expectRevert(NotInstalled.selector); - kernel.installModule(MODULE_TYPE_EXECUTOR, address(mockExecutor), abi.encode(hex"", abi.encodePacked(fakeHook))); - } - // ========================================================================= // NotExecutor / Unauthorized — executeFromExecutor from non-executor // ========================================================================= @@ -1028,33 +966,6 @@ contract KernelCoverageTest is Test { kernel.installModule(pkgs); } - // ========================================================================= - // Fallback with real hook — preHook and postHook execution - // ========================================================================= - - function test_fallback_WhenHookInstalled_ShouldCallPreAndPostHook() public { - vm.startPrank(address(ep)); - - // Install hook - kernel.installModule(MODULE_TYPE_HOOK, address(hook), abi.encode(hex"", hex"")); - - // Install fallback with real hook, using fallbackFunction which modifies state - // (testFunction is pure, triggering staticcall which fails when hook writes state) - bytes4 fbSel = MockFallback.fallbackFunction.selector; - kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(fbSel, bytes1(0x00), address(hook))) - ); - vm.stopPrank(); - - // Call fallback — must be a non-view call so hooks can write state - uint256 result = MockFallback(address(kernel)).fallbackFunction(5); - assertEq(result, 25, "Fallback should return 5*5=25"); - assertTrue(hook.preHookCalled(), "Pre-hook should have been called"); - assertTrue(hook.postHookCalled(), "Post-hook should have been called"); - } - // ========================================================================= // ERC-1271 isValidSignature — root validation path // ========================================================================= @@ -1064,7 +975,6 @@ contract KernelCoverageTest is Test { rootValidator.sudoSetValidSig(hex"aabb"); bytes memory signature = abi.encodePacked( - bytes1(0x00), // mode: standard bytes1(0x00), // type: root hex"aabb" // validator signature ); @@ -1078,7 +988,6 @@ contract KernelCoverageTest is Test { // Don't set valid sig => validator will reject bytes memory signature = abi.encodePacked( - bytes1(0x00), // mode: standard bytes1(0x00), // type: root hex"ccdd" ); diff --git a/test/unit/ModuleManagerCoverage.t.sol b/test/unit/ModuleManagerCoverage.t.sol index 446a7455..e2186b0e 100644 --- a/test/unit/ModuleManagerCoverage.t.sol +++ b/test/unit/ModuleManagerCoverage.t.sol @@ -27,7 +27,6 @@ import { NotImplemented, Unauthorized, ModuleInstallFailed, - NotInstalled, InstallSignatureVerificationFailed, InvalidSigner, ImplementationNotDeployed, @@ -41,12 +40,9 @@ import { InvalidSelectorTarget } from "src/types/Error.sol"; import { - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, - MODULE_TYPE_HOOK, MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER, CALLTYPE_SINGLE, @@ -59,7 +55,7 @@ import { VALIDATION_TYPE_PERMISSION } from "src/types/Constants.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; -import {IValidator, IHook, IExecutor} from "src/interfaces/IERC7579Modules.sol"; +import {IValidator, IExecutor} from "src/interfaces/IERC7579Modules.sol"; import {IERC7579Account} from "src/interfaces/IERC7579Account.sol"; import {LibERC7579} from "solady/accounts/LibERC7579.sol"; @@ -151,7 +147,6 @@ contract ModuleManagerCoverageTest is Test { newValidator.sudoSetValidSig(hex"aabb"); bytes memory signature = abi.encodePacked( - bytes1(0x00), // mode: standard bytes1(0x01), // type: validator address(newValidator), hex"aabb" @@ -168,7 +163,6 @@ contract ModuleManagerCoverageTest is Test { bytes32 testHash = keccak256("validator test"); bytes memory signature = abi.encodePacked( - bytes1(0x00), bytes1(0x01), address(newValidator), hex"ccdd" // not valid @@ -212,7 +206,6 @@ contract ModuleManagerCoverageTest is Test { signatures[1] = hex"beef"; bytes memory signature = abi.encodePacked( - bytes1(0x00), // mode: standard bytes1(0x02), // type: permission permissionId, abi.encode(signatures) @@ -229,7 +222,6 @@ contract ModuleManagerCoverageTest is Test { function test_isValidSignature_WhenInvalidValidationType_ShouldRevertWithInvalidValidationType() public { bytes32 testHash = keccak256("test"); bytes memory signature = abi.encodePacked( - bytes1(0x00), bytes1(0x03), // invalid type hex"00000000000000000000000000000000000000000000" ); @@ -265,7 +257,7 @@ contract ModuleManagerCoverageTest is Test { bytes[] memory signatures = new bytes[](1); signatures[0] = hex"dead"; - bytes memory signature = abi.encodePacked(bytes1(0x00), bytes1(0x02), permissionId, abi.encode(signatures)); + bytes memory signature = abi.encodePacked(bytes1(0x02), permissionId, abi.encode(signatures)); vm.expectRevert(InvalidSignature.selector); kernel.isValidSignature(testHash, signature); @@ -306,9 +298,7 @@ contract ModuleManagerCoverageTest is Test { vm.prank(address(ep)); vm.expectRevert(ModuleInstallFailed.selector); kernel.installModule( - MODULE_TYPE_FALLBACK, - revertingFallback, - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00), address(1))) + MODULE_TYPE_FALLBACK, revertingFallback, abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00))) ); } @@ -319,32 +309,13 @@ contract ModuleManagerCoverageTest is Test { // CALLTYPE_DELEGATECALL does not require onInstall success vm.prank(address(ep)); kernel.installModule( - MODULE_TYPE_FALLBACK, - revertingFallback, - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF), address(1))) + MODULE_TYPE_FALLBACK, revertingFallback, abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF))) ); SelectorConfig memory config = kernel.selectorConfig(testSel); assertEq(config.target, revertingFallback, "Fallback should be installed despite onInstall failure"); } - // ========================================================================= - // NotInstalled — install fallback with non-installed hook - // ========================================================================= - - function test_installFallback_WhenHookNotInstalled_ShouldRevertWithNotInstalled() public { - address fakeHook = makeAddr("NotAHook"); - bytes4 testSel = MockFallback.testFunction.selector; - - vm.prank(address(ep)); - vm.expectRevert(NotInstalled.selector); - kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00), fakeHook)) - ); - } - // ========================================================================= // InvalidSelectorTarget — fallback install with zero-address module // ========================================================================= @@ -359,7 +330,7 @@ contract ModuleManagerCoverageTest is Test { vm.prank(address(ep)); vm.expectRevert(InvalidSelectorTarget.selector); kernel.installModule( - MODULE_TYPE_FALLBACK, address(0), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF), address(1))) + MODULE_TYPE_FALLBACK, address(0), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF))) ); } @@ -372,9 +343,7 @@ contract ModuleManagerCoverageTest is Test { vm.startPrank(address(ep)); kernel.installModule( - MODULE_TYPE_FALLBACK, - address(mockFallback), - abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00), address(1))) + MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0x00))) ); SelectorConfig memory config = kernel.selectorConfig(testSel); @@ -391,17 +360,6 @@ contract ModuleManagerCoverageTest is Test { // ========================================================================= // Uninstall hook - // ========================================================================= - - function test_uninstallHook_ShouldDisableHook() public { - vm.startPrank(address(ep)); - kernel.installModule(MODULE_TYPE_HOOK, address(hook), abi.encode(hex"", hex"")); - assertTrue(kernel.isModuleInstalled(MODULE_TYPE_HOOK, address(hook), hex""), "Hook should be installed"); - - kernel.uninstallModule(MODULE_TYPE_HOOK, address(hook), abi.encode(hex"", hex"")); - assertFalse(kernel.isModuleInstalled(MODULE_TYPE_HOOK, address(hook), hex""), "Hook should be uninstalled"); - vm.stopPrank(); - } // ========================================================================= // Uninstall executor @@ -565,7 +523,7 @@ contract ModuleManagerCoverageTest is Test { moduleType: MODULE_TYPE_VALIDATOR, module: address(enabledValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); // Compute enable signature digest (non-replayable) @@ -610,66 +568,6 @@ contract ModuleManagerCoverageTest is Test { // ========================================================================= // UserOp with validation hook — executeUserOp path - // ========================================================================= - - function test_processUserOp_WhenNonRootValidationWithHook_ShouldRouteViaExecuteUserOp() public { - vm.startPrank(address(ep)); - - // Install hook - kernel.installModule(MODULE_TYPE_HOOK, address(hook), abi.encode(hex"", hex"")); - - // Install validator with hook and allowed selector = execute - kernel.installModule( - MODULE_TYPE_VALIDATOR, - address(newValidator), - abi.encode(hex"", abi.encodePacked(address(hook), Kernel.execute.selector)) - ); - vm.stopPrank(); - - newValidator.sudoSetSuccess(true); - - uint192 key = uint192( - bytes24( - abi.encodePacked( - uint8(0x00), - bytes1(0x01), // validator type - bytes20(address(newValidator)), - bytes2(0x0000) - ) - ) - ); - uint256 nonce = ep.getNonce(address(kernel), key); - - // When validation has a hook, _processUserOp requires: - // callData[0:4] == executeUserOp.selector - // callData[4:8] == an allowed selector (Kernel.execute.selector) - // The inner call data (execute call) is placed at callData[4:] - bytes memory executeCallData = abi.encodeWithSelector( - Kernel.execute.selector, bytes32(0), abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector) - ); - // Wrap: executeUserOp.selector ++ executeCallData (raw, not ABI-encoded as bytes) - bytes memory outerCallData = abi.encodePacked(Kernel.executeUserOp.selector, executeCallData); - - PackedUserOperation[] memory ops = new PackedUserOperation[](1); - ops[0] = PackedUserOperation({ - sender: address(kernel), - nonce: nonce, - initCode: hex"", - callData: outerCallData, - accountGasLimits: bytes32(abi.encodePacked(uint128(2000000), uint128(2000000))), - preVerificationGas: 1000000, - gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), - paymasterAndData: hex"", - signature: hex"" - }); - - vm.prank(beneficiary, beneficiary); - ep.handleOps(ops, beneficiary); - - assertEq(callee.bar(), 1, "Hooked validator path should succeed"); - assertTrue(hook.preHookCalled(), "Hook preCheck should have been called"); - assertTrue(hook.postHookCalled(), "Hook postCheck should have been called"); - } // ========================================================================= // intersectValidationData in _processUserOp — enable returns time-bounded result @@ -686,7 +584,7 @@ contract ModuleManagerCoverageTest is Test { moduleType: MODULE_TYPE_VALIDATOR, module: address(enabledValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); // Set rootValidator with custom validation data (time bounds) @@ -735,25 +633,8 @@ contract ModuleManagerCoverageTest is Test { assertEq(callee.bar(), 1, "Time-bounded enable should succeed within bounds"); } - // ========================================================================= - // Hook install with empty internalData // ========================================================================= - function test_installHook_WhenOnInstallFailsAndEmptyInternalData_ShouldRevertWithModuleInstallFailed() public { - address revertingHook = address(new RevertingOnInstallHook()); - vm.prank(address(ep)); - vm.expectRevert(ModuleInstallFailed.selector); - kernel.installModule(MODULE_TYPE_HOOK, revertingHook, abi.encode(hex"", hex"")); - } - - function test_installHook_WhenOnInstallFailsAndNonEmptyInternalData_ShouldSucceed() public { - address revertingHook = address(new RevertingOnInstallHook()); - vm.prank(address(ep)); - // Non-empty internalData skips the require(_installSuccess) check - kernel.installModule(MODULE_TYPE_HOOK, revertingHook, abi.encode(hex"", hex"01")); - assertTrue(kernel.isModuleInstalled(MODULE_TYPE_HOOK, revertingHook, hex""), "Hook should be installed"); - } - // ========================================================================= // Executor install without internalData // ========================================================================= @@ -763,60 +644,10 @@ contract ModuleManagerCoverageTest is Test { kernel.installModule(MODULE_TYPE_EXECUTOR, address(mockExecutor), abi.encode(hex"", hex"")); ExecutorConfig memory config = kernel.executorConfig(address(mockExecutor)); - assertEq(address(config.hook), HOOK_MODULE_INSTALLED_NO_HOOK, "Executor should have no hook (address(1))"); - } - - function test_installExecutor_WhenInternalDataIsAddress0_ShouldInstallWithNoHook() public { - vm.prank(address(ep)); - kernel.installModule( - MODULE_TYPE_EXECUTOR, address(mockExecutor), abi.encode(hex"", abi.encodePacked(address(0))) - ); - - ExecutorConfig memory config = kernel.executorConfig(address(mockExecutor)); - assertEq( - address(config.hook), HOOK_MODULE_INSTALLED_NO_HOOK, "Hook address(0) should be converted to address(1)" - ); - } - - function test_installExecutor_WhenInternalDataIsAddress1_ShouldInstallWithNoHook() public { - vm.prank(address(ep)); - kernel.installModule( - MODULE_TYPE_EXECUTOR, address(mockExecutor), abi.encode(hex"", abi.encodePacked(address(1))) - ); - - ExecutorConfig memory config = kernel.executorConfig(address(mockExecutor)); - assertEq(address(config.hook), HOOK_MODULE_INSTALLED_NO_HOOK, "Hook address(1) should remain address(1)"); + assertTrue(config.installed); } // ========================================================================= - // Validator install with hook=address(0) and hook=address(1) - // ========================================================================= - - function test_installValidator_WhenHookAddress0_ShouldSetHookToAddress1() public { - vm.prank(address(ep)); - kernel.installModule( - MODULE_TYPE_VALIDATOR, - address(newValidator), - abi.encode(hex"", abi.encodePacked(address(0), Kernel.execute.selector)) - ); - - ValidationId vId = validatorToIdentifier(IValidator(address(newValidator))); - ValidationInfo memory info = kernel.validationInfo(vId); - assertEq(info.hook, HOOK_MODULE_INSTALLED_NO_HOOK, "Hook address(0) should be mapped to address(1)"); - } - - function test_installValidator_WhenHookAddress1_ShouldKeepAddress1() public { - vm.prank(address(ep)); - kernel.installModule( - MODULE_TYPE_VALIDATOR, - address(newValidator), - abi.encode(hex"", abi.encodePacked(address(1), Kernel.execute.selector)) - ); - - ValidationId vId = validatorToIdentifier(IValidator(address(newValidator))); - ValidationInfo memory info = kernel.validationInfo(vId); - assertEq(info.hook, HOOK_MODULE_INSTALLED_NO_HOOK, "Hook address(1) should remain address(1)"); - } // ========================================================================= // Validator install with empty internalData — no selectors allowed @@ -916,16 +747,16 @@ contract RevertingOnInstallHook { function onUninstall(bytes calldata) external payable {} function isModuleType(uint256 typeId) external pure returns (bool) { - return typeId == 4; + return typeId == 11; } function isInitialized(address) external pure returns (bool) { return false; } - function preCheck(address, uint256, bytes calldata) external payable returns (bytes memory) { + function preCheck(bytes32, address, uint256, bytes calldata) external payable returns (bytes memory) { return hex""; } - function postCheck(bytes calldata) external payable {} + function postCheck(bytes32, bytes calldata) external payable {} } diff --git a/test/unit/RevertPaths.t.sol b/test/unit/RevertPaths.t.sol index 56bea2d0..5c6d1f6d 100644 --- a/test/unit/RevertPaths.t.sol +++ b/test/unit/RevertPaths.t.sol @@ -30,12 +30,9 @@ import { InvalidVid } from "src/types/Error.sol"; import { - HOOK_MODULE_NOT_INSTALLED, - HOOK_MODULE_INSTALLED_NO_HOOK, MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, - MODULE_TYPE_HOOK, MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER, CALLTYPE_SINGLE, @@ -44,7 +41,7 @@ import { SELECTOR_MANAGER_STORAGE_SLOT } from "src/types/Constants.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; -import {IValidator, IHook, IExecutor} from "src/interfaces/IERC7579Modules.sol"; +import {IValidator, IExecutor} from "src/interfaces/IERC7579Modules.sol"; import {IERC7579Account} from "src/interfaces/IERC7579Account.sol"; import {LibERC7579} from "solady/accounts/LibERC7579.sol"; @@ -118,7 +115,7 @@ contract RevertPathsTest is Test { // Verify it's installed ValidationId vId = validatorToIdentifier(IValidator(address(newValidator))); ValidationInfo memory vInfo = kernel.validationInfo(vId); - assertTrue(vInfo.hook != HOOK_MODULE_NOT_INSTALLED); + assertTrue(vInfo.installed); // Act & Assert: try to install the SAME validator again (same ValidationId) without uninstalling vm.expectRevert(OccupiedValidationId.selector); @@ -162,7 +159,7 @@ contract RevertPathsTest is Test { // Build inner signature: mode(1) + type(1) + validator(20) + validatorSig // First set the valid sig on the mock newValidator.sudoSetValidSig(hex"aabbccdd"); - bytes memory innerSig = abi.encodePacked(bytes1(0x00), bytes1(0x01), address(newValidator), hex"aabbccdd"); + bytes memory innerSig = abi.encodePacked(bytes1(0x01), address(newValidator), hex"aabbccdd"); // ERC-6492 sentinel = 0x6492...6492 // The sentinel is: mul(0x6492, div(not(shr(address(), address())), 0xffff)) @@ -262,38 +259,6 @@ contract RevertPathsTest is Test { // ========================================================================= // 3.16 - Executor with real hook - // ========================================================================= - - function test_executorWithHook_WhenExecutorInstalledWithHook_ShouldRunHookOnExecution() public { - vm.startPrank(address(ep)); - - // Step 1: Install hook - kernel.installModule(MODULE_TYPE_HOOK, address(hook), abi.encode(hex"deadbeef", "")); - - // Step 2: Install executor referencing the hook - kernel.installModule( - MODULE_TYPE_EXECUTOR, address(mockExecutor), abi.encode(hex"deadbeef", abi.encodePacked(address(hook))) - ); - vm.stopPrank(); - - // Verify executor config has the hook - ExecutorConfig memory config = kernel.executorConfig(address(mockExecutor)); - assertEq(address(config.hook), address(hook), "Executor config should reference the hook"); - - // Step 3: Execute from executor and verify hook runs - // Before execution, hook pre/post should not have been called yet - assertFalse(hook.preHookCalled(), "Pre-hook should not have been called yet"); - assertFalse(hook.postHookCalled(), "Post-hook should not have been called yet"); - - // Execute via the executor - vm.prank(address(mockExecutor)); - kernel.executeFromExecutor(bytes32(0), abi.encodePacked(address(callee), uint256(0), MockCallee.foo.selector)); - - // Assert: hook was called - assertTrue(hook.preHookCalled(), "Pre-hook should have been called"); - assertTrue(hook.postHookCalled(), "Post-hook should have been called"); - assertEq(callee.bar(), 1, "Callee should have been called"); - } // ========================================================================= // 3.17 - InvalidCallType in fallback @@ -313,32 +278,17 @@ contract RevertPathsTest is Test { kernel.installModule( MODULE_TYPE_FALLBACK, address(mockFallback), - abi.encode( - hex"deadbeef", - abi.encodePacked( - testSelector, - bytes1(0x00), // CALLTYPE_SINGLE - address(0) // no hook, will be set to address(0) - ) - ) + abi.encode(hex"deadbeef", abi.encodePacked(testSelector, bytes1(0x00))) ); // Verify fallback is installed SelectorConfig memory config = kernel.selectorConfig(testSelector); assertEq(config.target, address(mockFallback)); - // Now corrupt the callType in storage to an invalid value - // SelectorConfig layout: hook (20 bytes) | target (20 bytes) | callType (1 byte) - // SelectorStorage is at SELECTOR_MANAGER_STORAGE_SLOT, mapped by bytes4 selector - // Slot = keccak256(abi.encode(selector, SELECTOR_MANAGER_STORAGE_SLOT)) + // Now corrupt the callType in storage to an invalid value. + // SelectorConfig packs target (20 bytes) and callType (1 byte) into the mapping value slot. bytes32 selectorSlot = keccak256(abi.encode(bytes32(testSelector), SELECTOR_MANAGER_STORAGE_SLOT)); - - // The SelectorConfig is stored across two slots: - // slot 0: hook (address, 20 bytes, right-aligned in slot) - // slot 1: target (address, 20 bytes) | callType (bytes1, 1 byte) - // target is in the low 20 bytes of slot+1, callType is in byte 20 - bytes32 slot1 = bytes32(uint256(selectorSlot) + 1); - bytes32 currentVal = vm.load(address(kernel), slot1); + bytes32 currentVal = vm.load(address(kernel), selectorSlot); // Clear and set a bad callType (0x02, which is neither 0x00 nor 0xFF) // The layout for slot1: [unused bytes][callType 1byte][target 20bytes] @@ -351,11 +301,9 @@ contract RevertPathsTest is Test { val = val & ~(uint256(0xFF) << 160); // Set callType to 0x02 val = val | (uint256(0x02) << 160); - vm.store(address(kernel), slot1, bytes32(val)); + vm.store(address(kernel), selectorSlot, bytes32(val)); // Act & Assert: calling the fallback selector should revert with InvalidCallType - // We need to call from entrypoint since hook is address(0) which means HOOK_MODULE_NOT_INSTALLED - // and the fallback requires either target != 0 AND (hook != NOT_INSTALLED OR sender == entrypoint) vm.prank(address(ep)); vm.expectRevert(InvalidCallType.selector); MockFallback(address(kernel)).testFunction(); @@ -379,7 +327,7 @@ contract RevertPathsTest is Test { moduleType: MODULE_TYPE_VALIDATOR, module: address(enabledValidator), moduleData: hex"", - internalData: abi.encodePacked(address(0), Kernel.execute.selector) + internalData: abi.encodePacked(Kernel.execute.selector) }); // Compute the enable signature digest (replayable = true) @@ -429,6 +377,6 @@ contract RevertPathsTest is Test { // The validator should now be installed ValidationInfo memory vInfo = kernel.validationInfo(validatorToIdentifier(IValidator(address(enabledValidator)))); - assertEq(vInfo.hook, HOOK_MODULE_INSTALLED_NO_HOOK, "Enabled validator should be installed"); + assertTrue(vInfo.installed, "Enabled validator should be installed"); } } From 8b08d55dfc7aca1e8016202239eaed406406fb62 Mon Sep 17 00:00:00 2001 From: taek Date: Wed, 5 Aug 2026 09:50:57 +0900 Subject: [PATCH 6/8] test: remove enable mode from ERC-1271 Replace stateless enable-mode coverage with the validation-type-first structured signature format and Kernel7702 raw-signature cases. Remove obsolete stateless validators and formal tests, and differentially fuzz Lib4337 validity handling against EntryPoint v0.9. --- snapshots/Kernel7702Test.json | 4 +- snapshots/KernelECDSATest.json | 4 +- test/CheckValidation.t.sol | 11 +- test/IntersectValidationData.t.sol | 11 +- test/Kernel7702.t.sol | 44 +- test/KernelERC1271Test.sol | 553 +------------------- test/btt/Kernel.isValidSignature.t.sol | 468 ++--------------- test/btt/Kernel.isValidSignature.tree | 24 +- test/btt/Kernel7702.t.sol | 3 +- test/fuzz/CheckValidationDifferential.t.sol | 59 +++ test/halmos/KernelSignatureHalmos.t.sol | 8 +- test/halmos/PermissionStatelessHalmos.t.sol | 270 ---------- test/mock/ECDSAValidator.sol | 20 +- test/mock/MockPolicy.sol | 10 - test/mock/MockSigner.sol | 10 - 15 files changed, 175 insertions(+), 1324 deletions(-) create mode 100644 test/fuzz/CheckValidationDifferential.t.sol delete mode 100644 test/halmos/PermissionStatelessHalmos.t.sol diff --git a/snapshots/Kernel7702Test.json b/snapshots/Kernel7702Test.json index f601a899..55ff0c12 100644 --- a/snapshots/Kernel7702Test.json +++ b/snapshots/Kernel7702Test.json @@ -1,5 +1,5 @@ { - "Install - 3": "247441", - "Root - foo()": "101098", + "Install - 3": "246348", + "Root - foo()": "100976", "Simple - foo()": "100208" } \ No newline at end of file diff --git a/snapshots/KernelECDSATest.json b/snapshots/KernelECDSATest.json index 9b218312..ae7cd191 100644 --- a/snapshots/KernelECDSATest.json +++ b/snapshots/KernelECDSATest.json @@ -1,5 +1,5 @@ { - "Install - 3": "260914", - "Root - foo()": "113161", + "Install - 3": "259766", + "Root - foo()": "113002", "Simple - foo()": "100208" } \ No newline at end of file diff --git a/test/CheckValidation.t.sol b/test/CheckValidation.t.sol index 9d10a14b..51e2d16f 100644 --- a/test/CheckValidation.t.sol +++ b/test/CheckValidation.t.sol @@ -98,13 +98,12 @@ contract CheckValidationTest is Test { assertTrue(Lib4337.checkValidation(_pack(MODE_BIT | 100, 0, address(0)))); } - // --- exact MODE_BIT classification (>= MODE_BIT, equality counts) -------- + // --- exact MODE_BIT classification (strictly greater than the flag) ------- - function test_ExactModeBitBound_ClassifiesAsBlockMode() public pure { - // RP-01: a bound exactly equal to MODE_BIT counts as block-number format (>= MODE_BIT), - // not just strictly greater. Both bounds must carry the flag. - assertTrue(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT)); - assertTrue(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT | 10)); + function test_ExactModeBitBound_ClassifiesAsTimestampMode() public pure { + // EntryPoint v0.9 uses block-number mode only when both bounds exceed the flag. + assertFalse(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT)); + assertFalse(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT | 10)); // One bound below MODE_BIT → timestamp format. assertFalse(Lib4337._usesBlockNumberFormat(MODE_BIT - 1, MODE_BIT)); assertFalse(Lib4337._usesBlockNumberFormat(MODE_BIT, MODE_BIT - 1)); diff --git a/test/IntersectValidationData.t.sol b/test/IntersectValidationData.t.sol index ba9c4bf8..c9198bca 100644 --- a/test/IntersectValidationData.t.sol +++ b/test/IntersectValidationData.t.sol @@ -381,11 +381,11 @@ contract IntersectValidationDataTest is Test { uint48 validAfter2, uint48 validUntil2 ) public { - // Mask to lower bits then add MODE_BIT to ensure block number format - validAfter1 = (validAfter1 & TIMESTAMP_MASK) | MODE_BIT; - validUntil1 = (validUntil1 & TIMESTAMP_MASK) | MODE_BIT; - validAfter2 = (validAfter2 & TIMESTAMP_MASK) | MODE_BIT; - validUntil2 = (validUntil2 & TIMESTAMP_MASK) | MODE_BIT; + // EntryPoint v0.9 uses block number format only when both bounds exceed MODE_BIT. + validAfter1 = (validAfter1 & (TIMESTAMP_MASK - 1)) + MODE_BIT + 1; + validUntil1 = (validUntil1 & (TIMESTAMP_MASK - 1)) + MODE_BIT + 1; + validAfter2 = (validAfter2 & (TIMESTAMP_MASK - 1)) + MODE_BIT + 1; + validUntil2 = (validUntil2 & (TIMESTAMP_MASK - 1)) + MODE_BIT + 1; uint256 val1 = createValidationData(validAfter1, validUntil1, address(0)); uint256 val2 = createValidationData(validAfter2, validUntil2, address(0)); @@ -463,4 +463,3 @@ contract IntersectValidationDataTest is Test { assertEq(uint48(result >> 160), 200 | MODE_BIT, "M-03: block validUntil must be preserved"); } } - diff --git a/test/Kernel7702.t.sol b/test/Kernel7702.t.sol index 7521742b..3542f7b1 100644 --- a/test/Kernel7702.t.sol +++ b/test/Kernel7702.t.sol @@ -8,24 +8,10 @@ import {Kernel} from "src/Kernel.sol"; import {Install} from "src/types/Structs.sol"; import {ValidationId} from "src/types/Types.sol"; import {ERC1271_MAGICVALUE} from "src/types/Constants.sol"; -import {ERC1271_INVALID} from "src/types/Constants.sol"; import {InvalidValidationType} from "src/types/Error.sol"; import {validatorToIdentifier} from "src/lib/Utils.sol"; import {IValidator} from "src/interfaces/IERC7579Modules.sol"; -contract Kernel7702Harness is Kernel7702 { - constructor(IEntryPoint _ep) Kernel7702(_ep) {} - - function exposed_verifyStatelessSignature( - Install[] calldata packages, - ValidationId vId, - bytes32 hash, - bytes calldata signature - ) external view returns (bool) { - return _verifyStatelessSignature(packages, vId, hash, signature); - } -} - contract Kernel7702Test is KernelTest { address owner; uint256 ownerKey; @@ -81,14 +67,28 @@ contract Kernel7702Test is KernelTest { assertEq(ret, ERC1271_MAGICVALUE); } + function test_7702_raw_compact_signature(bytes32 hash) external { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); + bytes32 vs = bytes32(uint256(s) | (uint256(v - 27) << 255)); + bytes memory signature = abi.encodePacked(r, vs); + assertEq(signature.length, 64); + assertEq(kernel.isValidSignature(hash, signature), ERC1271_MAGICVALUE); + } + + function test_7702_structured_root_compact_signature(bytes32 hash) external { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); + bytes32 vs = bytes32(uint256(s) | (uint256(v - 27) << 255)); + bytes memory signature = abi.encodePacked(bytes1(0x00), r, vs); + assertEq(signature.length, 65); + assertEq(kernel.isValidSignature(hash, signature), ERC1271_MAGICVALUE); + } + function test_7702_raw_signature_invalid() external { // Use a fixed hash to ensure deterministic signature bytes bytes32 hash = keccak256("test_invalid_signature"); (, uint256 wrongKey) = makeAddrAndKey("WrongSigner"); (uint8 v, bytes32 r, bytes32 s) = vm.sign(wrongKey, hash); - // When raw signature verification fails, the code falls through to validation mode parsing - // which reverts because a raw signature doesn't have valid validation type bytes - vm.expectRevert(); + vm.expectRevert(InvalidValidationType.selector); kernel.isValidSignature(hash, abi.encodePacked(r, s, v)); } @@ -227,14 +227,4 @@ contract Kernel7702Test is KernelTest { uint256 validationData = kernel.validateUserOp(op, opHash, 0); assertEq(validationData, 1); } - - // ===== _verifyStatelessSignature: InvalidValidationType route ===== - - function test_7702_verifyStatelessSignature_revert_invalidValidationType() external { - Kernel7702Harness harness = new Kernel7702Harness(ep); - Install[] memory packages = new Install[](0); - // ValidationId with ROOT type (0x00) is neither VALIDATOR nor PERMISSION - vm.expectRevert(InvalidValidationType.selector); - harness.exposed_verifyStatelessSignature(packages, ValidationId.wrap(bytes21(0)), bytes32(0), hex""); - } } diff --git a/test/KernelERC1271Test.sol b/test/KernelERC1271Test.sol index 2a719dd7..cb2ed203 100644 --- a/test/KernelERC1271Test.sol +++ b/test/KernelERC1271Test.sol @@ -3,16 +3,8 @@ pragma solidity ^0.8.0; import {LibString} from "solady/utils/LibString.sol"; import {ERC1271_MAGICVALUE, ERC1271_INVALID} from "src/types/Constants.sol"; import {KernelTestBase} from "./KernelTestBase.sol"; -import {Install} from "src/types/Structs.sol"; import {Kernel} from "src/Kernel.sol"; -import { - InvalidSignature, - InvalidValidationType, - InvalidValidator, - InvalidPermissionId, - InvalidVid, - InvalidNonce -} from "src/types/Error.sol"; +import {InvalidValidationType, InvalidVid} from "src/types/Error.sol"; import {MockValidator} from "./mock/MockValidator.sol"; import {PermissionId} from "src/types/Types.sol"; import {IValidator} from "src/interfaces/IERC7579Modules.sol"; @@ -35,7 +27,7 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes32 messageHash = keccak256("Hello world"); (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE); } @@ -45,7 +37,7 @@ abstract contract KernelERC1271Test is KernelTestBase { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); vm.expectRevert(abi.encodeWithSelector(InvalidVid.selector, validatorToIdentifier(IValidator(address(this))))); kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(this)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(this)), sig) ); } @@ -53,7 +45,7 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes32 messageHash = keccak256("Hello world"); (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, false); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID); } @@ -61,7 +53,7 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes32 messageHash = keccak256("Hello world"); bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE); } @@ -69,7 +61,7 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes32 messageHash = keccak256("Hello world"); bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, false); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID); } @@ -79,8 +71,7 @@ abstract contract KernelERC1271Test is KernelTestBase { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); kernel.installModule(1, address(newValidator), abi.encode(hex"", hex"")); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); assertEq(ret, ERC1271_MAGICVALUE); } @@ -91,8 +82,7 @@ abstract contract KernelERC1271Test is KernelTestBase { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, false); kernel.installModule(1, address(newValidator), abi.encode(hex"", hex"")); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); assertEq(ret, ERC1271_INVALID); } @@ -102,9 +92,8 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _validatorSignHash(personalHash, true); kernel.installModule(1, address(newValidator), abi.encode(hex"", hex"")); - bytes4 ret = kernel.isValidSignature( - messageHash, abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) - ); + bytes4 ret = + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig)); assertEq(ret, ERC1271_MAGICVALUE); } @@ -113,9 +102,8 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _validatorSignHash(personalHash, false); kernel.installModule(1, address(newValidator), abi.encode(hex"", hex"")); - bytes4 ret = kernel.isValidSignature( - messageHash, abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) - ); + bytes4 ret = + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig)); assertEq(ret, ERC1271_INVALID); } @@ -128,8 +116,7 @@ abstract contract KernelERC1271Test is KernelTestBase { kernel.installModule(1, address(newValidator), abi.encode(hex"", hex"")); vm.expectRevert(abi.encodeWithSelector(InvalidVid.selector, validatorToIdentifier(mockValidator))); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(mockValidator)), hex"") + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(mockValidator)), hex"") ); } @@ -139,9 +126,8 @@ abstract contract KernelERC1271Test is KernelTestBase { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, true); kernel.installModule(5, address(policy), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); kernel.installModule(6, address(signer), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); - bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + bytes4 ret = + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_MAGICVALUE); } @@ -151,9 +137,8 @@ abstract contract KernelERC1271Test is KernelTestBase { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, false); kernel.installModule(5, address(policy), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); kernel.installModule(6, address(signer), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); - bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + bytes4 ret = + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_INVALID); } @@ -163,7 +148,7 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes memory sig = _permissionSignHash(personalHash, true); kernel.installModule(5, address(policy), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); kernel.installModule(6, address(signer), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_MAGICVALUE); } @@ -173,7 +158,7 @@ abstract contract KernelERC1271Test is KernelTestBase { bytes memory sig = _permissionSignHash(personalHash, false); kernel.installModule(5, address(policy), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); kernel.installModule(6, address(signer), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_INVALID); } @@ -186,7 +171,7 @@ abstract contract KernelERC1271Test is KernelTestBase { abi.encodeWithSelector(InvalidVid.selector, permissionToIdentifier(PermissionId.wrap(0xefefefef))) ); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), bytes4(0xefefefef), hex"") + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), bytes4(0xefefefef), hex"") ); } @@ -197,499 +182,19 @@ abstract contract KernelERC1271Test is KernelTestBase { kernel.installModule(5, address(policy), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); kernel.installModule(6, address(signer), abi.encode(hex"deadbeef", abi.encodePacked(permissionId))); vm.expectRevert(InvalidValidationType.selector); - kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0xee), permissionId, sig)); - } - - function test_erc1271_enable_validator( - bool replayable, - bool enable, - bool signature, - bool personalSign, - bool vIdExist, - bytes32 extra - ) external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: replayable, - enableSuccess: enable, - signatureSuccess: signature, - personalSign: personalSign, - vIdExist: vIdExist, - extra: extra - }) - ); - } - - function test_erc1271_enable_permission( - bool replayable, - bool enable, - bool signature, - bool personalSign, - bool vIdExist, - bytes32 extra - ) external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: replayable, - enableSuccess: enable, - signatureSuccess: signature, - personalSign: personalSign, - vIdExist: vIdExist, - extra: extra - }) - ); - } - - function test_erc1271_enable_validator() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_validator_wrongNonce() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: true, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_validator_fail_validator_not_exist() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: false, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_validator_fail() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: false, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_validator_personal_sign() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_validator_personal_sign_fail() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: false, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_replayable_validator() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_replayable_validator_fail() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: false, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_replayable_validator_personal_sign() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: true, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_replayable_validator_personal_sign_fail() external unitTest erc1271Test { - _testSigEnableValidator( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: false, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_permission() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_permission_wrongNonce() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: true, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_permission_fail_validator_not_exist() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: false, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_permission_fail() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: false, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_permission_personal_sign() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: true, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_permission_personal_sign_fail() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: false, - enableSuccess: true, - signatureSuccess: false, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_replayable_permission() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: true, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0xee), permissionId, sig)); } - function test_erc1271_enable_replayable_permission_fail() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: false, - personalSign: false, - vIdExist: true, - extra: bytes32(0) - }) - ); + function test_erc1271_truncated_structured_signatures_return_invalid() external erc1271Test { + assertEq(kernel.isValidSignature(keccak256("empty"), hex""), ERC1271_INVALID); + assertEq(kernel.isValidSignature(keccak256("validator"), hex"01"), ERC1271_INVALID); + assertEq(kernel.isValidSignature(keccak256("permission"), hex"02dead"), ERC1271_INVALID); } - function test_erc1271_enable_replayable_permission_personal_sign() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: true, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - function test_erc1271_enable_replayable_permission_personal_sign_fail() external unitTest erc1271Test { - _testSigEnablePermission( - EnableTestParam({ - wrongNonce: false, - replayable: true, - enableSuccess: true, - signatureSuccess: false, - personalSign: true, - vIdExist: true, - extra: bytes32(0) - }) - ); - } - - struct EnableTestParam { - bool wrongNonce; - bool replayable; - bool enableSuccess; - bool signatureSuccess; - bool personalSign; - bool vIdExist; - bytes32 extra; - } - - function _testSigEnableValidator(EnableTestParam memory args) internal { - bytes32 messageHash = keccak256("Hello world"); - bytes memory sig; - if (args.personalSign) { - bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); - sig = _validatorSignHash(personalHash, args.signatureSuccess); - } else { - bytes32 contentsHash; - (contentsHash, sig) = _erc1271Signature( - messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, args.signatureSuccess - ); - messageHash = _toContentsHash(contentsHash); - } - Install[] memory packages = new Install[](1); - if (args.vIdExist) { - packages[0] = - Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - } else { - MockValidator mockValidator = new MockValidator(); - mockValidator.sudoSetSuccess(true); - packages[0] = - Install({moduleType: 1, module: address(mockValidator), moduleData: hex"", internalData: hex""}); - } - uint8 uMode = 0; - // enable mode flag - uMode += 2 ** 3; - if (args.replayable) { - uMode += 2 ** 2; - } - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode( - uint256(0), packages, enableSig(0, args.enableSuccess, args.replayable, packages, _rootSignHash), sig - ) - ); - - if (args.wrongNonce) { - vm.startPrank(address(kernel)); - kernel.setNonce(0, 10); - } - if (args.vIdExist && args.enableSuccess && !args.wrongNonce) { - bytes4 res = kernel.isValidSignature(messageHash, sigWithEnable); - assertEq(res, !args.wrongNonce && args.signatureSuccess ? ERC1271_MAGICVALUE : ERC1271_INVALID); - - if (!isMock) { - vm.chainId(1000); - res = kernel.isValidSignature(messageHash, sigWithEnable); - assertEq( - res, - !args.wrongNonce && args.signatureSuccess && args.replayable ? ERC1271_MAGICVALUE : ERC1271_INVALID - ); - } - } else if (!args.enableSuccess) { - bytes32 res = kernel.isValidSignature(messageHash, sigWithEnable); - assertEq(res, ERC1271_INVALID); - } else { - if (args.wrongNonce) { - vm.expectRevert(InvalidNonce.selector); - } else { - vm.expectRevert(InvalidValidator.selector); - } - kernel.isValidSignature(messageHash, sigWithEnable); - } - } - - function _testSigEnablePermission(EnableTestParam memory args) internal { - bytes32 messageHash = keccak256("Hello world"); - bytes memory sig; - if (args.personalSign) { - bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); - if (!args.signatureSuccess && args.extra != bytes32(0)) { - bytes[] memory empty = new bytes[](0); - sig = abi.encode(empty); - } else { - sig = _permissionSignHash(personalHash, args.signatureSuccess); - } - } else { - bytes32 contentsHash; - (contentsHash, sig) = _erc1271Signature( - messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, args.signatureSuccess - ); - if (!args.signatureSuccess && args.extra != bytes32(0)) { - bytes[] memory empty = new bytes[](0); - sig = abi.encode(empty); - } - messageHash = _toContentsHash(contentsHash); - } - Install[] memory packages = new Install[](2); - if (args.vIdExist) { - packages[0] = Install({ - moduleType: 5, module: address(policy), moduleData: hex"", internalData: abi.encodePacked(permissionId) - }); - packages[1] = Install({ - moduleType: 6, module: address(signer), moduleData: hex"", internalData: abi.encodePacked(permissionId) - }); - } else { - packages[0] = Install({ - moduleType: 5, module: address(policy), moduleData: hex"", internalData: abi.encodePacked(hex"cafecafe") - }); - packages[1] = Install({ - moduleType: 6, module: address(signer), moduleData: hex"", internalData: abi.encodePacked(hex"cafecafe") - }); - } - uint8 uMode = 0; - // enable mode flag - uMode += 2 ** 3; - if (args.replayable) { - uMode += 2 ** 2; - } - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x02), - permissionId, - abi.encode( - uint256(0), - packages, - enableSig(args.wrongNonce ? 1 : 0, args.enableSuccess, args.replayable, packages, _rootSignHash), - sig - ) - ); - - if (args.wrongNonce) { - vm.startPrank(address(kernel)); - kernel.setNonce(0, 10); - } - if (args.vIdExist && !args.wrongNonce && !(!args.signatureSuccess && args.extra != bytes32(0))) { - bytes4 res = kernel.isValidSignature(messageHash, sigWithEnable); - assertEq( - res, - args.signatureSuccess && args.enableSuccess && !args.wrongNonce ? ERC1271_MAGICVALUE : ERC1271_INVALID - ); - - if (!isMock) { - vm.chainId(1000); - res = kernel.isValidSignature(messageHash, sigWithEnable); - assertEq( - res, - args.signatureSuccess && args.replayable && args.enableSuccess && !args.wrongNonce - ? ERC1271_MAGICVALUE - : ERC1271_INVALID - ); - } - } else if (!args.enableSuccess) { - bytes4 res = kernel.isValidSignature(messageHash, sigWithEnable); - assertEq(res, ERC1271_INVALID); - } else if (!args.signatureSuccess && args.extra != bytes32(0)) { - vm.expectRevert(InvalidSignature.selector); - kernel.isValidSignature(messageHash, sigWithEnable); - } else { - if (args.wrongNonce) { - vm.expectRevert(InvalidNonce.selector); - } else { - vm.expectRevert(InvalidPermissionId.selector); - } - kernel.isValidSignature(messageHash, sigWithEnable); - } + function test_erc1271_legacy_mode_prefix_returns_invalid() external unitTest erc1271Test { + bytes32 messageHash = keccak256("legacy mode prefix"); + bytes memory sig = _rootSignHash(_toErc1271HashPersonalSign(messageHash), true); + assertEq(kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)), ERC1271_INVALID); } // Code heavily inspired by solady's erc1271, erc4337 test file diff --git a/test/btt/Kernel.isValidSignature.t.sol b/test/btt/Kernel.isValidSignature.t.sol index f10a303e..b28aa4f6 100644 --- a/test/btt/Kernel.isValidSignature.t.sol +++ b/test/btt/Kernel.isValidSignature.t.sol @@ -5,21 +5,13 @@ import {LibString} from "solady/utils/LibString.sol"; import {LibClone} from "solady/utils/LibClone.sol"; import {ERC1271_MAGICVALUE, ERC1271_INVALID} from "src/types/Constants.sol"; import {BTTModifiers} from "./BTTModifiers.sol"; -import {Install} from "src/types/Structs.sol"; import {Kernel} from "src/Kernel.sol"; -import { - InvalidValidationType, - InvalidValidator, - InvalidPermissionId, - InvalidNonce, - InvalidVid -} from "src/types/Error.sol"; +import {InvalidValidationType, InvalidVid} from "src/types/Error.sol"; import {ValidationId, validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; import {IValidator} from "src/interfaces/IERC7579Modules.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; import {PermissionId} from "src/types/Types.sol"; -import {MockValidator} from "../mock/MockValidator.sol"; /// @title Kernel.isValidSignature BTT Tests /// @notice Tests for isValidSignature following Branching Tree Technique @@ -28,7 +20,6 @@ abstract contract Kernel_isValidSignature is BTTModifiers { // State variables for isValidSignature branch tracking // Note: _validationType and _isTypedDataSign are inherited from BTTModifiers bytes32 internal _testHash; - bool internal _enableMode; bool internal _isExplicitContentsName; bool internal _isReplayableSignature; @@ -43,264 +34,6 @@ abstract contract Kernel_isValidSignature is BTTModifiers { _; } - modifier givenTheSignatureModeByteIndicatesEnableMode() { - _enableMode = true; - _; - } - - function test_GivenTheValidationTypeIsROOT() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - { - // it should revert with InvalidValidationType error - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - // ROOT validation type (0x00) is not allowed with enable mode - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x00), // ROOT validation type - bytes20(0), // No validator address for root - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - vm.expectRevert(InvalidValidationType.selector); - kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - } - - function test_GivenTheEnableNonceIsInvalid() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - { - // it should revert with InvalidNonce error - // Note: isValidSignature is a view function, so nonces are validated but not consumed. - // To test invalid nonce, we use nonce 1 when stored nonce is 0 (invalid because seq != stored). - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - // Use nonce 1 when stored nonce is 0 - this is invalid - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(1), packages, enableSig(1, true, false, packages, _rootSignHash), sig) - ); - - vm.expectRevert(InvalidNonce.selector); - kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - } - - function test_GivenTheEnableSignatureIsInvalid() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - { - // it should return ERC1271_INVALID - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - // Use invalid root signature for enable - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, false, false, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - assertEq(res, ERC1271_INVALID, "Enable mode with invalid signature should return INVALID"); - } - - modifier givenTheEnableSignatureIsValid() { - require(_enableMode, "Enable mode should be set"); - _; - } - - function test_GivenTheEnableSignatureIsValid() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - { - // it should install the packages from the signature - // it should continue with the remaining signature validation - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - assertEq(res, ERC1271_MAGICVALUE, "Enable mode with valid signature should return MAGICVALUE"); - } - - function test_GivenTheValidatorPackageIsMissing() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - givenTheEnableSignatureIsValid - { - // it should revert with InvalidValidator error - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - // Create packages that don't include the validator specified in the signature - MockValidator wrongValidator = new MockValidator(); - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(wrongValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - // The signature specifies newValidator but packages install wrongValidator - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, // This validator is NOT in the packages - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - vm.expectRevert(InvalidValidator.selector); - kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - } - - function test_GivenThePermissionSignaturesAreInconsistent() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - givenTheEnableSignatureIsValid - { - // it should revert with InvalidPermissionId error - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, true); - - // Create packages with inconsistent permissionIds - MockPolicy mockPolicy = new MockPolicy(); - MockSigner mockSigner = new MockSigner(); - PermissionId permId1 = PermissionId.wrap(bytes4(keccak256("permId1"))); - PermissionId permId2 = PermissionId.wrap(bytes4(keccak256("permId2"))); // Different! - - // Set up the mock policy to pass validation so we can reach the permissionId consistency check - mockPolicy.sudoSetPass(address(kernel), bytes32(PermissionId.unwrap(permId1)), true); - - Install[] memory packages = new Install[](2); - packages[0] = Install({ - moduleType: 5, module: address(mockPolicy), moduleData: hex"", internalData: abi.encodePacked(permId1) - }); - packages[1] = Install({ - moduleType: 6, - module: address(mockSigner), - moduleData: hex"", - internalData: abi.encodePacked(permId2) // Different permissionId! - }); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x02), // PERMISSION validation type - permId1, - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - vm.expectRevert(InvalidPermissionId.selector); - kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - } - - function test_GivenEnableModeIsReplayable() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - { - // it should verify the enable signature without chainId - // it should allow cross-chain enable mode - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - uMode += 2 ** 2; // replayable flag - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, true, true, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - assertEq(res, ERC1271_MAGICVALUE, "Replayable enable mode should validate"); - } - - function test_GivenEnableModeIsNotReplayable() - external - whenHashIsNotERC7739_MAGIC_HASH - givenTheSignatureModeByteIndicatesEnableMode - { - // it should verify the enable signature with chainId - // it should fail on different chains - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - // NOT replayable (no replayable flag) - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - assertEq(res, ERC1271_MAGICVALUE, "Non-replayable enable mode should validate on same chain"); - } - modifier givenTheValidationTypeIsROOT() { _validationType = 0; _; @@ -322,7 +55,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid root signature should return MAGICVALUE"); } @@ -337,7 +70,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, false); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid root signature should return INVALID"); } @@ -357,7 +90,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "PersonalSign should wrap hash and validate"); } @@ -372,7 +105,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid root PersonalSign should return MAGICVALUE"); } @@ -387,7 +120,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, false); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid root PersonalSign should return INVALID"); } @@ -401,7 +134,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Full TypedDataSign structure should validate"); } @@ -426,8 +159,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); assertEq(ret, ERC1271_MAGICVALUE, "Valid validator signature should return MAGICVALUE"); } @@ -448,8 +180,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, false); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); assertEq(ret, ERC1271_INVALID, "Invalid validator signature should return INVALID"); } @@ -469,9 +200,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _validatorSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature( - messageHash, abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) - ); + bytes4 ret = + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid validator PersonalSign should return MAGICVALUE"); } @@ -490,9 +220,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _validatorSignHash(personalHash, false); - bytes4 ret = kernel.isValidSignature( - messageHash, abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) - ); + bytes4 ret = + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig)); assertEq(ret, ERC1271_INVALID, "Invalid validator PersonalSign should return INVALID"); } @@ -517,9 +246,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, true); - bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + bytes4 ret = + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid permission should return MAGICVALUE"); } @@ -540,9 +268,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, false); - bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + bytes4 ret = + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_INVALID, "Failed policy should return INVALID"); } @@ -563,9 +290,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, false); - bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + bytes4 ret = + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_INVALID, "Invalid signer should return INVALID"); } @@ -585,7 +311,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _permissionSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid permission PersonalSign should return MAGICVALUE"); } @@ -606,7 +332,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { permissionRevertIndex = 0; // policy fails bytes memory sig = _permissionSignHash(personalHash, false); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_INVALID, "Failed policy/signer should return INVALID"); } @@ -617,7 +343,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes memory sig = _permissionSignHash(personalHash, true); vm.expectRevert(InvalidValidationType.selector); - kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0xee), permissionId, sig)); + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0xee), permissionId, sig)); } function test_GivenTheSignatureIsWrappedWithERC6492Sentinel() external whenHashIsNotERC7739_MAGIC_HASH { @@ -626,7 +352,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory innerSig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); - bytes memory fullInnerSig = abi.encodePacked(bytes1(0), bytes1(0), innerSig); + bytes memory fullInnerSig = abi.encodePacked(bytes1(0), innerSig); // Wrap with ERC6492 sentinel: abi.encode(address, bytes, bytes) ++ sentinel // The sentinel is 0x6492...6492 @@ -659,7 +385,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "MyContents", _rootSignHash, true, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid explicit contentsName should return MAGICVALUE"); } @@ -673,7 +399,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "MyContents", _rootSignHash, true, false); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid explicit contentsName should return INVALID"); } @@ -693,7 +419,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271SignatureReplayableExplicit(messageHash, "C(bytes32 stuff)", "MyContents", _rootSignHash, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid replayable explicit contentsName should return MAGICVALUE"); } @@ -707,7 +433,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271SignatureReplayableExplicit(messageHash, "C(bytes32 stuff)", "MyContents", _rootSignHash, false); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid replayable explicit contentsName should return INVALID"); } @@ -721,7 +447,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { // Signature won't match TypedDataSign reconstruction, falls to PersonalSign. // PersonalSign wraps hash and calls _erc1271IsValidSignatureNowCalldata. // ROOT type with root=0 triggers _verifyFallbackSignature -> returns false. - bytes4 ret = uninit.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), bytes32(0))); + bytes4 ret = uninit.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes32(0))); assertEq(ret, ERC1271_INVALID, "Uninitialized kernel should return INVALID via fallback"); } @@ -740,7 +466,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271SignatureReplayable(messageHash, "C(bytes32 stuff)", _rootSignHash, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid replayable TypedDataSign should return MAGICVALUE"); } @@ -754,7 +480,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271SignatureReplayable(messageHash, "C(bytes32 stuff)", _rootSignHash, false); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid replayable TypedDataSign should return INVALID"); } /*////////////////////////////////////////////////////////////// @@ -791,7 +517,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, true); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid root signature should return MAGICVALUE"); } @@ -808,7 +534,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _rootSignHash, false, false); - bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid root signature should return INVALID"); } @@ -825,7 +551,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid root PersonalSign should return MAGICVALUE"); } @@ -842,7 +568,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _rootSignHash(personalHash, false); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID, "Invalid root PersonalSign should return INVALID"); } @@ -868,8 +594,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { ValidationId vId = validatorToIdentifier(IValidator(address(newValidator))); vm.expectRevert(abi.encodeWithSelector(InvalidVid.selector, vId)); kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); } @@ -887,8 +612,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); assertEq(ret, ERC1271_MAGICVALUE, "Valid validator signature should return MAGICVALUE"); @@ -908,8 +632,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, false); bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), - abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) + _toContentsHash(contentsHash), abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig) ); assertEq(ret, ERC1271_INVALID, "Invalid validator signature should return INVALID"); @@ -928,9 +651,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _validatorSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature( - messageHash, abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(newValidator)), sig) - ); + bytes4 ret = + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x01), bytes20(address(newValidator)), sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid validator PersonalSign should return MAGICVALUE"); } @@ -953,9 +675,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { ValidationId vId = permissionToIdentifier(permissionId); vm.expectRevert(abi.encodeWithSelector(InvalidVid.selector, vId)); - kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); } /// @notice it should return ERC1271_INVALID when any policy fails @@ -970,9 +690,8 @@ abstract contract Kernel_isValidSignature is BTTModifiers { (bytes32 contentsHash, bytes memory sig) = _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _permissionSignHash, false, false); - bytes4 ret = kernel.isValidSignature( - _toContentsHash(contentsHash), abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig) - ); + bytes4 ret = + kernel.isValidSignature(_toContentsHash(contentsHash), abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_INVALID, "Failed policy should return INVALID"); } @@ -990,7 +709,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes32 personalHash = _toErc1271HashPersonalSign(messageHash); bytes memory sig = _permissionSignHash(personalHash, true); - bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0x02), permissionId, sig)); + bytes4 ret = kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0x02), permissionId, sig)); assertEq(ret, ERC1271_MAGICVALUE, "Valid permission PersonalSign should return MAGICVALUE"); } @@ -1006,106 +725,7 @@ abstract contract Kernel_isValidSignature is BTTModifiers { bytes memory sig = _permissionSignHash(personalHash, true); vm.expectRevert(InvalidValidationType.selector); - kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0), bytes1(0xee), permissionId, sig)); - } - - /*////////////////////////////////////////////////////////////// - ENABLE MODE TESTS - //////////////////////////////////////////////////////////////*/ - - /// @notice it should install packages and validate when enable mode signature is valid - function test_isValidSignature_WhenEnableModeWithValidSignature() - external - unitTest - givenHashIsNotERC7739MagicHash - givenSignatureModeIndicatesEnableMode - givenValidationTypeIsValidator - { - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - - assertEq(res, ERC1271_MAGICVALUE, "Enable mode with valid signature should return MAGICVALUE"); - } - - /// @notice it should return ERC1271_INVALID when enable mode signature is invalid - function test_isValidSignature_WhenEnableModeWithInvalidSignature() - external - unitTest - givenHashIsNotERC7739MagicHash - givenSignatureModeIndicatesEnableMode - givenValidationTypeIsValidator - { - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, false); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, true, false, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - - assertEq(res, ERC1271_INVALID, "Enable mode with invalid signature should return INVALID"); - } - - /*////////////////////////////////////////////////////////////// - REPLAYABLE MODE TESTS - //////////////////////////////////////////////////////////////*/ - - /// @notice it should allow cross-chain enable mode with replayable signature - function test_WhenEnableModeReplayable_CrossChainValid() external unitTest givenHashIsNotERC7739MagicHash { - bytes32 messageHash = keccak256("Hello world"); - (bytes32 contentsHash, bytes memory sig) = - _erc1271Signature(messageHash, "C(bytes32 stuff)", "", _validatorSignHash, false, true); - - Install[] memory packages = new Install[](1); - packages[0] = Install({moduleType: 1, module: address(newValidator), moduleData: hex"", internalData: hex""}); - - uint8 uMode = 0; - uMode += 2 ** 3; // enable mode flag - uMode += 2 ** 2; // replayable flag - - bytes memory sigWithEnable = abi.encodePacked( - uMode, - bytes1(0x01), - newValidator, - abi.encode(uint256(0), packages, enableSig(0, true, true, packages, _rootSignHash), sig) - ); - - bytes4 res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - assertEq(res, ERC1271_MAGICVALUE, "Replayable enable mode should validate"); - - // Simulate different chain by changing chainId (if not mock) - if (!isMock) { - vm.chainId(1000); - res = kernel.isValidSignature(_toContentsHash(contentsHash), sigWithEnable); - assertEq(res, ERC1271_MAGICVALUE, "Replayable should work on different chain"); - } + kernel.isValidSignature(messageHash, abi.encodePacked(bytes1(0xee), permissionId, sig)); } /*////////////////////////////////////////////////////////////// diff --git a/test/btt/Kernel.isValidSignature.tree b/test/btt/Kernel.isValidSignature.tree index 8483d718..6913ab11 100644 --- a/test/btt/Kernel.isValidSignature.tree +++ b/test/btt/Kernel.isValidSignature.tree @@ -2,26 +2,10 @@ Kernel_isValidSignature ├── when hash equals ERC7739_MAGIC_HASH │ └── it should return ERC7739 support indicator └── when hash is not ERC7739_MAGIC_HASH - ├── given the signature mode byte indicates enable mode - │ ├── given the validation type is ROOT - │ │ └── it should revert with InvalidValidationType error - │ ├── given the enable nonce is invalid - │ │ └── it should revert with InvalidNonce error - │ ├── given the enable signature is invalid - │ │ └── it should return ERC1271_INVALID - │ └── given the enable signature is valid - │ ├── it should install-validate the packages in the signature - │ ├── it should continue with stateless signature validation - │ ├── given the validator package is missing - │ │ └── it should revert with InvalidValidator error - │ └── given the permission signatures are inconsistent - │ └── it should revert with InvalidPermissionId error - ├── given enable mode is replayable - │ ├── it should verify the enable signature without chainId - │ └── it should allow cross-chain enable mode - ├── given enable mode is not replayable - │ ├── it should verify the enable signature with chainId - │ └── it should fail on different chains + ├── given the structured signature is truncated + │ └── it should return ERC1271_INVALID + ├── given the signature includes the removed validation-mode prefix + │ └── it should return ERC1271_INVALID ├── given the validation type is ROOT │ ├── given the signature format is TypedDataSign │ │ ├── when the root validator returns valid diff --git a/test/btt/Kernel7702.t.sol b/test/btt/Kernel7702.t.sol index 1552681a..edb33382 100644 --- a/test/btt/Kernel7702.t.sol +++ b/test/btt/Kernel7702.t.sol @@ -12,7 +12,7 @@ import {KernelFactory} from "src/KernelFactory.sol"; import {Install} from "src/types/Structs.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; -import {ERC1271_MAGICVALUE, ERC1271_INVALID} from "src/types/Constants.sol"; +import {ERC1271_MAGICVALUE} from "src/types/Constants.sol"; import {InvalidValidationType} from "src/types/Error.sol"; import {Received} from "src/types/Events.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; @@ -194,7 +194,6 @@ contract Kernel7702_Test is Test { } function test_WhenIsValidSignatureReceivesAnInvalidRawECDSASignature() external { - // it should revert because fallthrough parses invalid validation type bytes32 hash = keccak256("test_invalid_signature"); (, uint256 wrongKey) = makeAddrAndKey("WrongSigner"); (uint8 v, bytes32 r, bytes32 s) = vm.sign(wrongKey, hash); diff --git a/test/fuzz/CheckValidationDifferential.t.sol b/test/fuzz/CheckValidationDifferential.t.sol new file mode 100644 index 00000000..a83bb467 --- /dev/null +++ b/test/fuzz/CheckValidationDifferential.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {EntryPoint} from "account-abstraction/core/EntryPoint.sol"; + +import {Lib4337} from "src/lib/Lib4337.sol"; + +contract EntryPointV09ValidationHarness is EntryPoint { + function getValidationData(uint256 validationData) + external + view + returns (address aggregator, bool outOfValidityRange, bool isBlockRange) + { + return _getValidationData(validationData); + } +} + +contract CheckValidationDifferentialTest is Test { + EntryPointV09ValidationHarness internal entryPoint; + + function setUp() public { + entryPoint = new EntryPointV09ValidationHarness(); + } + + function testFuzz_CheckValidationMatchesEntryPointV09( + uint48 validAfter, + uint48 validUntil, + uint160 aggregatorSeed, + uint8 aggregatorMode, + bool unbounded, + uint64 currentBlock, + uint64 currentTimestamp + ) public { + if (unbounded) validUntil = 0; + + address aggregator; + if (aggregatorMode % 3 == 1) { + aggregator = address(1); + } else if (aggregatorMode % 3 == 2) { + aggregator = address(aggregatorSeed | 2); + } + uint256 validationData = uint256(validAfter) << 208 | uint256(validUntil) << 160 | uint160(aggregator); + + vm.roll(currentBlock); + vm.warp(currentTimestamp); + + (address entryPointAggregator, bool outOfValidityRange, bool isBlockRange) = + entryPoint.getValidationData(validationData); + + // Lib4337.checkValidation is used only where signature aggregators are unsupported, + // equivalent to EntryPoint validation with an expected aggregator of address(0). + bool expected = entryPointAggregator == address(0) && !outOfValidityRange; + assertEq(Lib4337.checkValidation(validationData), expected); + + (uint48 parsedValidAfter, uint48 parsedValidUntil,) = Lib4337.parseValidationData(validationData); + assertEq(Lib4337._usesBlockNumberFormat(parsedValidAfter, parsedValidUntil), isBlockRange); + } +} diff --git a/test/halmos/KernelSignatureHalmos.t.sol b/test/halmos/KernelSignatureHalmos.t.sol index a74ee772..94077258 100644 --- a/test/halmos/KernelSignatureHalmos.t.sol +++ b/test/halmos/KernelSignatureHalmos.t.sol @@ -33,14 +33,14 @@ contract KernelSignatureHalmos is SymTest, Test { bytes32 hash = bytes32(svm.createBytes(32, "hash")); bytes memory sig = svm.createBytes(65, "sig"); rootValidator.sudoSetValidSig(sig); - bytes4 ret = kernel.isValidSignature(hash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(hash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_MAGICVALUE); } function checkRootSignatureInvalid() external { bytes32 hash = bytes32(svm.createBytes(32, "hash")); bytes memory sig = svm.createBytes(65, "sig"); - bytes4 ret = kernel.isValidSignature(hash, abi.encodePacked(bytes1(0), bytes1(0), sig)); + bytes4 ret = kernel.isValidSignature(hash, abi.encodePacked(bytes1(0), sig)); assertEq(ret, ERC1271_INVALID); } @@ -48,7 +48,7 @@ contract KernelSignatureHalmos is SymTest, Test { bytes32 hash = bytes32(svm.createBytes(32, "hash")); bytes memory sig = svm.createBytes(65, "sig"); vm.expectRevert(InvalidValidationType.selector); - kernel.isValidSignature(hash, abi.encodePacked(bytes1(0), bytes1(0x03), sig)); + kernel.isValidSignature(hash, abi.encodePacked(bytes1(0x03), sig)); } function checkValidatorNotInstalledReverts() external { @@ -56,6 +56,6 @@ contract KernelSignatureHalmos is SymTest, Test { bytes32 hash = bytes32(svm.createBytes(32, "hash")); bytes memory sig = svm.createBytes(65, "sig"); vm.expectRevert(abi.encodeWithSelector(InvalidVid.selector, validatorToIdentifier(validator))); - kernel.isValidSignature(hash, abi.encodePacked(bytes1(0), bytes1(0x01), bytes20(address(validator)), sig)); + kernel.isValidSignature(hash, abi.encodePacked(bytes1(0x01), bytes20(address(validator)), sig)); } } diff --git a/test/halmos/PermissionStatelessHalmos.t.sol b/test/halmos/PermissionStatelessHalmos.t.sol deleted file mode 100644 index 9e4e1018..00000000 --- a/test/halmos/PermissionStatelessHalmos.t.sol +++ /dev/null @@ -1,270 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {SymTest} from "halmos-cheatcodes/SymTest.sol"; - -import {ModuleManager} from "src/core/ModuleManager.sol"; -import {Install} from "src/types/Structs.sol"; -import {ValidationId, PermissionId} from "src/types/Types.sol"; -import {permissionToIdentifier} from "src/lib/Utils.sol"; -import {InvalidPermissionId} from "src/types/Error.sol"; - -import {MockSigner} from "../mock/MockSigner.sol"; -import {MockValidator} from "../mock/MockValidator.sol"; -import {MockHook} from "../mock/MockHook.sol"; - -/// @notice Concrete `ModuleManager` subclass that exposes the internal -/// `_verifyStatelessSignature` as an external entry point so Halmos -/// can drive it directly with symbolic calldata. -/// @dev The function under test is `view` and depends only on calldata + -/// the module addresses passed in `packages` (it does not read this -/// contract's storage). The harness therefore needs no setUp-time -/// state seeding beyond deploying the contract itself. -contract PermissionStatelessHarness is ModuleManager { - /// @dev Required by Solady's `EIP712`. Trivial values for the harness. - function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { - name = "PermissionStatelessHarness"; - version = "1"; - } - - /// @notice External passthrough so Halmos can call the internal - /// permission-branch loop with controlled, symbolic-friendly - /// calldata. - function verifyStatelessExternal( - Install[] calldata packages, - ValidationId vId, - bytes32 hash, - bytes calldata signature - ) external view returns (bool) { - return _verifyStatelessSignature(packages, vId, hash, signature); - } -} - -/// @notice Halmos regression for commit `bfbef77` -/// ("fix: filter permission stateless match by module type"). -/// -/// Property under test -/// ------------------- -/// In the permission branch of `_verifyStatelessSignature`, only packages -/// whose `moduleType` is `5` (POLICY) or `6` (SIGNER) AND whose -/// `internalData[0:4] == pId` may advance the `sigIdx` cursor. A package -/// whose `moduleType` is `1` (VALIDATOR), `2` (EXECUTOR), `3` (FALLBACK), -/// or `4` (HOOK) MUST NOT be consumed into the signature chain — even if -/// its `internalData[0:4]` happens to equal `pId`. -/// -/// Why this matters -/// ---------------- -/// Before `bfbef77` the loop body only filtered on -/// `internalData[0:4] == pId`. A package of the wrong module type could -/// therefore be enrolled in the chain. With at least two signatures in -/// the `PermissionSignature` array, that lets a non-policy module's -/// `validateSignatureWithDataWithSender` stand in for a real policy — -/// i.e. an attacker can install a module of any type whose `internalData` -/// starts with a victim's `pId` and have it satisfy the permission's -/// signature chain. -/// -/// Encoding strategy -/// ----------------- -/// - `packages.length = 2`. Smallest length that actually exercises the -/// bug: length 1 forces `sigIdx == signatures.length - 1` on the very -/// first match, which is blocked by the -/// `require(pkg.moduleType == 6, ...)` "last signature is signer" -/// check on EVERY branch (buggy and fixed). -/// - `pkg[0].moduleType` symbolic over `{1, 2, 3, 4}` — the suspect non -/// policy / non signer types. -/// - `pkg[1].moduleType = 6` (a real `MockSigner`). -/// - Both packages set `internalData[0:4] = pId` so the -/// `bytes4(pkg.internalData) == pId` test always matches. -/// - `signatures.length = 2`, signed bytes are concrete (any value works -/// — the mocks ignore them). -/// - The mocks have their internal `success` flag set to `true`, so any -/// `validateSignatureWithDataWithSender` call they receive returns -/// `true`. This gives the buggy path every chance to (wrongly) return -/// `true` — a counterexample is interesting only when the buggy code -/// actively consumes the wrong-type module. -/// -/// Calldata layout note -/// -------------------- -/// `_verifyStatelessSignature` reads `permissionSig` via the inline -/// assembly trick `permissionSig := signature.offset`. This treats -/// `signature` as the encoded body of a `PermissionSignature calldata` -/// struct (one dynamic field `bytes[] signatures`). The layout is the -/// head (`offset to signatures = 0x20`) followed by the `bytes[]`. -/// `abi.encode(structInstance)` adds an EXTRA wrapper offset that the -/// assembly does NOT expect, so we encode the inner `bytes[]` directly -/// with `abi.encode(sigs)`, which produces exactly the layout above. -/// -/// Expected result -/// --------------- -/// - On the FIXED implementation: every `check…` PASSES. The wrong-type -/// package is filtered out, `sigIdx` never reaches `signatures.length`, -/// and the function reverts with `InvalidPermissionId`. -/// - On the CURRENT (buggy) source at `master @ a836274`: each -/// bug-targeted check FAILS with a counterexample. The counterexamples -/// are the security finding — they demonstrate that any module type -/// whose `internalData` starts with `pId` is enrolled in the signature -/// chain regardless of `moduleType`. -contract PermissionStatelessHalmos is SymTest, Test { - PermissionStatelessHarness harness; - MockValidator validatorModule; // moduleType == 1 - MockHook hookModule; // moduleType == 4 - MockSigner signerModule; // moduleType == 6 - - function setUp() external { - harness = new PermissionStatelessHarness(); - validatorModule = new MockValidator(); - hookModule = new MockHook(); - signerModule = new MockSigner(); - } - - // ------------------------------------------------------------------- - // Sanity: confirm the harness is actually wired up so PASSes on the - // bug-targeted checks above are not vacuous. - // ------------------------------------------------------------------- - - /// @notice With one legitimate signer package matching `pId` and a - /// single-element `signatures` array, the function MUST accept. - /// A failure here means the harness is broken — every other - /// result in this file should be treated as suspect. - function checkSanitySinglePolicyAccepts() external { - _primeMocksForAcceptance(); - bytes4 pIdBytes = svm.createBytes4("pId_sanity"); - bytes memory matchingInternalData = abi.encodePacked(pIdBytes); - ValidationId vId = permissionToIdentifier(PermissionId.wrap(pIdBytes)); - - Install[] memory packages = new Install[](1); - packages[0] = Install({ - moduleType: uint256(6), module: address(signerModule), moduleData: "", internalData: matchingInternalData - }); - - bytes[] memory sigs = new bytes[](1); - sigs[0] = hex"1111"; - bytes memory signature = abi.encode(sigs); - - bytes32 hash = svm.createBytes32("opHash_sanity"); - - bool ok = harness.verifyStatelessExternal(packages, vId, hash, signature); - assertTrue(ok, "single signer package should accept"); - } - - // ------------------------------------------------------------------- - // Bug-targeted checks (regression for bfbef77) - // ------------------------------------------------------------------- - - /// @notice Core regression: a `moduleType == 1` (VALIDATOR) package - /// cannot be enrolled into the permission signature chain just - /// because its `internalData[0:4]` equals `pId`. The function - /// MUST revert with `InvalidPermissionId`. - function checkSigIdxDoesNotAdvanceForModuleType1() external { - _checkRejectsWrongTypeAt0(uint256(1), address(validatorModule)); - } - - /// @notice Same regression for `moduleType == 4` (HOOK) — the other - /// module type explicitly called out in `bfbef77`. We strictly - /// require the revert reason to be `InvalidPermissionId`; any - /// other revert means the loop body ran for the Hook package - /// (e.g. it reached the now-non-existent - /// `validateSignatureWithDataWithSender` selector on - /// `MockHook` and reverted there). - function checkSigIdxDoesNotAdvanceForModuleType4() external { - _checkRejectsWrongTypeAt0(uint256(4), address(hookModule)); - } - - /// @notice Symbolic generalization: quantify over every suspect - /// module type in `{1, 2, 3, 4}` and assert the function - /// rejects all of them. Halmos splits this into one path per - /// type so the counterexamples (on buggy code) identify - /// exactly which types break the property. - function checkSigIdxDoesNotAdvanceForAnyNonPolicySignerType() external { - uint256 wrongType = svm.createUint256("wrongModuleType"); - vm.assume(wrongType == 1 || wrongType == 2 || wrongType == 3 || wrongType == 4); - // We reuse `validatorModule` as the wrong-type stand-in. Its - // `validateSignatureWithDataWithSender` returns `success`, so on - // buggy code we get the strongest possible counterexample - // (function returns `true`). - _checkRejectsWrongTypeAt0(wrongType, address(validatorModule)); - } - - // ------------------------------------------------------------------- - // Internal scaffolding - // ------------------------------------------------------------------- - - /// @dev Set the `success` flag on every mock module to `true`. This - /// gives the buggy path every opportunity to wrongly return - /// `true`, which is the strongest possible counterexample. - function _primeMocksForAcceptance() internal { - validatorModule.sudoSetSuccess(true); - // MockSigner.validateSignatureWithDataWithSender reads the same - // private `success` flag that `sudoSetPass` flips. - signerModule.sudoSetPass(address(harness), bytes32(0), true); - } - - /// @dev Build a 2-element `packages` array where index 0 has the - /// given (wrong) module type and index 1 is a legitimate signer. - /// Both packages set `internalData[0:4] = pId`. Then assert that - /// the call reverts with the precise `InvalidPermissionId` - /// selector. - function _checkRejectsWrongTypeAt0(uint256 wrongType, address wrongModule) internal { - _primeMocksForAcceptance(); - - // Symbolic permission id (any concrete value works, but symbolic - // gives Halmos more freedom). - bytes4 pIdBytes = svm.createBytes4("pId"); - bytes memory matchingInternalData = abi.encodePacked(pIdBytes); - - // High byte 0x02 forces `getType(vId) == VALIDATION_TYPE_PERMISSION` - // so the function dispatches into the loop we care about. - ValidationId vId = permissionToIdentifier(PermissionId.wrap(pIdBytes)); - - Install[] memory packages = new Install[](2); - packages[0] = - Install({moduleType: wrongType, module: wrongModule, moduleData: "", internalData: matchingInternalData}); - packages[1] = Install({ - moduleType: uint256(6), module: address(signerModule), moduleData: "", internalData: matchingInternalData - }); - - // `signatures.length = 2`. Concrete bytes contents — the mocks - // ignore them. - bytes[] memory sigs = new bytes[](2); - sigs[0] = hex"1111"; - sigs[1] = hex"2222"; - // See "Calldata layout note" in the contract NatSpec for why we - // encode the inner bytes[] directly instead of using - // `abi.encode(PermissionSignature(...))`. - bytes memory signature = abi.encode(sigs); - - bytes32 hash = svm.createBytes32("opHash"); - - // The property: the call MUST revert with `InvalidPermissionId`. - // Halmos v0.3.3 does not support `vm.expectRevert(bytes4)`, so we - // use the documented try/catch idiom from the project's FV plan. - try harness.verifyStatelessExternal(packages, vId, hash, signature) returns (bool ok) { - // Any successful return is a violation regardless of value: - // * `true` => buggy code accepted a wrong-type module - // * `false` => the inner stateless call WAS reached and - // returned `false` — i.e. the wrong-type - // package was already consumed. - ok; // silence unused-variable warning - assertTrue(false, "wrong-type module was consumed (non-revert return)"); - } catch (bytes memory reason) { - // The only acceptable revert reason on the fixed code is the - // top-level - // `require(sigIdx == permissionSig.signatures.length, - // InvalidPermissionId())`. - // Anything else (including an empty EVM revert from calling - // a non-existent selector on a Hook / Executor module) means - // the loop body ran for the wrong-type package — bfbef77 bug. - assertTrue(reason.length >= 4, "empty/short revert: wrong-type module was called and reverted"); - bytes4 sel; - assembly { - sel := mload(add(reason, 0x20)) - } - assertEq( - bytes32(sel), - bytes32(InvalidPermissionId.selector), - "wrong revert reason: a non-InvalidPermissionId revert means the wrong-type package was consumed" - ); - } - } -} diff --git a/test/mock/ECDSAValidator.sol b/test/mock/ECDSAValidator.sol index d67f5680..e212f2a5 100644 --- a/test/mock/ECDSAValidator.sol +++ b/test/mock/ECDSAValidator.sol @@ -3,14 +3,12 @@ pragma solidity ^0.8.0; import {ECDSA} from "solady/utils/ECDSA.sol"; -import {IValidator, IStatelessValidatorWithSender} from "src/interfaces/IERC7579Modules.sol"; +import {IValidator} from "src/interfaces/IERC7579Modules.sol"; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; import { SIG_VALIDATION_SUCCESS_UINT, SIG_VALIDATION_FAILED_UINT, MODULE_TYPE_VALIDATOR, - MODULE_TYPE_HOOK, - MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER, ERC1271_MAGICVALUE, ERC1271_INVALID } from "src/types/Constants.sol"; @@ -21,7 +19,7 @@ struct ECDSAValidatorStorage { address owner; } -contract ECDSAValidator is IValidator, IStatelessValidatorWithSender { +contract ECDSAValidator is IValidator { event OwnerRegistered(address indexed kernel, address indexed owner); mapping(address => ECDSAValidatorStorage) public ecdsaValidatorStorage; @@ -38,8 +36,7 @@ contract ECDSAValidator is IValidator, IStatelessValidatorWithSender { } function isModuleType(uint256 typeId) external pure override returns (bool) { - return typeId == MODULE_TYPE_VALIDATOR || typeId == MODULE_TYPE_HOOK - || typeId == MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER; + return typeId == MODULE_TYPE_VALIDATOR; } function isInitialized(address smartAccount) external view override returns (bool) { @@ -81,15 +78,4 @@ contract ECDSAValidator is IValidator, IStatelessValidatorWithSender { } return ERC1271_MAGICVALUE; } - - function validateSignatureWithDataWithSender(address, bytes32 hash, bytes calldata signature, bytes calldata data) - external - view - returns (bool) - { - require(data.length == 20, "Invalid Data Length"); - // forge-lint: disable-next-line(unsafe-typecast) - address owner = address(bytes20(data)); - return owner == ECDSA.tryRecoverCalldata(hash, signature); - } } diff --git a/test/mock/MockPolicy.sol b/test/mock/MockPolicy.sol index 50865e02..c73d4b4d 100644 --- a/test/mock/MockPolicy.sol +++ b/test/mock/MockPolicy.sol @@ -9,7 +9,6 @@ contract MockPolicy is IPolicy { mapping(address => mapping(bytes32 => bool)) public pass; mapping(address => bytes) public installData; mapping(address => mapping(bytes32 => bytes)) public sig; - bool success; uint256 public customValidationData; function onInstall(bytes calldata data) external payable override { @@ -23,7 +22,6 @@ contract MockPolicy is IPolicy { } function sudoSetPass(address _wallet, bytes32 _id, bool _pass) external payable { - success = _pass; pass[_wallet][_id] = _pass; } @@ -60,12 +58,4 @@ contract MockPolicy is IPolicy { { return pass[msg.sender][id] ? 0 : 1; } - - function validateSignatureWithDataWithSender(address, bytes32, bytes calldata signature, bytes calldata) - external - view - returns (bool) - { - return success; - } } diff --git a/test/mock/MockSigner.sol b/test/mock/MockSigner.sol index 1c2326b3..aa9bca1c 100644 --- a/test/mock/MockSigner.sol +++ b/test/mock/MockSigner.sol @@ -9,7 +9,6 @@ contract MockSigner is ISigner { mapping(address wallet => bytes) public data; mapping(address => mapping(bytes32 => bytes)) public sig; mapping(address => mapping(bytes32 => bool)) public pass; - bool success; uint256 public customValidationData; function sudoSetValidSig(address _wallet, bytes32 _id, bytes calldata _sig) external payable { @@ -17,7 +16,6 @@ contract MockSigner is ISigner { } function sudoSetPass(address _wallet, bytes32 _id, bool _flag) external payable { - success = _flag; pass[_wallet][_id] = _flag; } @@ -63,12 +61,4 @@ contract MockSigner is ISigner { return 0xffffffff; } } - - function validateSignatureWithDataWithSender(address, bytes32, bytes calldata signature, bytes calldata) - external - view - returns (bool) - { - return success; - } } From be3e01095edc50bdcf59ecb0d98e4bef4282d394 Mon Sep 17 00:00:00 2001 From: taek Date: Sun, 9 Aug 2026 07:48:14 +0900 Subject: [PATCH 7/8] fix: gate unhooked fallback selectors to entry point only Restore the v0.4.0 entry-point gate on fallback selectors that have no scoped execution hook installed. Installing a type-11 selector-scoped execution hook makes a selector publicly callable; without one only the entry point may route to it. Update tests to install scoped hooks where public access is asserted, add a regression test and halmos proofs for the gate, and document the access control in the README. --- README.md | 2 + src/Kernel.sol | 8 +- test/KernelSelectorTest.sol | 30 +++++ test/btt/Kernel.fallback.t.sol | 44 +++++++- test/fuzz/KernelFuzz.t.sol | 32 +++++- test/halmos/KernelFallbackHalmos.t.sol | 48 +++++++- .../KernelIntegrationEdgeCases.t.sol | 105 +++++++++++------- test/unit/GasBenchmark.t.sol | 22 +++- test/unit/KernelCoverage.t.sol | 22 +++- 9 files changed, 253 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 6709e446..3234df34 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ The 32-byte ERC-4337 nonce encodes which validator to use, giving each validator An optional type-11 scoped execution hook runs before and after execution in one of three scopes: validation, executor, or selector. Hooked non-root validations are routed through `executeUserOp`; root validations bypass hooks. Executor hooks wrap `executeFromExecutor`, and selector hooks wrap fallback selector dispatch. Selector hooks apply only to calls that reach Kernel's fallback; native Kernel function dispatch bypasses them, and built-in token-receiver selectors cannot be installed as fallback targets. +**Fallback access control:** a fallback selector with no scoped execution hook installed is callable only by the EntryPoint. Installing a selector-scoped execution hook makes the selector publicly callable, with the hook gating direct access via `preCheck`/`postCheck`. + `preCheck` and `postCheck` receive the same Kernel-generated `bytes32 id`. The ID layout is `[bytes1 scope][target][zero padding]`, where the target is a 21-byte ValidationId, 20-byte executor address, or 4-byte selector. `Utils.sol` exposes generation and decoding helpers for every scope. ### Signature Verification (ERC-1271 / ERC-7739) diff --git a/src/Kernel.sol b/src/Kernel.sol index 00c0f644..9efa5eac 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -287,7 +287,13 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { bytes4 selector = bytes4(msg.data[0:4]); SelectorConfig storage $ = _selectorConfig(selector); - require($.target != address(0), InvalidSelector()); + // target must be initialized, and if no scoped execution hook is installed + // only the entry point may call it (scoped hooks gate direct access). + require( + $.target != address(0) + && (address($.scopedExecutionHook) != address(0) || msg.sender == address(ENTRYPOINT)), + InvalidSelector() + ); IScopedExecutionHook hook = $.scopedExecutionHook; bytes32 id; diff --git a/test/KernelSelectorTest.sol b/test/KernelSelectorTest.sol index 7232218f..5608df83 100644 --- a/test/KernelSelectorTest.sol +++ b/test/KernelSelectorTest.sol @@ -24,6 +24,16 @@ abstract contract KernelSelectorTest is KernelTestBase { address(mockFallback), abi.encode(hex"deadbeef", abi.encodePacked(MockFallback.fallbackFunction.selector, bytes1(0x00))) ); + // Selectors without a scoped execution hook are EntryPoint-only; install a + // passthrough scoped hook so the selector can be called by arbitrary callers. + kernel.installModule( + 11, + address(hook), + abi.encode( + hex"deadbeef", + abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, MockFallback.fallbackFunction.selector) + ) + ); vm.stopPrank(); vm.startPrank(newCaller); vm.expectEmit(address(mockFallback)); @@ -40,6 +50,26 @@ abstract contract KernelSelectorTest is KernelTestBase { ); } + function test_install_selector_without_hook_is_entrypoint_only() external unitTest { + bytes4 selector = MockFallback.fallbackFunction.selector; + kernel.installModule( + 3, address(mockFallback), abi.encode(hex"deadbeef", abi.encodePacked(selector, bytes1(0x00))) + ); + vm.stopPrank(); + + // Selectors without a scoped execution hook are only callable by the EntryPoint. + address newCaller = makeAddr("Caller"); + vm.startPrank(newCaller); + vm.expectRevert(InvalidSelector.selector); + MockFallback(address(kernel)).fallbackFunction(10); + vm.stopPrank(); + + vm.startPrank(address(ep)); + uint256 res = MockFallback(address(kernel)).fallbackFunction(10); + assertEq(res, 100); + vm.stopPrank(); + } + function test_selector_scoped_execution_hook() external unitTest { bytes4 selector = MockFallback.fallbackFunction.selector; bytes memory hookContext = abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, selector); diff --git a/test/btt/Kernel.fallback.t.sol b/test/btt/Kernel.fallback.t.sol index 236af917..82d8d0d4 100644 --- a/test/btt/Kernel.fallback.t.sol +++ b/test/btt/Kernel.fallback.t.sol @@ -5,7 +5,7 @@ import {BTTModifiers} from "./BTTModifiers.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol"; import {InvalidSelector, InvalidCallType} from "src/types/Error.sol"; -import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL} from "src/types/Constants.sol"; +import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE} from "src/types/Constants.sol"; import {MockFallback} from "../mock/MockFallback.sol"; /// @notice Fallback routing tests after removal of generic fallback hooks. @@ -44,36 +44,68 @@ abstract contract Kernel_fallback is BTTModifiers { } function test_WhenSingleCallFallbackIsInstalled() external { - vm.prank(address(ep)); + vm.startPrank(address(ep)); kernel.installModule( 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(MockFallback.testFunction.selector, CALLTYPE_SINGLE)) ); + // Selectors without a scoped execution hook are EntryPoint-only; install a + // passthrough scoped hook so the selector is publicly callable. + kernel.installModule( + 11, + address(hook), + abi.encode( + hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, MockFallback.testFunction.selector) + ) + ); + vm.stopPrank(); - assertEq(MockFallback(address(kernel)).testFunction(), 42); + // testFunction() is view, so use a raw call (non-static) to let the + // scoped hook run pre/post checks. + (bool success, bytes memory ret) = address(kernel).call(abi.encodePacked(MockFallback.testFunction.selector)); + assertTrue(success); + assertEq(abi.decode(ret, (uint256)), 42); } function test_WhenDelegatecallFallbackIsInstalled() external { - vm.prank(address(ep)); + vm.startPrank(address(ep)); kernel.installModule( 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(MockFallback.fallbackFunction.selector, CALLTYPE_DELEGATECALL)) ); + kernel.installModule( + 11, + address(hook), + abi.encode( + hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, MockFallback.fallbackFunction.selector) + ) + ); + vm.stopPrank(); assertEq(MockFallback(address(kernel)).fallbackFunction(5), 25); } function test_WhenFallbackCallTypeIsInvalid() external { - vm.prank(address(ep)); + vm.startPrank(address(ep)); kernel.installModule( 3, address(mockFallback), abi.encode(hex"", abi.encodePacked(MockFallback.testFunction.selector, bytes1(0x02))) ); + // A scoped hook bypasses the EntryPoint-only gate so the call reaches the + // invalid-callType check. + kernel.installModule( + 11, + address(hook), + abi.encode( + hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, MockFallback.testFunction.selector) + ) + ); + vm.stopPrank(); vm.expectRevert(InvalidCallType.selector); - MockFallback(address(kernel)).testFunction(); + address(kernel).call(abi.encodePacked(MockFallback.testFunction.selector)); } } diff --git a/test/fuzz/KernelFuzz.t.sol b/test/fuzz/KernelFuzz.t.sol index 4c70962c..a62a2d6b 100644 --- a/test/fuzz/KernelFuzz.t.sol +++ b/test/fuzz/KernelFuzz.t.sol @@ -22,7 +22,8 @@ import { MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_POLICY, - MODULE_TYPE_SIGNER + MODULE_TYPE_SIGNER, + SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE } from "src/types/Constants.sol"; import { Unauthorized, @@ -40,6 +41,7 @@ import {EntryPointLib} from "../utils/EntryPointLib.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; import {MockFallback} from "../mock/MockFallback.sol"; +import {MockHook} from "../mock/MockHook.sol"; import {MockPolicy} from "../mock/MockPolicy.sol"; import {MockSigner} from "../mock/MockSigner.sol"; import {IERC7579Account} from "src/interfaces/IERC7579Account.sol"; @@ -158,9 +160,9 @@ contract KernelFuzz is Test { (success); } - /// @dev Installed fallback selectors are callable without a generic hook. - function testFuzz_fallback_installedSelector_anyoneCalls(address caller) public { - vm.assume(caller != address(0)); + /// @dev Installed fallback selectors without a scoped execution hook are EntryPoint-only. + function testFuzz_fallback_installedSelector_withoutHook_nonEpReverts(address caller) public { + vm.assume(caller != address(ep)); bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); @@ -170,7 +172,27 @@ contract KernelFuzz is Test { vm.prank(caller); (bool success,) = address(kernel).call(abi.encodePacked(selector)); - assertTrue(success, "installed selector should be callable"); + assertFalse(success, "unhooked selector must not be callable by non-EP callers"); + } + + /// @dev Installed fallback selectors with a scoped execution hook are callable by anyone. + function testFuzz_fallback_installedSelector_withHook_anyoneCalls(address caller) public { + vm.assume(caller != address(0)); + + MockHook h = new MockHook(); + bytes4 selector = MockFallback.testFunction.selector; + bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); + bytes memory internalData = abi.encodePacked(selector, callType); + vm.startPrank(address(ep)); + kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); + kernel.installModule( + 11, address(h), abi.encode(hex"deadbeef", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, selector)) + ); + vm.stopPrank(); + + vm.prank(caller); + (bool success,) = address(kernel).call(abi.encodePacked(selector)); + assertTrue(success, "hooked selector should be callable by anyone"); } // ========= isValidSignature fuzz tests ========= diff --git a/test/halmos/KernelFallbackHalmos.t.sol b/test/halmos/KernelFallbackHalmos.t.sol index 7502cc85..986b0924 100644 --- a/test/halmos/KernelFallbackHalmos.t.sol +++ b/test/halmos/KernelFallbackHalmos.t.sol @@ -9,17 +9,20 @@ import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; import {KernelFactory} from "src/KernelFactory.sol"; import {Install, SelectorConfig} from "src/types/Structs.sol"; import {CallType} from "src/types/Types.sol"; -import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL} from "src/types/Constants.sol"; +import {CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE} from "src/types/Constants.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockFallback} from "../mock/MockFallback.sol"; +import {MockHook} from "../mock/MockHook.sol"; /// @title KernelFallbackHalmos -/// @notice Halmos proofs that _fallback can never route to an uninstalled module +/// @notice Halmos proofs that _fallback can never route to an uninstalled module and +/// that selectors without a scoped execution hook are EntryPoint-only. contract KernelFallbackHalmos is SymTest, Test { Kernel private kernel; IEntryPoint private ep; MockFallback private fallbackModule; + MockHook private scopedHook; function setUp() external { ep = EntryPointLib.deploy(); @@ -31,6 +34,7 @@ contract KernelFallbackHalmos is SymTest, Test { pkgs[0] = Install({moduleType: 1, module: address(rootValidator), moduleData: hex"", internalData: hex""}); kernel = factory.deploy(pkgs, 0); fallbackModule = new MockFallback(); + scopedHook = new MockHook(); } /// @notice Prove that calling a selector with no installed fallback always reverts @@ -64,7 +68,7 @@ contract KernelFallbackHalmos is SymTest, Test { assertFalse(success, "call to uninstalled selector should revert"); } - /// @notice Prove that install sets correct target and callType + /// @notice Prove that install sets correct target, callType, and scoped execution hook function check_FallbackInstallSetsCorrectConfig() external { bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); @@ -72,21 +76,32 @@ contract KernelFallbackHalmos is SymTest, Test { vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); + kernel.installModule( + 11, + address(scopedHook), + abi.encode(hex"deadbeef", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, selector)) + ); vm.stopPrank(); SelectorConfig memory cfg = kernel.selectorConfig(selector); assertEq(cfg.target, address(fallbackModule)); assertEq(CallType.unwrap(cfg.callType), callType); + assertEq(address(cfg.scopedExecutionHook), address(scopedHook)); } - /// @notice Prove an installed fallback allows any caller - function check_FallbackAllowsAnyCaller() external { + /// @notice Prove an installed fallback with a scoped execution hook allows any caller + function check_FallbackWithScopedHookAllowsAnyCaller() external { bytes4 selector = MockFallback.testFunction.selector; bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); bytes memory internalData = abi.encodePacked(selector, callType); vm.startPrank(address(ep)); kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); + kernel.installModule( + 11, + address(scopedHook), + abi.encode(hex"deadbeef", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, selector)) + ); vm.stopPrank(); address caller = address(0xBEEF); @@ -95,4 +110,27 @@ contract KernelFallbackHalmos is SymTest, Test { vm.stopPrank(); assertTrue(success); } + + /// @notice Prove an installed fallback without a scoped execution hook is EntryPoint-only + function check_FallbackWithoutHookIsEntryPointOnly() external { + bytes4 selector = MockFallback.testFunction.selector; + bytes1 callType = CallType.unwrap(CALLTYPE_SINGLE); + bytes memory internalData = abi.encodePacked(selector, callType); + + vm.startPrank(address(ep)); + kernel.installModule(3, address(fallbackModule), abi.encode(hex"deadbeef", internalData)); + vm.stopPrank(); + + // Non-EntryPoint callers must revert. + vm.startPrank(address(0xBEEF)); + (bool successNonEp,) = address(kernel).call(abi.encodePacked(selector, bytes20(address(0x1234)))); + vm.stopPrank(); + assertFalse(successNonEp, "unhooked selector must revert for non-EP callers"); + + // The EntryPoint may call the unhooked selector. + vm.startPrank(address(ep)); + (bool successEp,) = address(kernel).call(abi.encodePacked(selector, bytes20(address(0x1234)))); + vm.stopPrank(); + assertTrue(successEp, "unhooked selector must be callable by the entry point"); + } } diff --git a/test/integration/KernelIntegrationEdgeCases.t.sol b/test/integration/KernelIntegrationEdgeCases.t.sol index b9538267..2ce4c4ae 100644 --- a/test/integration/KernelIntegrationEdgeCases.t.sol +++ b/test/integration/KernelIntegrationEdgeCases.t.sol @@ -8,13 +8,14 @@ import {Kernel} from "src/Kernel.sol"; import {KernelUUPS} from "src/KernelUUPS.sol"; import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; import {KernelFactory} from "src/KernelFactory.sol"; -import {Install, ValidationInfo} from "src/types/Structs.sol"; -import {ERC1967_IMPLEMENTATION_SLOT} from "src/types/Constants.sol"; +import {Install, ValidationInfo, Call} from "src/types/Structs.sol"; +import {ERC1967_IMPLEMENTATION_SLOT, SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE} from "src/types/Constants.sol"; import {ValidationId, PermissionId} from "src/types/Types.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; import {InvalidSelector} from "src/types/Error.sol"; import {IERC7579Account} from "src/interfaces/IERC7579Account.sol"; import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; +import {LibERC7579} from "solady/accounts/LibERC7579.sol"; import {MockValidator} from "../mock/MockValidator.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; import {MockCallee} from "../mock/MockCallee.sol"; @@ -337,37 +338,51 @@ contract KernelIntegrationEdgeCasesTest is Test { // Test 4: Fallback module lifecycle // ----------------------------------------------------------------------- - /// @notice Install a fallback module via UserOp, call it externally, - /// uninstall it, verify it reverts. + /// @notice Install a fallback module + scoped execution hook via UserOp, call it externally, + /// uninstall both, verify the call reverts. function test_fallbackModuleLifecycle() public { Kernel kernel = _deployKernel(); MockFallback fb = new MockFallback(); + MockHook h = new MockHook(); rootValidator.sudoSetSuccess(true); - // Install fallback via UserOp through root - // Fallback internalData: [selector(4) | callType(1) | hookAddress(20)] - // callType 0x00 = CALLTYPE_SINGLE, hook address(1) = no hook, anyone can call + // Fallback internalData: [selector(4) | callType(1)] + // callType 0x00 = CALLTYPE_SINGLE. Selectors without a scoped execution hook are + // EntryPoint-only, so a scoped execution hook is installed to make it publicly callable. bytes4 fbSelector = MockFallback.fallbackFunction.selector; + bytes32 batchMode = bytes32( + abi.encodePacked(LibERC7579.CALLTYPE_BATCH, LibERC7579.EXECTYPE_DEFAULT, bytes4(0), bytes4(0), bytes22(0)) + ); + + // Install fallback + scoped hook via a single UserOp with a batch execute { uint256 nonce = _rootNonce(address(kernel)); + Call[] memory calls = new Call[](2); + calls[0] = Call({ + to: address(kernel), + value: 0, + data: abi.encodeWithSelector( + IERC7579Account.installModule.selector, + uint256(3), + address(fb), + abi.encode(hex"deadbeef", abi.encodePacked(fbSelector, bytes1(0x00))) + ) + }); + calls[1] = Call({ + to: address(kernel), + value: 0, + data: abi.encodeWithSelector( + IERC7579Account.installModule.selector, + uint256(11), + address(h), + abi.encode(hex"deadbeef", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, fbSelector)) + ) + }); PackedUserOperation memory installOp = PackedUserOperation({ sender: address(kernel), nonce: nonce, initCode: hex"", - callData: abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked( - address(kernel), - uint256(0), - abi.encodeWithSelector( - IERC7579Account.installModule.selector, - uint256(3), - address(fb), - abi.encode(hex"deadbeef", abi.encodePacked(fbSelector, bytes1(0x00))) - ) - ) - ), + callData: abi.encodeWithSelector(Kernel.execute.selector, batchMode, abi.encode(calls)), accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), preVerificationGas: 1_000_000, gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), @@ -379,12 +394,18 @@ contract KernelIntegrationEdgeCasesTest is Test { _handleOps(ops); } - // Verify: fallback is installed + // Verify: fallback and scoped hook are installed assertTrue( kernel.isModuleInstalled(3, address(fb), abi.encodePacked(fbSelector)), "fallback should be installed" ); + assertTrue( + kernel.isModuleInstalled( + 11, address(h), abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, fbSelector) + ), + "scoped execution hook should be installed" + ); - // Call the installed fallback externally + // Call the installed fallback externally (scoped hook makes it publicly callable) address alice = makeAddr("Alice"); vm.prank(alice); (bool success, bytes memory ret) = address(kernel).call(abi.encodeWithSelector(fbSelector, uint256(5))); @@ -392,27 +413,35 @@ contract KernelIntegrationEdgeCasesTest is Test { uint256 result = abi.decode(ret, (uint256)); assertEq(result, 25, "fallbackFunction(5) should return 25 (5*5)"); - // Uninstall fallback via UserOp + // Uninstall hook + fallback via a single UserOp with a batch execute { uint256 nonce = _rootNonce(address(kernel)); + Call[] memory calls = new Call[](2); + calls[0] = Call({ + to: address(kernel), + value: 0, + data: abi.encodeWithSelector( + IERC7579Account.uninstallModule.selector, + uint256(11), + address(h), + abi.encode(hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, fbSelector)) + ) + }); + calls[1] = Call({ + to: address(kernel), + value: 0, + data: abi.encodeWithSelector( + IERC7579Account.uninstallModule.selector, + uint256(3), + address(fb), + abi.encode(hex"", abi.encodePacked(fbSelector)) + ) + }); PackedUserOperation memory uninstallOp = PackedUserOperation({ sender: address(kernel), nonce: nonce, initCode: hex"", - callData: abi.encodeWithSelector( - Kernel.execute.selector, - bytes32(0), - abi.encodePacked( - address(kernel), - uint256(0), - abi.encodeWithSelector( - IERC7579Account.uninstallModule.selector, - uint256(3), - address(fb), - abi.encode(hex"", abi.encodePacked(fbSelector)) - ) - ) - ), + callData: abi.encodeWithSelector(Kernel.execute.selector, batchMode, abi.encode(calls)), accountGasLimits: bytes32(abi.encodePacked(uint128(2_000_000), uint128(2_000_000))), preVerificationGas: 1_000_000, gasFees: bytes32(abi.encodePacked(uint128(1), uint128(1))), diff --git a/test/unit/GasBenchmark.t.sol b/test/unit/GasBenchmark.t.sol index f2a71689..cef08b01 100644 --- a/test/unit/GasBenchmark.t.sol +++ b/test/unit/GasBenchmark.t.sol @@ -9,9 +9,16 @@ import {KernelImmutableECDSA} from "src/KernelImmutableECDSA.sol"; import {KernelFactory} from "src/KernelFactory.sol"; import {MockFallback} from "../mock/MockFallback.sol"; import {MockValidator} from "../mock/MockValidator.sol"; +import {MockHook} from "../mock/MockHook.sol"; import {Install} from "src/types/Structs.sol"; import {EntryPointLib} from "../utils/EntryPointLib.sol"; -import {CALLTYPE_SINGLE, MODULE_TYPE_VALIDATOR, MODULE_TYPE_FALLBACK} from "src/types/Constants.sol"; +import { + CALLTYPE_SINGLE, + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_SCOPED_EXECUTION_HOOK, + SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE +} from "src/types/Constants.sol"; /// @title Gas Benchmark Tests /// @notice Focused gas benchmarks for specific optimization paths @@ -21,6 +28,7 @@ contract GasBenchmarkTest is Test { Kernel kernel; MockValidator validator; MockFallback mockFallback; + MockHook mockHook; function setUp() public { ep = EntryPointLib.deploy(); @@ -30,6 +38,7 @@ contract GasBenchmarkTest is Test { factory = new KernelFactory(uups, immutableEcdsa); validator = new MockValidator(); mockFallback = new MockFallback(); + mockHook = new MockHook(); // Deploy kernel via factory Install[] memory packages = new Install[](1); @@ -44,6 +53,15 @@ contract GasBenchmarkTest is Test { // internalData format: selector(4) + callType(1) bytes memory internalData = abi.encodePacked(MockFallback.fallbackFunction.selector, CALLTYPE_SINGLE); kernel.installModule(MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", internalData)); + // Selectors without a scoped execution hook are EntryPoint-only; installing one + // (MockHook is a passthrough) makes the fallback benchmark publicly callable. + kernel.installModule( + MODULE_TYPE_SCOPED_EXECUTION_HOOK, + address(mockHook), + abi.encode( + hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, MockFallback.fallbackFunction.selector) + ) + ); vm.stopPrank(); } @@ -75,7 +93,7 @@ contract GasBenchmarkTest is Test { vm.stopPrank(); } - /// @notice Benchmark: installed fallback routing + /// @notice Benchmark: fallback routing through a scoped execution hook (required for public calls) function test_gasBenchmark_fallback() public { uint256 gasBefore = gasleft(); MockFallback(address(kernel)).fallbackFunction(5); diff --git a/test/unit/KernelCoverage.t.sol b/test/unit/KernelCoverage.t.sol index df9c7fb8..69e0f370 100644 --- a/test/unit/KernelCoverage.t.sol +++ b/test/unit/KernelCoverage.t.sol @@ -18,6 +18,7 @@ import {MockSigner} from "../mock/MockSigner.sol"; import {MockFallback} from "../mock/MockFallback.sol"; import {MockExecutor} from "../mock/MockExecutor.sol"; import {MockCallee} from "../mock/MockCallee.sol"; +import {MockHook} from "../mock/MockHook.sol"; import {ChainAgnosticHashHelper} from "../utils/ChainAgnosticHashHelper.sol"; import { NotImplemented, @@ -60,6 +61,8 @@ import { VALIDATION_TYPE_ROOT, VALIDATION_TYPE_VALIDATOR, VALIDATION_TYPE_PERMISSION, + MODULE_TYPE_SCOPED_EXECUTION_HOOK, + SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, SELECTOR_MANAGER_STORAGE_SLOT } from "src/types/Constants.sol"; import {validatorToIdentifier, permissionToIdentifier} from "src/lib/Utils.sol"; @@ -80,6 +83,7 @@ contract KernelCoverageTest is Test { MockFallback mockFallback; MockExecutor mockExecutor; MockCallee callee; + MockHook mockHook; address payable beneficiary; PermissionId permissionId; ChainAgnosticHashHelper hashHelper; @@ -96,6 +100,7 @@ contract KernelCoverageTest is Test { mockFallback = new MockFallback(); mockExecutor = new MockExecutor(); callee = new MockCallee(); + mockHook = new MockHook(); beneficiary = payable(makeAddr("Beneficiary")); hashHelper = new ChainAgnosticHashHelper(); permissionId = PermissionId.wrap(bytes4(keccak256(abi.encodePacked("TestPermission")))); @@ -690,13 +695,24 @@ contract KernelCoverageTest is Test { function test_fallback_WhenCallTypeDelegatecall_ShouldDelegatecall() public { bytes4 testSel = MockFallback.testFunction.selector; - vm.prank(address(ep)); + vm.startPrank(address(ep)); kernel.installModule( MODULE_TYPE_FALLBACK, address(mockFallback), abi.encode(hex"", abi.encodePacked(testSel, bytes1(0xFF))) ); + // Selectors without a scoped execution hook are EntryPoint-only; install a + // passthrough scoped hook so the delegatecall fallback is publicly callable. + kernel.installModule( + MODULE_TYPE_SCOPED_EXECUTION_HOOK, + address(mockHook), + abi.encode(hex"", abi.encodePacked(SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, testSel)) + ); + vm.stopPrank(); - // Anyone can call an installed fallback - uint256 result = MockFallback(address(kernel)).testFunction(); + // Anyone can call an installed fallback that has a scoped execution hook + // (testFunction() is view, so use a raw non-static call to allow the hook's pre/post checks) + (bool success, bytes memory ret) = address(kernel).call(abi.encodePacked(testSel)); + assertTrue(success, "Delegatecall fallback should succeed"); + uint256 result = abi.decode(ret, (uint256)); assertEq(result, 42, "Delegatecall fallback should return 42"); } From 62db9981ab921129be67637df9a5f3095dda2749 Mon Sep 17 00:00:00 2001 From: taek Date: Sun, 9 Aug 2026 08:26:17 +0900 Subject: [PATCH 8/8] refactor: centralize sentinel constants --- src/Kernel.sol | 30 ++++++----- src/core/ExecutorManager.sol | 7 ++- src/core/ModuleManager.sol | 95 +++++++++++++++++++++++++--------- src/core/SelectorManager.sol | 18 +++++-- src/core/ValidationManager.sol | 21 +++++--- src/lib/Lib4337.sol | 29 ++++++----- src/types/Constants.sol | 8 +++ 7 files changed, 146 insertions(+), 62 deletions(-) diff --git a/src/Kernel.sol b/src/Kernel.sol index 9efa5eac..7d382a12 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -52,7 +52,10 @@ import { MODULE_TYPE_FALLBACK, MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER, - MODULE_TYPE_SCOPED_EXECUTION_HOOK + MODULE_TYPE_SCOPED_EXECUTION_HOOK, + SELECTOR_NOT_INSTALLED, + SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + SIG_VALIDATION_FAILED_UINT } from "./types/Constants.sol"; import { ValidationStorage, @@ -165,7 +168,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { validationData = _verifyInstallSignatureRaw(enableReplayable, sig.nonce, sig.packages, sig.enableSignature); // EntryPoint owns validity-window enforcement. Reject hard signature failures before // mutating state; EntryPoint rolls back installs for all other invalid results. - if (uint160(validationData) == 1) { + if (uint160(validationData) == SIG_VALIDATION_FAILED_UINT) { return validationData; } _checkAndIncrementNonce(sig.nonce); @@ -178,7 +181,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { if (vType != VALIDATION_TYPE_ROOT) { ValidationInfo storage info = $.vInfo[vId]; require(info.installed, InvalidVid(vId)); - bool hasScopedExecutionHook = address(info.scopedExecutionHook) != address(0); + bool hasScopedExecutionHook = address(info.scopedExecutionHook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED; // Validation-scoped hooks must wrap execution even when the outer selector is directly allowed. if (hasScopedExecutionHook || !_allowedSelector(vId, callDataSelector)) { require( @@ -211,7 +214,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { (ValidationId vId, IScopedExecutionHook hook) = _validationScopedExecutionHook(userOpHash); bytes32 hookId; bytes memory context; - if (address(hook) != address(0)) { + if (address(hook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { hookId = _validationScopedExecutionHookId(vId); context = hook.preCheck(hookId, msg.sender, msg.value, userOp.callData[4:]); } @@ -222,7 +225,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { revert(add(ret, 0x20), mload(ret)) } } - if (address(hook) != address(0)) { + if (address(hook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { hook.postCheck(hookId, context); } } @@ -257,12 +260,12 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { IScopedExecutionHook hook = config.scopedExecutionHook; bytes32 id; bytes memory context; - if (address(hook) != address(0)) { + if (address(hook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { id = executorScopedExecutionHookId(msg.sender); context = hook.preCheck(id, msg.sender, msg.value, msg.data); } returnData = _execute(mode, executionData); - if (address(hook) != address(0)) { + if (address(hook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { hook.postCheck(id, context); } } @@ -290,15 +293,16 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { // target must be initialized, and if no scoped execution hook is installed // only the entry point may call it (scoped hooks gate direct access). require( - $.target != address(0) - && (address($.scopedExecutionHook) != address(0) || msg.sender == address(ENTRYPOINT)), + $.target != SELECTOR_NOT_INSTALLED + && (address($.scopedExecutionHook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED + || msg.sender == address(ENTRYPOINT)), InvalidSelector() ); IScopedExecutionHook hook = $.scopedExecutionHook; bytes32 id; bytes memory context; - if (address(hook) != address(0)) { + if (address(hook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { id = selectorScopedExecutionHookId(selector); context = hook.preCheck(id, msg.sender, msg.value, msg.data); } @@ -316,7 +320,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { } else { res = _getReturn(); } - if (address(hook) != address(0)) { + if (address(hook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { hook.postCheck(id, context); } } @@ -382,7 +386,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { ValidationInfo memory vInfo = _validationStorage().vInfo[vId]; if (vType == VALIDATION_TYPE_VALIDATOR) { bytes calldata validatorUninstallData = uninstallData; - if (address(vInfo.scopedExecutionHook) != address(0)) { + if (address(vInfo.scopedExecutionHook) != SCOPED_EXECUTION_HOOK_NOT_INSTALLED) { ValidationUninstallData calldata data; assembly { data := uninstallData.offset @@ -408,7 +412,7 @@ abstract contract Kernel is ModuleManager, ExecutionManager, IERC7579Account { data := uninstallData.offset } bytes[] calldata uninstallDataArr = data.uninstallData; - uint256 hookOffset = address(vInfo.scopedExecutionHook) == address(0) ? 0 : 1; + uint256 hookOffset = address(vInfo.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED ? 0 : 1; require(uninstallDataArr.length == vInfo.policies.length + 1 + hookOffset, InvalidDataLength()); if (hookOffset == 1) { // forge-lint: disable-next-line(unchecked-call) diff --git a/src/core/ExecutorManager.sol b/src/core/ExecutorManager.sol index ab5d3323..7beedc05 100644 --- a/src/core/ExecutorManager.sol +++ b/src/core/ExecutorManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {EXECUTOR_MANAGER_STORAGE_SLOT} from "../types/Constants.sol"; +import {EXECUTOR_MANAGER_STORAGE_SLOT, SCOPED_EXECUTION_HOOK_NOT_INSTALLED} from "../types/Constants.sol"; import {IExecutor} from "../interfaces/IERC7579Modules.sol"; import {ExecutorStorage, ExecutorConfig} from "../types/Structs.sol"; import {InvalidDataLength, ScopedExecutionHookStillInstalled} from "../types/Error.sol"; @@ -37,7 +37,10 @@ abstract contract ExecutorManager { function _uninstallExecutor(address _executor, bytes calldata _internalData, bool) internal { require(_internalData.length == 0, InvalidDataLength()); ExecutorConfig storage config = _executorConfig(IExecutor(_executor)); - require(address(config.scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); + require( + address(config.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + ScopedExecutionHookStillInstalled() + ); config.installed = false; } } diff --git a/src/core/ModuleManager.sol b/src/core/ModuleManager.sol index 726c5d73..d8d28956 100644 --- a/src/core/ModuleManager.sol +++ b/src/core/ModuleManager.sol @@ -36,7 +36,13 @@ import { MODULE_TYPE_SCOPED_EXECUTION_HOOK, SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE, SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE, - SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE + SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE, + SELECTOR_NOT_INSTALLED, + SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + SCOPED_EXECUTION_HOOK_TARGET_OFFSET, + SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH, + SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH, + SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH } from "../types/Constants.sol"; import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; @@ -142,8 +148,10 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM require(installSuccess && hook.code.length > 0, ModuleInstallFailed()); bytes1 scope = _scopedExecutionHookScope(internalData); if (scope == SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) { - require(internalData.length == 22, InvalidDataLength()); - ValidationId vId = ValidationId.wrap(bytes21(internalData[1:22])); + require(internalData.length == SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH, InvalidDataLength()); + ValidationId vId = ValidationId.wrap( + bytes21(internalData[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH]) + ); ValidationType vType = getType(vId); require( vType == VALIDATION_TYPE_VALIDATOR || vType == VALIDATION_TYPE_PERMISSION, @@ -151,18 +159,31 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM ); _installValidationScopedExecutionHook(hook, vId, installSuccess); } else if (scope == SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) { - require(internalData.length == 21, InvalidDataLength()); - IExecutor executor = IExecutor(address(bytes20(internalData[1:21]))); + require(internalData.length == SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH, InvalidDataLength()); + IExecutor executor = IExecutor( + address( + bytes20( + internalData[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH] + ) + ) + ); ExecutorConfig storage config = _executorConfig(executor); require(config.installed, InvalidScopedExecutionHookTarget()); - require(address(config.scopedExecutionHook) == address(0), ScopedExecutionHookAlreadyInstalled()); + require( + address(config.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + ScopedExecutionHookAlreadyInstalled() + ); config.scopedExecutionHook = IScopedExecutionHook(hook); } else if (scope == SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) { - require(internalData.length == 5, InvalidDataLength()); - bytes4 selector = bytes4(internalData[1:5]); + require(internalData.length == SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH, InvalidDataLength()); + bytes4 selector = + bytes4(internalData[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH]); SelectorConfig storage config = _selectorConfig(selector); - require(config.target != address(0), InvalidScopedExecutionHookTarget()); - require(address(config.scopedExecutionHook) == address(0), ScopedExecutionHookAlreadyInstalled()); + require(config.target != SELECTOR_NOT_INSTALLED, InvalidScopedExecutionHookTarget()); + require( + address(config.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + ScopedExecutionHookAlreadyInstalled() + ); config.scopedExecutionHook = IScopedExecutionHook(hook); } else { revert InvalidScopedExecutionHookTarget(); @@ -173,18 +194,35 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM function _uninstallScopedExecutionHook(address hook, bytes calldata internalData, bool) internal { bytes1 scope = _scopedExecutionHookScope(internalData); if (scope == SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) { - require(internalData.length == 22, InvalidDataLength()); - _uninstallScopedExecutionHookWithVid(hook, ValidationId.wrap(bytes21(internalData[1:22]))); + require(internalData.length == SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH, InvalidDataLength()); + _uninstallScopedExecutionHookWithVid( + hook, + ValidationId.wrap( + bytes21( + internalData[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH] + ) + ) + ); } else if (scope == SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) { - require(internalData.length == 21, InvalidDataLength()); - ExecutorConfig storage config = _executorConfig(IExecutor(address(bytes20(internalData[1:21])))); + require(internalData.length == SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH, InvalidDataLength()); + ExecutorConfig storage config = _executorConfig( + IExecutor( + address( + bytes20( + internalData[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH] + ) + ) + ) + ); require(address(config.scopedExecutionHook) == hook, InvalidScopedExecutionHookTarget()); - config.scopedExecutionHook = IScopedExecutionHook(address(0)); + config.scopedExecutionHook = IScopedExecutionHook(SCOPED_EXECUTION_HOOK_NOT_INSTALLED); } else if (scope == SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) { - require(internalData.length == 5, InvalidDataLength()); - SelectorConfig storage config = _selectorConfig(bytes4(internalData[1:5])); + require(internalData.length == SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH, InvalidDataLength()); + SelectorConfig storage config = _selectorConfig( + bytes4(internalData[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH]) + ); require(address(config.scopedExecutionHook) == hook, InvalidScopedExecutionHookTarget()); - config.scopedExecutionHook = IScopedExecutionHook(address(0)); + config.scopedExecutionHook = IScopedExecutionHook(SCOPED_EXECUTION_HOOK_NOT_INSTALLED); } else { revert InvalidScopedExecutionHookTarget(); } @@ -199,18 +237,27 @@ abstract contract ModuleManager is ValidationManager, ExecutorManager, SelectorM if (context.length == 0) return false; bytes1 scope = bytes1(context[0]); if (scope == SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE) { - if (context.length != 22) return false; - ValidationId vId = ValidationId.wrap(bytes21(context[1:22])); + if (context.length != SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH) return false; + ValidationId vId = ValidationId.wrap( + bytes21(context[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH]) + ); return address(_validationStorage().vInfo[vId].scopedExecutionHook) == hook; } if (scope == SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE) { - if (context.length != 21) return false; - address executor = address(bytes20(context[1:21])); + if (context.length != SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH) return false; + address executor = address( + bytes20(context[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH]) + ); return address(_executorConfig(IExecutor(executor)).scopedExecutionHook) == hook; } if (scope == SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE) { - if (context.length != 5) return false; - return address(_selectorConfig(bytes4(context[1:5])).scopedExecutionHook) == hook; + if (context.length != SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH) return false; + return address( + _selectorConfig( + bytes4(context[SCOPED_EXECUTION_HOOK_TARGET_OFFSET:SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH]) + ) + .scopedExecutionHook + ) == hook; } return false; } diff --git a/src/core/SelectorManager.sol b/src/core/SelectorManager.sol index 30b183e5..f62b7cf4 100644 --- a/src/core/SelectorManager.sol +++ b/src/core/SelectorManager.sol @@ -2,7 +2,13 @@ pragma solidity ^0.8.0; import {CallType} from "../types/Types.sol"; -import {SELECTOR_MANAGER_STORAGE_SLOT, CALLTYPE_DELEGATECALL} from "../types/Constants.sol"; +import { + SELECTOR_MANAGER_STORAGE_SLOT, + CALLTYPE_SINGLE, + CALLTYPE_DELEGATECALL, + SELECTOR_NOT_INSTALLED, + SCOPED_EXECUTION_HOOK_NOT_INSTALLED +} from "../types/Constants.sol"; import { ModuleInstallFailed, InvalidSelectorTarget, @@ -38,7 +44,7 @@ abstract contract SelectorManager { /// so their selector configuration and scoped execution hook have no effect. function _installSelector(address _module, bytes calldata _internalData, bool _installSuccess) internal { require(_internalData.length == 5, InvalidDataLength()); - require(_module != address(0), InvalidSelectorTarget()); + require(_module != SELECTOR_NOT_INSTALLED, InvalidSelectorTarget()); bytes4 selector = bytes4(_internalData[0:4]); CallType callType = CallType.wrap(bytes1(_internalData[4])); require(callType == CALLTYPE_DELEGATECALL || _installSuccess, ModuleInstallFailed()); @@ -51,8 +57,10 @@ abstract contract SelectorManager { function _uninstallSelector(address, bytes calldata _internalData, bool) internal { require(_internalData.length == 4, InvalidDataLength()); SelectorConfig storage $ = _selectorConfig(bytes4(_internalData[0:4])); - require(address($.scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); - $.target = address(0); - $.callType = CallType.wrap(bytes1(0x00)); + require( + address($.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, ScopedExecutionHookStillInstalled() + ); + $.target = SELECTOR_NOT_INSTALLED; + $.callType = CALLTYPE_SINGLE; } } diff --git a/src/core/ValidationManager.sol b/src/core/ValidationManager.sol index 77386f7a..4bbc8207 100644 --- a/src/core/ValidationManager.sol +++ b/src/core/ValidationManager.sol @@ -33,7 +33,8 @@ import { MODULE_TYPE_POLICY, MODULE_TYPE_SIGNER, SIG_VALIDATION_FAILED_UINT, - SIG_VALIDATION_SUCCESS_UINT + SIG_VALIDATION_SUCCESS_UINT, + SCOPED_EXECUTION_HOOK_NOT_INSTALLED } from "../types/Constants.sol"; import {PermissionSignature, ValidationStorage, ValidationInfo, Install} from "../types/Structs.sol"; import {Lib4337} from "../lib/Lib4337.sol"; @@ -199,7 +200,10 @@ abstract contract ValidationManager { if (getType(vId) == VALIDATION_TYPE_PERMISSION) { require(info.signer != address(0), InvalidScopedExecutionHookTarget()); } - require(address(info.scopedExecutionHook) == address(0), ScopedExecutionHookAlreadyInstalled()); + require( + address(info.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + ScopedExecutionHookAlreadyInstalled() + ); info.scopedExecutionHook = IScopedExecutionHook(_hook); } @@ -228,7 +232,10 @@ abstract contract ValidationManager { function _uninstallValidation(ValidationId _vId) internal { ValidationStorage storage $ = _validationStorage(); require($.root != _vId, CannotUninstallRoot()); - require(address($.vInfo[_vId].scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); + require( + address($.vInfo[_vId].scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, + ScopedExecutionHookStillInstalled() + ); $.vInfo[_vId].installed = false; } @@ -243,7 +250,7 @@ abstract contract ValidationManager { function _uninstallScopedExecutionHookWithVid(address _hook, ValidationId vId) internal { ValidationInfo storage info = _validationStorage().vInfo[vId]; require(address(info.scopedExecutionHook) == _hook, InvalidScopedExecutionHookTarget()); - info.scopedExecutionHook = IScopedExecutionHook(address(0)); + info.scopedExecutionHook = IScopedExecutionHook(SCOPED_EXECUTION_HOOK_NOT_INSTALLED); } /// @notice Uninstalls a policy module. Policies must be uninstalled in reverse order (LIFO). @@ -280,7 +287,9 @@ abstract contract ValidationManager { /// @param vId The validation identifier the signer belongs to. function _uninstallSignerWithVid(address _signer, ValidationId vId) internal { ValidationInfo storage $ = _validationStorage().vInfo[vId]; - require(address($.scopedExecutionHook) == address(0), ScopedExecutionHookStillInstalled()); + require( + address($.scopedExecutionHook) == SCOPED_EXECUTION_HOOK_NOT_INSTALLED, ScopedExecutionHookStillInstalled() + ); require($.policies.length == 0, InvalidPermissionUninstallOrder()); require($.signer == _signer, InvalidPermissionId()); $.signer = address(0); @@ -419,7 +428,7 @@ abstract contract ValidationManager { // Require a properly-encoded `uint256` (32 bytes) return. A codeless / non-conforming // validator returns success with empty returndata, which would otherwise decode to 0 // (SIG_VALIDATION_SUCCESS) and authorise any signature. - validationData = (success && ret.length == 32) ? abi.decode(ret, (uint256)) : 1; + validationData = (success && ret.length == 32) ? abi.decode(ret, (uint256)) : SIG_VALIDATION_FAILED_UINT; } /// @notice Validates a userOp using a permission (policies + signer). diff --git a/src/lib/Lib4337.sol b/src/lib/Lib4337.sol index b1fa2b01..470684fb 100644 --- a/src/lib/Lib4337.sol +++ b/src/lib/Lib4337.sol @@ -5,7 +5,11 @@ import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOper import {UserOperationLib} from "account-abstraction/core/UserOperationLib.sol"; import {Eip7702Support} from "account-abstraction/core/Eip7702Support.sol"; import {IERC5267} from "../interfaces/IERC5267.sol"; -import {DOMAIN_TYPEHASH_SANS_CHAIN_ID} from "../types/Constants.sol"; +import { + DOMAIN_TYPEHASH_SANS_CHAIN_ID, + SIG_VALIDATION_FAILED_UINT, + SIG_VALIDATION_SUCCESS_UINT +} from "../types/Constants.sol"; import {ValidityFormatMismatch} from "../types/Error.sol"; library Lib4337 { @@ -32,7 +36,7 @@ library Lib4337 { } function checkValidation(uint256 validationData) internal view returns (bool) { - if (validationData == 0) { + if (validationData == SIG_VALIDATION_SUCCESS_UINT) { return true; } (uint48 vAfter, uint48 vUntil, address res) = Lib4337.parseValidationData(validationData); @@ -45,7 +49,7 @@ library Lib4337 { current = block.timestamp; } // Canonical EntryPoint v0.9 interval: (validAfter, validUntil]. - return res == address(0) && current > vAfter && current <= vUntil; + return uint160(res) == SIG_VALIDATION_SUCCESS_UINT && current > vAfter && current <= vUntil; } /// @dev Variant of `_hashTypedData` that excludes the chain ID. @@ -83,7 +87,7 @@ library Lib4337 { pure returns (uint256 resValidationData) { - if (preValidationData == 0 || validationRes == 0) { + if (preValidationData == SIG_VALIDATION_SUCCESS_UINT || validationRes == SIG_VALIDATION_SUCCESS_UINT) { return preValidationData | validationRes; } @@ -101,17 +105,18 @@ library Lib4337 { uint160 preAgg = uint160(preValidationData); uint160 resAgg = uint160(validationRes); - uint160 finalAgg = (preAgg == 1 || resAgg == 1) - ? 1 // Any failure - : (preAgg == 0 && resAgg == 0) - ? 0 // Both success - : (preAgg > 1 && resAgg == 0) + uint160 finalAgg = (preAgg == uint160(SIG_VALIDATION_FAILED_UINT) + || resAgg == uint160(SIG_VALIDATION_FAILED_UINT)) + ? uint160(SIG_VALIDATION_FAILED_UINT) // Any failure + : (preAgg == uint160(SIG_VALIDATION_SUCCESS_UINT) && resAgg == uint160(SIG_VALIDATION_SUCCESS_UINT)) + ? uint160(SIG_VALIDATION_SUCCESS_UINT) // Both success + : (preAgg > uint160(SIG_VALIDATION_FAILED_UINT) && resAgg == uint160(SIG_VALIDATION_SUCCESS_UINT)) ? preAgg // Preserve aggregator - : (preAgg == 0 && resAgg > 1) + : (preAgg == uint160(SIG_VALIDATION_SUCCESS_UINT) && resAgg > uint160(SIG_VALIDATION_FAILED_UINT)) ? resAgg // Use new aggregator : (preAgg == resAgg) ? preAgg // Same aggregator - : 1; // Conflict or unknown + : uint160(SIG_VALIDATION_FAILED_UINT); // Conflict or unknown // Extract raw time bounds uint48 validUntil1 = uint48(preValidationData >> 160); @@ -131,7 +136,7 @@ library Lib4337 { // ValidityFormatMismatch revert — so the check is skipped. A neutral [0, max] range // carries no restriction and no format and is likewise exempt (its normalized validUntil // has MODE_BIT set, which would otherwise misclassify it as a block range). - if (finalAgg != 1) { + if (finalAgg != SIG_VALIDATION_FAILED_UINT) { bool preNeutral = validAfter1 == 0 && validUntil1 == type(uint48).max; bool resNeutral = validAfter2 == 0 && validUntil2 == type(uint48).max; // Block number format: both validAfter and validUntil have the highest bit set. diff --git a/src/types/Constants.sol b/src/types/Constants.sol index 3051c9f9..609bcc3f 100644 --- a/src/types/Constants.sol +++ b/src/types/Constants.sol @@ -22,6 +22,14 @@ bytes1 constant SCOPED_EXECUTION_HOOK_VALIDATION_SCOPE = 0x01; bytes1 constant SCOPED_EXECUTION_HOOK_EXECUTOR_SCOPE = 0x02; bytes1 constant SCOPED_EXECUTION_HOOK_SELECTOR_SCOPE = 0x03; +address constant SELECTOR_NOT_INSTALLED = address(0); +address constant SCOPED_EXECUTION_HOOK_NOT_INSTALLED = address(0); + +uint256 constant SCOPED_EXECUTION_HOOK_TARGET_OFFSET = 1; +uint256 constant SCOPED_EXECUTION_HOOK_VALIDATION_DATA_LENGTH = 22; +uint256 constant SCOPED_EXECUTION_HOOK_EXECUTOR_DATA_LENGTH = 21; +uint256 constant SCOPED_EXECUTION_HOOK_SELECTOR_DATA_LENGTH = 5; + // note : ROOT == FALLBACK, they do indicate same value but to have different meanings in different context // FALLBACK - usually used when using 7702 validation logic // ROOT - mostly used when identifying that you are using root validation on userOp.nonce