From 7353e2aa8d8bff58439ffba4786247b52d728439 Mon Sep 17 00:00:00 2001 From: zeroknots Date: Fri, 1 Mar 2024 11:50:11 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20fix=20checks=20in=20policy=20val?= =?UTF-8?q?idator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎨 Fooq --- .../src/HookMultiplex/HookMultiplexer.sol | 6 +- examples/src/HookMultiplex/PermissionHook.sol | 63 +++ .../src/HookMultiplex/lib/HookEncodingLib.sol | 13 - .../HookMultiplex/lib/PermissionFlagLib.sol | 70 ++++ .../subHooks/PermissionFlags.sol | 161 ++++++-- .../src/PermissionsHook/PermissionsHookV2.sol | 18 +- .../HookMultiPlexer/HookMultiplexer.t.sol | 78 +++- .../PermissionsHook/PermissionsHook.t.sol.bak | 8 +- packages/PermissionManager/foundry.toml | 22 + packages/PermissionManager/package.json | 71 ++++ packages/PermissionManager/remappings.txt | 24 ++ packages/PermissionManager/src/Helper.sol | 53 +++ .../PermissionManager/src/IHookPolicy.sol | 80 ++++ packages/PermissionManager/src/IPolicy.sol | 37 ++ packages/PermissionManager/src/ISigner.sol | 31 ++ .../PermissionManager/src/PermissionHook.sol | 311 ++++++++++++++ .../src/PermissionValidator.sol | 371 +++++++++++++++++ .../PermissionManager/src/PolicyConfig.sol | 67 +++ .../src/policies/ExecutionPolicy.sol.bak | 70 ++++ .../src/policies/SudoPolicy.sol | 46 +++ .../src/signers/ECDSASigner.sol | 80 ++++ .../src/signers/WebAuthnSigner.sol.bak | 108 +++++ .../test/PermissionValidator.t.sol | 89 ++++ .../src/modules/ERC7579HookDestruct.sol | 1 + pnpm-lock.yaml | 389 +++++++++++------- 25 files changed, 2035 insertions(+), 232 deletions(-) create mode 100644 examples/src/HookMultiplex/PermissionHook.sol create mode 100644 examples/src/HookMultiplex/lib/PermissionFlagLib.sol create mode 100644 packages/PermissionManager/foundry.toml create mode 100644 packages/PermissionManager/package.json create mode 100644 packages/PermissionManager/remappings.txt create mode 100644 packages/PermissionManager/src/Helper.sol create mode 100644 packages/PermissionManager/src/IHookPolicy.sol create mode 100644 packages/PermissionManager/src/IPolicy.sol create mode 100644 packages/PermissionManager/src/ISigner.sol create mode 100644 packages/PermissionManager/src/PermissionHook.sol create mode 100644 packages/PermissionManager/src/PermissionValidator.sol create mode 100644 packages/PermissionManager/src/PolicyConfig.sol create mode 100644 packages/PermissionManager/src/policies/ExecutionPolicy.sol.bak create mode 100644 packages/PermissionManager/src/policies/SudoPolicy.sol create mode 100644 packages/PermissionManager/src/signers/ECDSASigner.sol create mode 100644 packages/PermissionManager/src/signers/WebAuthnSigner.sol.bak create mode 100644 packages/PermissionManager/test/PermissionValidator.t.sol diff --git a/examples/src/HookMultiplex/HookMultiplexer.sol b/examples/src/HookMultiplex/HookMultiplexer.sol index 9c0fce70..e74f570b 100644 --- a/examples/src/HookMultiplex/HookMultiplexer.sol +++ b/examples/src/HookMultiplex/HookMultiplexer.sol @@ -106,9 +106,9 @@ contract HookMultiPlexer is ERC7579HookDestruct, IHookMultiPlexer { for (uint256 i; i < length; i++) { bytes32 _globalHook = globalHooks[i]; - console2.logBytes32(_globalHook); - console2.log("flag", checkFlagFn(_globalHook)); - if (!checkFlagFn(_globalHook)) continue; + // console2.logBytes32(_globalHook); + // console2.log("flag", checkFlagFn(_globalHook)); + // if (!checkFlagFn(_globalHook)) continue; (bool success,) = _globalHook.decodeAddress().call(callData); if (!success) revert SubHookFailed(_globalHook); } diff --git a/examples/src/HookMultiplex/PermissionHook.sol b/examples/src/HookMultiplex/PermissionHook.sol new file mode 100644 index 00000000..b4bbb3a9 --- /dev/null +++ b/examples/src/HookMultiplex/PermissionHook.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { ERC7579HookDestruct } from "@rhinestone/modulekit/src/modules/ERC7579HookDestruct.sol"; +import { SENTINEL as SENTINELAddress, SentinelListLib } from "sentinellist/SentinelList.sol"; +import { SENTINEL as SENTINELBytes32, LinkedBytes32Lib } from "sentinellist/SentinelListBytes32.sol"; +import { TokenTransactionLib } from "./lib/TokenTransactionLib.sol"; +import { PermissionFlag, PermissionFlagLib } from "./lib/PermissionFlagLib.sol"; +import { SSTORE2 } from "solady/src/utils/SSTORE2.sol"; + +contract PermissionHook is ERC7579HookDestruct { + using SentinelListLib for SentinelListLib.SentinelList; + using LinkedBytes32Lib for LinkedBytes32Lib.LinkedBytes32; + using TokenTransactionLib for bytes4; + using PermissionFlagLib for PermissionFlag; + + error InvalidPermission(); + + struct ConfigParams { + PermissionFlag flags; + address[] allowedTargets; + bytes4[] allowedFunctions; + } + + struct ModulePermissions { + PermissionFlag flags; + LinkedBytes32Lib.LinkedBytes32 allowedFunctions; + SentinelListLib.SentinelList allowedTargets; + } + + mapping(address account => mapping(address module => ModulePermissions)) internal $permissions; + mapping(address account => mapping(address module => SentinelListLib.SentinelList subHooks)) + internal $moduleSubHooks; + + mapping(address smartAccount => LinkedBytes32Lib.LinkedBytes32 globalSubHooks) internal + $globalSubHooks; + + function configure(address module, ConfigParams memory params) public { + ModulePermissions storage $modulePermissions = $subHook().permissions[msg.sender][module]; + $modulePermissions.flags = params.flags; + + uint256 length = params.allowedTargets.length; + $modulePermissions.allowedTargets.init(); + for (uint256 i; i < length; i++) { + $modulePermissions.allowedTargets.push(params.allowedTargets[i]); + } + length = params.allowedFunctions.length; + for (uint256 i; i < length; i++) { + $modulePermissions.allowedFunctions.push(bytes32(params.allowedFunctions[i])); + } + } + + function configureWithRegistry(address module, address attester) external { + ConfigParams memory params = + abi.decode(SSTORE2.read(_getSSTORE2Ref(module, attester)), (ConfigParams)); + configure(module, params); + } + + function _getSSTORE2Ref(address module, address attester) internal pure returns (address) { + // TODO: implement actual registry lookup + return address(0xbBb6987cD1807141DBc07A9C164CAB37603Db429); + } +} diff --git a/examples/src/HookMultiplex/lib/HookEncodingLib.sol b/examples/src/HookMultiplex/lib/HookEncodingLib.sol index ad2f7262..258ddbcf 100644 --- a/examples/src/HookMultiplex/lib/HookEncodingLib.sol +++ b/examples/src/HookMultiplex/lib/HookEncodingLib.sol @@ -58,19 +58,6 @@ library HookEncodingLib { } function is4337Hook(bytes32 encoded) internal pure returns (bool) { - console2.log("----"); - console2.logBytes32(encoded); - - bytes32 foo; - - assembly { - - foo := shr(encoded, 8) - - } - console2.logBytes32(foo); - console2.log("----"); - return (uint256(encoded) >> 8) & 0xff == 1; } diff --git a/examples/src/HookMultiplex/lib/PermissionFlagLib.sol b/examples/src/HookMultiplex/lib/PermissionFlagLib.sol new file mode 100644 index 00000000..b32d7af7 --- /dev/null +++ b/examples/src/HookMultiplex/lib/PermissionFlagLib.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +type PermissionFlag is bytes32; + +library PermissionFlagLib { + function pack( + bool permit_selfCall, + bool permit_moduleCall, + bool permit_sendValue, + bool permit_erc20Transfer, + bool permit_erc721Transfer, + bool permit_hasAllowedFunctions, + bool permit_hasAllowedTargets, + bool permit_moduleConfig, + bool enfoce_subhooks + ) + internal + pure + returns (PermissionFlag) + { + return PermissionFlag.wrap( + bytes32( + uint256( + (permit_selfCall ? 1 : 0) + (permit_moduleCall ? 2 : 0) + + (permit_sendValue ? 4 : 0) + (permit_erc20Transfer ? 8 : 0) + + (permit_erc721Transfer ? 16 : 0) + (permit_hasAllowedFunctions ? 32 : 0) + + (permit_hasAllowedTargets ? 64 : 0) + (permit_moduleConfig ? 128 : 0) + + (enfoce_subhooks ? 256 : 0) + ) + ) + ); + } + + function isSelfCall(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 1 == 1; + } + + function isModuleCall(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 2 == 2; + } + + function isSendValue(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 4 == 4; + } + + function isERC20Transfer(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 8 == 8; + } + + function isERC721Transfer(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 16 == 16; + } + + function hasAllowedFunctions(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 32 == 32; + } + + function hasAllowedTargets(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 64 == 64; + } + + function isModuleConfig(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 128 == 128; + } + + function enfoceSubhooks(PermissionFlag flags) internal pure returns (bool) { + return uint256(PermissionFlag.unwrap(flags)) & 256 == 256; + } +} diff --git a/examples/src/HookMultiplex/subHooks/PermissionFlags.sol b/examples/src/HookMultiplex/subHooks/PermissionFlags.sol index 30320477..fb82e3cf 100644 --- a/examples/src/HookMultiplex/subHooks/PermissionFlags.sol +++ b/examples/src/HookMultiplex/subHooks/PermissionFlags.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import { SentinelListLib } from "sentinellist/SentinelList.sol"; -import { LinkedBytes32Lib } from "sentinellist/SentinelListBytes32.sol"; +import { SentinelListLib, SENTINEL as SENTINEL_ADDRESS } from "sentinellist/SentinelList.sol"; +import { + LinkedBytes32Lib, SENTINEL as SENTINEL_BYTES32 +} from "sentinellist/SentinelListBytes32.sol"; +import { SSTORE2 } from "solady/src/utils/SSTORE2.sol"; import { Execution } from "@rhinestone/modulekit/src/Accounts.sol"; import { SubHookBase } from "./SubHookBase.sol"; import { TokenTransactionLib } from "../lib/TokenTransactionLib.sol"; @@ -11,31 +14,90 @@ import "forge-std/console2.sol"; // bytes32 constant STORAGE_SLOT = keccak256("permissions.storage"); bytes32 constant STORAGE_SLOT = bytes32(uint256(123)); -contract PermissionFlags is SubHookBase { +type PermissionFlags is bytes32; + +library PermissionFlagsLib { + function pack( + bool permit_selfCall, + bool permit_moduleCall, + bool permit_sendValue, + bool permit_erc20Transfer, + bool permit_erc721Transfer, + bool permit_hasAllowedFunctions, + bool permit_hasAllowedTargets, + bool permit_moduleConfig, + bool enfoce_subhooks + ) + internal + pure + returns (PermissionFlags) + { + return PermissionFlags.wrap( + bytes32( + uint256( + (permit_selfCall ? 1 : 0) + (permit_moduleCall ? 2 : 0) + + (permit_sendValue ? 4 : 0) + (permit_erc20Transfer ? 8 : 0) + + (permit_erc721Transfer ? 16 : 0) + (permit_hasAllowedFunctions ? 32 : 0) + + (permit_hasAllowedTargets ? 64 : 0) + (permit_moduleConfig ? 128 : 0) + + (enfoce_subhooks ? 256 : 0) + ) + ) + ); + } + + function isSelfCall(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 1 == 1; + } + + function isModuleCall(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 2 == 2; + } + + function isSendValue(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 4 == 4; + } + + function isERC20Transfer(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 8 == 8; + } + + function isERC721Transfer(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 16 == 16; + } + + function hasAllowedFunctions(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 32 == 32; + } + + function hasAllowedTargets(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 64 == 64; + } + + function isModuleConfig(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 128 == 128; + } + + function enfoceSubhooks(PermissionFlags flags) internal pure returns (bool) { + return uint256(PermissionFlags.unwrap(flags)) & 256 == 256; + } +} + +contract PermissionHook is SubHookBase { using SentinelListLib for SentinelListLib.SentinelList; using LinkedBytes32Lib for LinkedBytes32Lib.LinkedBytes32; using TokenTransactionLib for bytes4; + using PermissionFlagsLib for PermissionFlags; error InvalidPermission(); - struct AccessFlags { - // Execution permissions - // - Target permissions - bool selfCall; - bool moduleCall; - // - Value permissions - bool sendValue; - bool erc20Transfer; - bool erc721Transfer; - // - Calldata permissions - bool hasAllowedFunctions; - bool hasAllowedTargets; - // Module configuration permissions - bool moduleConfig; + struct ConfigParams { + PermissionFlags flags; + address[] allowedTargets; + bytes4[] allowedFunctions; } struct ModulePermissions { - AccessFlags flags; + PermissionFlags flags; LinkedBytes32Lib.LinkedBytes32 allowedFunctions; SentinelListLib.SentinelList allowedTargets; } @@ -53,28 +115,57 @@ contract PermissionFlags is SubHookBase { } } - function configure( - address module, - AccessFlags calldata flags, - address[] calldata allowedTargets, - bytes4[] calldata allowedFunctions + function getPermissions( + address account, + address module ) external + view + returns (ConfigParams memory config) { + (address[] memory allowedTargets,) = $subHook().permissions[account][module] + .allowedTargets + .getEntriesPaginated(SENTINEL_ADDRESS, 100); + (bytes32[] memory _allowedFunctions,) = $subHook().permissions[account][module] + .allowedFunctions + .getEntriesPaginated(SENTINEL_BYTES32, 100); + bytes4[] memory allowedFunctions = new bytes4[](_allowedFunctions.length); + for (uint256 i; i < _allowedFunctions.length; i++) { + allowedFunctions[i] = bytes4(_allowedFunctions[i]); + } + config = ConfigParams({ + flags: $subHook().permissions[account][module].flags, + allowedTargets: allowedTargets, + allowedFunctions: allowedFunctions + }); + } + + function configure(address module, ConfigParams memory params) public { ModulePermissions storage $modulePermissions = $subHook().permissions[msg.sender][module]; - $modulePermissions.flags = flags; + $modulePermissions.flags = params.flags; - uint256 length = allowedTargets.length; + uint256 length = params.allowedTargets.length; $modulePermissions.allowedTargets.init(); for (uint256 i; i < length; i++) { - $modulePermissions.allowedTargets.push(allowedTargets[i]); + $modulePermissions.allowedTargets.push(params.allowedTargets[i]); } - length = allowedFunctions.length; + length = params.allowedFunctions.length; for (uint256 i; i < length; i++) { - $modulePermissions.allowedFunctions.push(bytes32(allowedFunctions[i])); + $modulePermissions.allowedFunctions.push(bytes32(params.allowedFunctions[i])); } } + function _getSSTORE2Ref(address module, address attester) internal pure returns (address) { + // TODO: implement actual registry lookup + return address(0xbBb6987cD1807141DBc07A9C164CAB37603Db429); + } + + function configureWithRegistry(address module, address attester) external { + ConfigParams memory params = + abi.decode(SSTORE2.read(_getSSTORE2Ref(module, attester)), (ConfigParams)); + configure(module, params); + } + function onExecute( address smartAccount, address superVisorModule, @@ -91,12 +182,12 @@ contract PermissionFlags is SubHookBase { ModulePermissions storage $modulePermissions = $subHook().permissions[smartAccount][superVisorModule]; - AccessFlags memory flags = $modulePermissions.flags; + PermissionFlags flags = $modulePermissions.flags; bytes4 functionSig = callData.length > 4 ? bytes4(callData[0:4]) : bytes4(0); // check for self call - if (!flags.selfCall && target == smartAccount) { + if (!flags.isSelfCall() && target == smartAccount) { revert InvalidPermission(); } @@ -108,27 +199,27 @@ contract PermissionFlags is SubHookBase { // } // check for value transfer - if (!flags.sendValue && value > 0) { + if (!flags.isSendValue() && value > 0) { revert InvalidPermission(); } // Calldata permissions - if (flags.erc20Transfer && functionSig.isERC20Transfer()) { + if (flags.isERC20Transfer() && functionSig.isERC20Transfer()) { revert InvalidPermission(); } - if (flags.erc721Transfer && functionSig.isERC721Transfer()) { + if (flags.isERC721Transfer() && functionSig.isERC721Transfer()) { revert InvalidPermission(); } // check if target address is allowed to be called - if (flags.hasAllowedTargets && !$modulePermissions.allowedTargets.contains(target)) { + if (flags.hasAllowedTargets() && !$modulePermissions.allowedTargets.contains(target)) { revert InvalidPermission(); } // check if target functioni is allowed to be called if ( - flags.hasAllowedFunctions + flags.hasAllowedFunctions() && !$modulePermissions.allowedFunctions.contains(bytes32(functionSig)) ) { revert InvalidPermission(); diff --git a/examples/src/PermissionsHook/PermissionsHookV2.sol b/examples/src/PermissionsHook/PermissionsHookV2.sol index fa471062..b46a37ec 100644 --- a/examples/src/PermissionsHook/PermissionsHookV2.sol +++ b/examples/src/PermissionsHook/PermissionsHookV2.sol @@ -26,13 +26,13 @@ contract PermissionsHook is ERC7579HookDestruct, HookMultiPlexer { error InvalidPermission(); struct InitParams { - AccessFlags flags; + PermissionFlags flags; address[] allowedTargets; bytes32[] allowedFunctions; address[] moduleSubHooks; } - struct AccessFlags { + struct PermissionFlags { // Execution permissions // - Target permissions bool selfCall; @@ -49,7 +49,7 @@ contract PermissionsHook is ERC7579HookDestruct, HookMultiPlexer { } struct ModulePermissions { - AccessFlags flags; + PermissionFlags flags; LinkedBytes32Lib.LinkedBytes32 allowedFunctions; SentinelListLib.SentinelList allowedTargets; } @@ -61,8 +61,8 @@ contract PermissionsHook is ERC7579HookDestruct, HookMultiPlexer { //////////////////////////////////////////////////////////////////////////*/ function onInstall(bytes calldata data) external override { - (address[] memory _modules, AccessFlags[] memory _initParams) = - abi.decode(data, (address[], AccessFlags[])); + (address[] memory _modules, PermissionFlags[] memory _initParams) = + abi.decode(data, (address[], PermissionFlags[])); uint256 permissionsLength = _initParams.length; @@ -88,18 +88,18 @@ contract PermissionsHook is ERC7579HookDestruct, HookMultiPlexer { function addPermissions( address[] calldata _modules, - AccessFlags[] calldata _accessFlags + PermissionFlags[] calldata _PermissionFlags ) external { - uint256 permissionsLength = _accessFlags.length; + uint256 permissionsLength = _PermissionFlags.length; if (_modules.length != permissionsLength) { revert("PermissionsHook: addPermissions: module and permissions length mismatch"); } for (uint256 i; i < permissionsLength; i++) { - permissions[msg.sender][_modules[i]].flags = _accessFlags[i]; + permissions[msg.sender][_modules[i]].flags = _PermissionFlags[i]; } } @@ -257,7 +257,7 @@ contract PermissionsHook is ERC7579HookDestruct, HookMultiPlexer { ) internal { - AccessFlags memory flags = $permissions.flags; + PermissionFlags memory flags = $permissions.flags; bytes4 functionSig = callData.length > 4 ? bytes4(callData[0:4]) : bytes4(0); console2.log("validate exeute permissions"); diff --git a/examples/test/HookMultiPlexer/HookMultiplexer.t.sol b/examples/test/HookMultiPlexer/HookMultiplexer.t.sol index 7be8716e..8c47c3e4 100644 --- a/examples/test/HookMultiPlexer/HookMultiplexer.t.sol +++ b/examples/test/HookMultiPlexer/HookMultiplexer.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "@rhinestone/modulekit/src/ModuleKit.sol"; +import { SSTORE2 } from "solady/src/utils/SSTORE2.sol"; import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { SessionData, @@ -16,7 +17,11 @@ import { } from "@rhinestone/modulekit/src/external/ERC7579.sol"; import { IHookMultiPlexer, HookMultiPlexer, hookFlag } from "src/HookMultiPlex/HookMultiPlexer.sol"; -import { PermissionFlags } from "src/HookMultiPlex/subHooks/PermissionFlags.sol"; +import { + PermissionHook, + PermissionFlags, + PermissionFlagsLib +} from "src/HookMultiPlex/subHooks/PermissionFlags.sol"; import { SpendingLimits } from "src/HookMultiPlex/subHooks/SpendingLimits.sol"; contract HookMultiPlexerTest is RhinestoneModuleKit, Test { @@ -27,7 +32,7 @@ contract HookMultiPlexerTest is RhinestoneModuleKit, Test { AccountInstance internal instance; MockERC20 internal token; HookMultiPlexer internal multiplexer; - PermissionFlags internal permissionFlagsSubHook; + PermissionHook internal permissionFlagsSubHook; SpendingLimits internal spendingLimitsSubHook; // Mock executors @@ -43,7 +48,7 @@ contract HookMultiPlexerTest is RhinestoneModuleKit, Test { multiplexer = new HookMultiPlexer(); vm.label(address(multiplexer), "multiplexer"); - permissionFlagsSubHook = new PermissionFlags(address(multiplexer)); + permissionFlagsSubHook = new PermissionHook(address(multiplexer)); vm.label(address(permissionFlagsSubHook), "SubHook:PermissionFlags"); spendingLimitsSubHook = new SpendingLimits(address(multiplexer)); vm.label(address(spendingLimitsSubHook), "SubHook:SpendingLimits"); @@ -94,24 +99,63 @@ contract HookMultiPlexerTest is RhinestoneModuleKit, Test { } function setUpPermissionsSubHook() internal { - PermissionFlags.AccessFlags memory flags = PermissionFlags.AccessFlags({ - selfCall: false, - moduleCall: false, - hasAllowedTargets: true, - sendValue: false, - hasAllowedFunctions: true, - erc20Transfer: true, - erc721Transfer: false, - moduleConfig: false + // PermissionFlags.PermissionFlags memory flags = PermissionFlags.PermissionFlags({ + // selfCall: false, + // moduleCall: false, + // hasAllowedTargets: true, + // sendValue: false, + // hasAllowedFunctions: true, + // erc20Transfer: true, + // erc721Transfer: false, + // moduleConfig: false + // }); + + PermissionFlags flags = PermissionFlagsLib.pack({ + permit_selfCall: false, + permit_moduleCall: false, + permit_hasAllowedTargets: true, + permit_sendValue: false, + permit_hasAllowedFunctions: true, + permit_erc20Transfer: true, + permit_erc721Transfer: false, + permit_moduleConfig: false }); - vm.prank(instance.account); - permissionFlagsSubHook.configure({ - module: address(instance.defaultValidator), + flags = PermissionFlags.wrap(bytes32(uint256(type(uint256).max))); + + // vm.prank(instance.account); + // permissionFlagsSubHook.configure({ + // module: address(instance.defaultValidator), + // flags: flags, + // allowedTargets: new address[](0), + // allowedFunctions: new bytes4[](0) + // }); + + address[] memory allowedTargets = new address[](1); + allowedTargets[0] = address(0x414141414141); + // allowedTargets[1] = address(0x414141414141); + bytes4[] memory allowedFunctions = new bytes4[](1); + allowedFunctions[0] = IERC20.transfer.selector; + PermissionHook.ConfigParams memory params = PermissionHook.ConfigParams({ flags: flags, - allowedTargets: new address[](0), - allowedFunctions: new bytes4[](0) + allowedTargets: allowedTargets, + allowedFunctions: allowedFunctions }); + + bytes memory data = abi.encode(params); + + address pointer = SSTORE2.write(data); + + vm.prank(instance.account); + permissionFlagsSubHook.configureWithRegistry(address(instance.defaultValidator), pointer); + permissionFlagsSubHook.configure(address(instance.defaultValidator), params); + + PermissionHook.ConfigParams memory _params = permissionFlagsSubHook.getPermissions( + instance.account, address(instance.defaultValidator) + ); + assertEq(PermissionFlags.unwrap(params.flags), PermissionFlags.unwrap(_params.flags)); + + console2.log("pointer", pointer); } function setupSpendingLimitsSubHook() public { diff --git a/examples/test/PermissionsHook/PermissionsHook.t.sol.bak b/examples/test/PermissionsHook/PermissionsHook.t.sol.bak index ff278f93..212fc9eb 100644 --- a/examples/test/PermissionsHook/PermissionsHook.t.sol.bak +++ b/examples/test/PermissionsHook/PermissionsHook.t.sol.bak @@ -72,8 +72,8 @@ contract PermissionsHookTest is RhinestoneModuleKit, Test { modules[1] = address(executorAllowed); modules[2] = address(instance.defaultValidator); - PermissionsHook.AccessFlags[] memory permissions = new PermissionsHook.AccessFlags[](3); - permissions[0] = PermissionsHook.AccessFlags({ + PermissionsHook.PermissionFlags[] memory permissions = new PermissionsHook.PermissionFlags[](3); + permissions[0] = PermissionsHook.PermissionFlags({ selfCall: false, moduleCall: false, hasAllowedTargets: true, @@ -84,7 +84,7 @@ contract PermissionsHookTest is RhinestoneModuleKit, Test { moduleConfig: false }); - permissions[1] = PermissionsHook.AccessFlags({ + permissions[1] = PermissionsHook.PermissionFlags({ selfCall: true, moduleCall: true, hasAllowedTargets: false, @@ -95,7 +95,7 @@ contract PermissionsHookTest is RhinestoneModuleKit, Test { moduleConfig: true }); - permissions[2] = PermissionsHook.AccessFlags({ + permissions[2] = PermissionsHook.PermissionFlags({ selfCall: true, moduleCall: true, hasAllowedTargets: false, diff --git a/packages/PermissionManager/foundry.toml b/packages/PermissionManager/foundry.toml new file mode 100644 index 00000000..cc12d49c --- /dev/null +++ b/packages/PermissionManager/foundry.toml @@ -0,0 +1,22 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +fs_permissions = [{ access = "read", path = "out-optimized" }, { access = "read-write", path = "gas_calculations" }] +allow_paths = ["*", "/"] +evm_version = "cancun" +ignored_error_codes = [2394] + +[rpc_endpoints] +mainnet = "https://rpc.ankr.com/eth" + + +[fmt] +bracket_spacing = true +int_types = "long" +line_length = 100 +multiline_func_header = "all" +number_underscore = "thousands" +quote_style = "double" +tab_width = 4 +wrap_comments = true diff --git a/packages/PermissionManager/package.json b/packages/PermissionManager/package.json new file mode 100644 index 00000000..45bd5609 --- /dev/null +++ b/packages/PermissionManager/package.json @@ -0,0 +1,71 @@ +{ + "name": "permissionmanager", + "description": "ERC-7579 Port of ZeroDev's ModularPermissionValidator. Credits to ZeroDev for the original work.", + "license": "MIT", + "version": "0.1.0", + "author": { + "name": "zeroknots.eth", + "url": "https://rhinestone.wtf" + }, + "bugs": { + "url": "https://github.com/rhinestonewtf/modulekit/issues" + }, + "devDependencies": { + "@rhinestone/modulekit": "workspace:*", + "@rhinestone/sessionkeymanager": "workspace:*", + "erc4337-validation": "github:rhinestonewtf/erc4337-validation", + "@prb/math": "^4.0.2", + "@safe-global/safe-contracts": "^1.4.1", + "@openzeppelin/contracts": "5.0.1", + "@rhinestone/safe7579": "workspace:*", + "ds-test": "github:dapphub/ds-test", + "p256-verifier": "https://github.com/daimo-eth/p256-verifier", + "@ERC4337/account-abstraction": "github:kopy-kat/account-abstraction#develop", + "@ERC4337/account-abstraction-v0.6": "github:eth-infinitism/account-abstraction#v0.6.0", + "sentinellist": "github:zeroknots/sentinellist", + "erc7579": "github:erc7579/erc7579-implementation#feature/hookRevert", + "forge-std": "github:foundry-rs/forge-std", + "solady": "github:vectorized/solady", + "solhint": "^4.1.1", + "prettier": "^2.8.8" + }, + "files": [ + "artifacts", + "src", + "test/utils", + "CHANGELOG.md", + "LICENSE-GPL.md" + ], + "keywords": [ + "blockchain", + "ethereum", + "foundry", + "smart-contracts", + "solidity", + "web3" + ], + "publishConfig": { + "access": "public" + }, + "repository": "github.com/rhinestonewtf/modulekit", + "scripts": { + "fmt": "forge fmt", + "fmt:check": "forge fmt --check", + "build": "forge build && ./bytecode.sh", + "bytecode": "./bytescode.sh", + "build:optimized": "FOUNDRY_PROFILE=optimized forge build", + "build:smt": "FOUNDRY_PROFILE=smt forge build", + "clean": "rm -rf artifacts broadcast cache docs out out-optimized out-svg", + "gas:report": "forge test --gas-report --mp \"./test/integration/**/*.sol\" --nmt \"test(Fuzz)?_RevertWhen_\\w{1,}?\"", + "gas:snapshot": "forge snapshot --mp \"./test/integration/**/*.sol\" --nmt \"test(Fuzz)?_RevertWhen_\\w{1,}?\"", + "gas:snapshot:optimized": "pnpm run build:optimized && FOUNDRY_PROFILE=test-optimized forge snapshot --mp \"./test/integration/**/*.sol\" --nmt \"test(Fork)?(Fuzz)?_RevertWhen_\\w{1,}?\"", + "lint": "pnpm run lint:sol && bun run prettier:check", + "lint:sol": "forge fmt --check && pnpm solhint \"{script,src,test}/**/*.sol\"", + "prepack": "pnpm install", + "prettier:check": "prettier --check \"**/*.{json,md,svg,yml}\"", + "prettier:write": "prettier --write \"**/*.{json,md,svg,yml}\"", + "test": "forge test", + "test:lite": "FOUNDRY_PROFILE=lite forge test", + "test:optimized": "pnpm run build:optimized && FOUNDRY_PROFILE=test-optimized forge test" + } +} diff --git a/packages/PermissionManager/remappings.txt b/packages/PermissionManager/remappings.txt new file mode 100644 index 00000000..9339ed07 --- /dev/null +++ b/packages/PermissionManager/remappings.txt @@ -0,0 +1,24 @@ +solady/=node_modules/solady/ +@rhinestone/=node_modules/@rhinestone/ +erc7579/=node_modules/erc7579/src/ +@ERC4337/=node_modules/@ERC4337/ +account-abstraction/=node_modules/@ERC4337/account-abstraction/contracts/ +account-abstraction-v0.6/=node_modules/@ERC4337/account-abstraction-v0.6/contracts/ +forge-std/=node_modules/forge-std/src/ +ds-test/=node_modules/ds-test/src/ +sentinellist/=node_modules/sentinellist/src/ +ds-test/=node_modules/ds-test/src/ +forge-std/=node_modules/forge-std/src/ +account-abstraction/=node_modules/@ERC4337/account-abstraction/contracts/ +account-abstraction-v0.6/=node_modules/@ERC4337/account-abstraction-v0.6/contracts/ +@openzeppelin/=node_modules/@openzeppelin/ +erc7579/=node_modules/erc7579/src/ +sentinellist/=node_modules/sentinellist/src/ +solmate/=node_modules/solmate/src/ +solady/=node_modules/solady/ +solarray/=node_modules/solarray/src/ +@rhinestone/=node_modules/@rhinestone/ +@safe-global/=node_modules/@safe-global/ +erc4337-validation/=node_modules/erc4337-validation/src/ +@ERC4337/=node_modules/@ERC4337/ +@prb/math/=node_modules/@prb/math/ diff --git a/packages/PermissionManager/src/Helper.sol b/packages/PermissionManager/src/Helper.sol new file mode 100644 index 00000000..2b1ac4ff --- /dev/null +++ b/packages/PermissionManager/src/Helper.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; + +function _intersectValidationData( + ERC7579ValidatorBase.ValidationData a, + ERC7579ValidatorBase.ValidationData b +) + pure + returns (ERC7579ValidatorBase.ValidationData validationData) +{ + assembly { + // xor(a,b) == shows only matching bits + // and(xor(a,b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff) == + // filters out the validAfter and validUntil bits + // if the result is not zero, then aggregator part is not matching + // validCase : + // a == 0 || b == 0 || xor(a,b) == 0 + // invalidCase : + // a mul b != 0 && xor(a,b) != 0 + let sum := shl(96, add(a, b)) + switch or( + iszero( + and(xor(a, b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff) + ), + or(eq(sum, shl(96, a)), eq(sum, shl(96, b))) + ) + case 1 { + validationData := + and(or(a, b), 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff) + // validAfter + let a_vd := and(0xffffffffffff0000000000000000000000000000000000000000000000000000, a) + let b_vd := and(0xffffffffffff0000000000000000000000000000000000000000000000000000, b) + validationData := or(validationData, xor(a_vd, mul(xor(a_vd, b_vd), gt(b_vd, a_vd)))) + // validUntil + a_vd := and(0x000000000000ffffffffffff0000000000000000000000000000000000000000, a) + if iszero(a_vd) { + a_vd := 0x000000000000ffffffffffff0000000000000000000000000000000000000000 + } + b_vd := and(0x000000000000ffffffffffff0000000000000000000000000000000000000000, b) + if iszero(b_vd) { + b_vd := 0x000000000000ffffffffffff0000000000000000000000000000000000000000 + } + let until := xor(a_vd, mul(xor(a_vd, b_vd), lt(b_vd, a_vd))) + if iszero(until) { + until := 0x000000000000ffffffffffff0000000000000000000000000000000000000000 + } + validationData := or(validationData, until) + } + default { validationData := 1 } + } +} diff --git a/packages/PermissionManager/src/IHookPolicy.sol b/packages/PermissionManager/src/IHookPolicy.sol new file mode 100644 index 00000000..b413cec2 --- /dev/null +++ b/packages/PermissionManager/src/IHookPolicy.sol @@ -0,0 +1,80 @@ +pragma solidity ^0.8.0; + +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; +import { Execution } from "@rhinestone/modulekit/src/Accounts.sol"; + +interface IHookPolicy { + function registerHookPolicy( + address smartAccount, + bytes32 permissionId, + bytes calldata policyData + ) + external + payable; + + function onExecute( + address smartAccount, + address module, + address target, + uint256 value, + bytes calldata callData + ) + external + returns (bytes memory hookData); + + function onExecuteBatch( + address smartAccount, + address module, + Execution[] calldata executions + ) + external + returns (bytes memory hookData); + + function onExecuteFromExecutor( + address smartAccount, + address module, + address target, + uint256 value, + bytes calldata callData + ) + external + returns (bytes memory hookData); + + function onExecuteBatchFromExecutor( + address smartAccount, + address module, + Execution[] calldata executions + ) + external + returns (bytes memory hookData); + + /*////////////////////////////////////////////////////////////////////////// + CONFIG + //////////////////////////////////////////////////////////////////////////*/ + + function onInstallModule( + address smartAccount, + address module, + uint256 moduleType, + address moduleToInstall, + bytes calldata initData + ) + external + returns (bytes memory hookData); + + function onUninstallModule( + address smartAccount, + address module, + uint256 moduleType, + address moduleToUninstall, + bytes calldata deInitData + ) + external + returns (bytes memory hookData); + + /*////////////////////////////////////////////////////////////////////////// + POSTCHECK + //////////////////////////////////////////////////////////////////////////*/ + + function onPostCheck(bytes calldata hookData) external returns (bool success); +} diff --git a/packages/PermissionManager/src/IPolicy.sol b/packages/PermissionManager/src/IPolicy.sol new file mode 100644 index 00000000..cf80b988 --- /dev/null +++ b/packages/PermissionManager/src/IPolicy.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.8.0; + +import { PackedUserOperation } from + "@ERC4337/account-abstraction/contracts/core/UserOperationLib.sol"; + +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; + +interface IPolicy { + function registerPolicy( + address kernel, + bytes32 permissionId, + bytes calldata policyData + ) + external + payable; + function checkUserOpPolicy( + address kernel, + bytes32 permissionId, + PackedUserOperation calldata userOp, + bytes calldata proofAndSig + ) + external + payable + returns (ERC7579ValidatorBase.ValidationData); + + function validateSignature( + address kernel, + address caller, + bytes32 permissionId, + bytes32 messageHash, + bytes32 rawHash, + bytes calldata signature + ) + external + view + returns (ERC7579ValidatorBase.ValidationData); +} diff --git a/packages/PermissionManager/src/ISigner.sol b/packages/PermissionManager/src/ISigner.sol new file mode 100644 index 00000000..d9acf240 --- /dev/null +++ b/packages/PermissionManager/src/ISigner.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.8.0; + +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; + +interface ISigner { + function registerSigner( + address kernel, + bytes32 permissionId, + bytes calldata signerData + ) + external + payable; + function validateUserOp( + address kernel, + bytes32 permissionId, + bytes32 userOpHash, + bytes calldata signature + ) + external + payable + returns (ERC7579ValidatorBase.ValidationData); + function validateSignature( + address kernel, + bytes32 permissionId, + bytes32 messageHash, + bytes calldata signature + ) + external + view + returns (ERC7579ValidatorBase.ValidationData); +} diff --git a/packages/PermissionManager/src/PermissionHook.sol b/packages/PermissionManager/src/PermissionHook.sol new file mode 100644 index 00000000..3f340dd4 --- /dev/null +++ b/packages/PermissionManager/src/PermissionHook.sol @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC7579HookDestruct } from "@rhinestone/modulekit/src/modules/ERC7579HookDestruct.sol"; +import { IHookPolicy } from "./IHookPolicy.sol"; +import { SENTINEL, SentinelListLib } from "sentinellist/SentinelList.sol"; +import { Execution } from "@rhinestone/modulekit/src/Accounts.sol"; +import { SENTINEL as SENTINELBytes32, LinkedBytes32Lib } from "sentinellist/SentinelListBytes32.sol"; +import "forge-std/console2.sol"; + +type PolicyConfig is bytes32; + +type hookFlag is bool; + +contract PermissionHook is ERC7579HookDestruct { + using LinkedBytes32Lib for LinkedBytes32Lib.LinkedBytes32; + using HookEncodingLib for bytes32; + using HookEncodingLib for ConfigParam; + + struct ConfigParam { + hookFlag isExecutorHook; + hookFlag isValidatorHook; + hookFlag isConfigHook; + address hook; + } + + error SubHookFailed(bytes32); + + uint256 constant MAX_HOOK_NR = 16; + + mapping(address smartAccount => LinkedBytes32Lib.LinkedBytes32 globalSubHooks) internal + $globalSubHooks; + mapping(address smartAccount => mapping(address module => LinkedBytes32Lib.LinkedBytes32)) + internal $moduleSubHooks; + + function execSubHooks( + address module, + bytes memory callData, + function (bytes32) returns(bool) checkFlagFn + ) + internal + { + (bytes32[] memory globalHooks,) = + $globalSubHooks[msg.sender].getEntriesPaginated(SENTINELBytes32, MAX_HOOK_NR); + + uint256 length = globalHooks.length; + + for (uint256 i; i < length; i++) { + bytes32 _globalHook = globalHooks[i]; + // console2.logBytes32(_globalHook); + // console2.log("flag", checkFlagFn(_globalHook)); + // if (!checkFlagFn(_globalHook)) continue; + (bool success,) = _globalHook.decodeAddress().call(callData); + if (!success) revert SubHookFailed(_globalHook); + } + + LinkedBytes32Lib.LinkedBytes32 storage $moduleHooks = $moduleSubHooks[msg.sender][module]; + // TODO: make this nicer + if (!$moduleHooks.alreadyInitialized()) return; + + (bytes32[] memory moduleHooks,) = + $moduleSubHooks[msg.sender][module].getEntriesPaginated(SENTINELBytes32, MAX_HOOK_NR); + length = moduleHooks.length; + for (uint256 i; i < length; i++) { + bytes32 _moduleHook = moduleHooks[i]; + + if (!checkFlagFn(_moduleHook)) continue; + (bool success,) = _moduleHook.decodeAddress().call(callData); + if (!success) revert SubHookFailed(_moduleHook); + } + } + + function installGlobalHooks(ConfigParam[] calldata params) public { + uint256 length = params.length; + for (uint256 i; i < length; i++) { + ConfigParam calldata conf = params[i]; + bytes32 _packed = conf.pack(); + console2.logBytes32(_packed); + console2.log( + conf.hook, + hookFlag.unwrap(conf.isValidatorHook), + hookFlag.unwrap(conf.isExecutorHook) + ); + $globalSubHooks[msg.sender].push(_packed); + } + } + + function installModuleHooks(address module, ConfigParam[] calldata params) public { + uint256 length = params.length; + $moduleSubHooks[msg.sender][module].init(); + for (uint256 i; i < length; i++) { + ConfigParam calldata conf = params[i]; + bytes32 _packed = conf.pack(); + // check if the hook is already enabled for global + if ($globalSubHooks[msg.sender].contains(_packed)) continue; + $moduleSubHooks[msg.sender][module].push(_packed); + } + } + + function onExecute( + address module, + address target, + uint256 value, + bytes calldata callData + ) + internal + virtual + override + returns (bytes memory hookData) + { + execSubHooks({ + module: module, + callData: abi.encodeCall( + IHookPolicy.onExecute, (msg.sender, module, target, value, callData) + ), + checkFlagFn: HookEncodingLib.is4337Hook + }); + } + + function onExecuteBatch( + address module, + Execution[] calldata executions + ) + internal + virtual + override + returns (bytes memory hookData) + { + execSubHooks({ + module: module, + callData: abi.encodeCall(IHookPolicy.onExecuteBatch, (msg.sender, module, executions)), + checkFlagFn: HookEncodingLib.is4337Hook + }); + } + + function onExecuteFromExecutor( + address module, + address target, + uint256 value, + bytes calldata callData + ) + internal + virtual + override + returns (bytes memory hookData) + { + execSubHooks({ + module: module, + callData: abi.encodeCall( + IHookPolicy.onExecuteFromExecutor, (msg.sender, module, target, value, callData) + ), + checkFlagFn: HookEncodingLib.isExecutorHook + }); + } + + function onExecuteBatchFromExecutor( + address module, + Execution[] calldata executions + ) + internal + virtual + override + returns (bytes memory hookData) + { + execSubHooks({ + module: module, + callData: abi.encodeCall( + IHookPolicy.onExecuteBatchFromExecutor, (msg.sender, module, executions) + ), + checkFlagFn: HookEncodingLib.isExecutorHook + }); + } + + function onInstallModule( + address module, + uint256 moduleType, + address installModule, + bytes calldata initData + ) + internal + virtual + override + returns (bytes memory hookData) + { + execSubHooks({ + module: module, + callData: abi.encodeCall( + IHookPolicy.onInstallModule, (msg.sender, module, moduleType, installModule, initData) + ), + checkFlagFn: HookEncodingLib.isConfigHook + }); + } + + function onUninstallModule( + address module, + uint256 moduleType, + address uninstallModule, + bytes calldata deInitData + ) + internal + virtual + override + returns (bytes memory hookData) + { + execSubHooks({ + module: module, + callData: abi.encodeCall( + IHookPolicy.onInstallModule, + (msg.sender, module, moduleType, uninstallModule, deInitData) + ), + checkFlagFn: HookEncodingLib.isConfigHook + }); + } + + function onPostCheck(bytes calldata hookData) + internal + virtual + override + returns (bool success) + { } + + function onInstall(bytes calldata data) external override { + $globalSubHooks[msg.sender].init(); + } + + function onUninstall(bytes calldata data) external override { + // todo + } + + function version() external pure virtual returns (string memory) { + return "1.0.0"; + } + + function name() external pure virtual returns (string memory) { + return "MultiPlexerHook"; + } + + function isModuleType(uint256 isType) external pure virtual override returns (bool) { + return isType == TYPE_HOOK; + } + + function isInitialized(address smartAccount) external view returns (bool) { + // todo + } +} + +library HookEncodingLib { + function pack(PermissionHook.ConfigParam calldata params) + internal + pure + returns (bytes32 encoded) + { + return + encode(params.hook, params.isExecutorHook, params.isValidatorHook, params.isConfigHook); + } + + function encode( + address hook, + hookFlag isExecutorHook, + hookFlag isValidatorHook, + hookFlag isConfigHook + ) + internal + pure + returns (bytes32 encoded) + { + assembly { + encoded := hook + encoded := or(encoded, shl(8, isExecutorHook)) + encoded := or(encoded, shl(16, isValidatorHook)) + encoded := or(encoded, shl(24, isConfigHook)) + } + encoded = bytes32( + (abi.encodePacked(isExecutorHook, isValidatorHook, isConfigHook, bytes5(0), hook)) + ); + } + + function decode(bytes32 encoded) + internal + pure + returns ( + address hook, + hookFlag isExecutorHook, + hookFlag isValidatorHook, + hookFlag isConfigHook + ) + { + assembly { + hook := encoded + isExecutorHook := shr(8, encoded) + isValidatorHook := shr(16, encoded) + isConfigHook := shr(24, encoded) + } + } + + function isExecutorHook(bytes32 encoded) internal pure returns (bool) { + return (uint256(encoded)) & 0xff == 1; + } + + function is4337Hook(bytes32 encoded) internal pure returns (bool) { + return (uint256(encoded) >> 8) & 0xff == 1; + } + + function isConfigHook(bytes32 encoded) internal pure returns (bool) { + return (uint256(encoded) >> 16) & 0xff == 1; + } + + function decodeAddress(bytes32 encoded) internal pure returns (address) { + return address(uint160(uint256(encoded))); + } +} diff --git a/packages/PermissionManager/src/PermissionValidator.sol b/packages/PermissionManager/src/PermissionValidator.sol new file mode 100644 index 00000000..c74dc5d3 --- /dev/null +++ b/packages/PermissionManager/src/PermissionValidator.sol @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { + ACCOUNT_EXEC_TYPE, + ERC7579ValidatorLib +} from "@rhinestone/modulekit/src/modules/utils/ERC7579ValidatorLib.sol"; +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; +import { + PackedUserOperation, UserOperationLib +} from "@rhinestone/modulekit/src/external/ERC4337.sol"; +import { SignatureCheckerLib } from "solady/src/utils/SignatureCheckerLib.sol"; +import { Execution } from "erc7579/interfaces/IERC7579Account.sol"; +import "./IPolicy.sol"; +import "./Helper.sol"; +import "./ISigner.sol"; +import "./PolicyConfig.sol"; + +import "forge-std/console2.sol"; + +type ValidAfter is uint48; + +type ValidUntil is uint48; + +struct Permission { + uint128 nonce; + bytes12 flag; // flag represents what permission can do + ISigner signer; + PolicyConfig firstPolicy; + ValidAfter validAfter; + ValidUntil validUntil; +} + +struct Nonce { + uint128 lastNonce; + uint128 revoked; +} + +/** + * PermissionValidator + * heavily inspired by ZeroDev's ModularPermissionValidator @author taek - zerodev + * ported to ERC-7579 by zeroknots.eth + */ +contract PermissionValidator is ERC7579ValidatorBase { + mapping(address => bytes32) public priorityPermission; + mapping(bytes32 permissionId => mapping(address smartAccount => Permission)) public permissions; + mapping( + bytes32 permissionId + => mapping(PolicyConfig policy => mapping(address smartAccount => PolicyConfig)) + ) public nextPolicy; + mapping(address smartAccount => Nonce) public nonces; + + event PermissionRegistered(address smartAccount, bytes32 permissionId); + event PermissionRevoked(address smartAccount, bytes32 permissionId); + event NonceRevoked(address smartAccount, uint256 nonce); + + function getPermissionId( + bytes12 flag, + ISigner signer, + ValidAfter validAfter, + ValidUntil validUntil, + PolicyConfig[] calldata _policyConfig, + bytes calldata signerData, + bytes[] calldata policyData + ) + public + pure + returns (bytes32) + { + return keccak256( + abi.encode( + flag, + signer, + ValidAfter.unwrap(validAfter), + ValidUntil.unwrap(validUntil), + _policyConfig, + signerData, + policyData + ) + ); + } + + function parseData(bytes calldata data) + public + pure + returns ( + uint128 nonce, + bytes12 flag, + ISigner signer, + ValidAfter validAfter, + ValidUntil validUntil, + PolicyConfig[] calldata policies, + bytes calldata signerData, + bytes[] calldata policyData + ) + { + nonce = uint128(bytes16(data[0:16])); + flag = bytes12(data[16:28]); + validAfter = ValidAfter.wrap(uint48(bytes6(data[28:34]))); + validUntil = ValidUntil.wrap(uint48(bytes6(data[34:40]))); + signer = ISigner(address(bytes20(data[40:60]))); + assembly { + let offset := add(data.offset, 60) + policies.offset := add(add(offset, 32), calldataload(offset)) + policies.length := calldataload(sub(policies.offset, 32)) + signerData.offset := add(add(offset, 32), calldataload(add(offset, 32))) + signerData.length := calldataload(sub(signerData.offset, 32)) + policyData.offset := add(add(offset, 32), calldataload(add(offset, 64))) + policyData.length := calldataload(sub(policyData.offset, 32)) + } + } + + function enable(bytes calldata data) external payable { + ( + uint128 nonce, + bytes12 flag, + ISigner signer, + ValidAfter validAfter, + ValidUntil validUntil, + PolicyConfig[] calldata policies, + bytes calldata signerData, + bytes[] calldata policyData + ) = parseData(data); + registerPermission( + nonce, flag, signer, validAfter, validUntil, policies, signerData, policyData + ); + } + + function registerPermission( + uint128 nonce, + bytes12 flag, + ISigner signer, + ValidAfter validAfter, + ValidUntil validUntil, + PolicyConfig[] calldata policy, + bytes calldata signerData, + bytes[] calldata policyData + ) + public + payable + returns (bytes32 permissionId) + { + require(flag != toFlag(0), "flag should not be empty"); + require( + nonce == nonces[msg.sender].lastNonce || nonce == nonces[msg.sender].lastNonce + 1, + "nonce should be next" + ); + nonces[msg.sender].lastNonce++; + permissionId = + getPermissionId(flag, signer, validAfter, validUntil, policy, signerData, policyData); + if (flag == MAX_FLAG) { + priorityPermission[msg.sender] = permissionId; + } + + bytes12 maxFlag = flag; + for (uint256 i = 0; i < policy.length; i++) { + //TODO make sure address of the policy is sorted + PolicyConfigLib.getAddress(policy[i]).registerPolicy( + msg.sender, permissionId, policyData[i] + ); + // NOTE: flag for policy is inverted version of flag for permission; + bytes12 currentFlag = PolicyConfigLib.getFlags(policy[i]); + // turn off flags that are used, + // meaning that remaining maxFlag will indicate the permissions that are not used on + // this permission + maxFlag = currentFlag & maxFlag; + } + signer.registerSigner(msg.sender, permissionId, signerData); + + PolicyConfig firstPolicy = policy[0]; // NOTE : policy should not be empty array + require(maxFlag == bytes12(0), "error : permission flag exceeds policy flag"); + permissions[permissionId][msg.sender] = + Permission(nonce, flag, signer, firstPolicy, validAfter, validUntil); + for (uint256 i = 1; i < policy.length; i++) { + nextPolicy[permissionId][policy[i - 1]][msg.sender] = policy[i]; + } + emit PermissionRegistered(msg.sender, permissionId); + } + + function disable(bytes calldata data) external payable { + if (data.length == 32) { + revokePermission(bytes32(data)); + } else { + revokePermission(uint128(bytes16(data))); + } + } + + function revokePermission(bytes32 permissionId) public payable { + permissions[permissionId][msg.sender].flag = toFlag(0); // NOTE: making flag == 0 makes it + // invalid + emit PermissionRevoked(msg.sender, permissionId); + } + + function revokePermission(uint128 nonce) public payable { + nonces[msg.sender].revoked = nonce; + emit NonceRevoked(msg.sender, nonce); + } + + function validateUserOp( + PackedUserOperation calldata _userOp, + bytes32 _userOpHash + ) + external + override + returns (ValidationData validationData) + { + require(_userOp.sender == msg.sender, "sender must be msg.sender"); + bytes32 permissionId = bytes32(_userOp.signature[0:32]); + console2.log("validate user Op. PermissionID:"); + console2.logBytes32(permissionId); + if ( + permissions[permissionId][msg.sender].flag & toFlag(1) == toFlag(0) + || nonces[msg.sender].revoked > permissions[permissionId][msg.sender].nonce + ) { + return VALIDATION_FAILED; + } + Permission memory permission = permissions[permissionId][msg.sender]; + PolicyConfig policy = permission.firstPolicy; + uint256 cursor = 32; + console2.log("policy", address(PolicyConfigLib.getAddress(policy))); + console2.logBytes32(PolicyConfig.unwrap(policy)); + while (address(PolicyConfigLib.getAddress(policy)) != address(0)) { + // if (PolicyConfigLib.skipOnValidateUserOp(policy)) { + // policy = nextPolicy[permissionId][policy][msg.sender]; + // console2.log("skip"); + // continue; + // } + bytes calldata policyData; + console2.log("cursor", cursor); + + if ( + _userOp.signature.length >= cursor + 52 + && address(bytes20(_userOp.signature[cursor:cursor + 20])) + == address(PolicyConfigLib.getAddress(policy)) + ) { + console2.log("if"); + // only when policy address is same as the one in signature + uint256 length = uint256(bytes32(_userOp.signature[cursor + 20:cursor + 52])); + require( + _userOp.signature.length >= cursor + 52 + length, + "policyData length exceeds signature length" + ); + policyData = _userOp.signature[cursor + 52:cursor + 52 + length]; // [policyAddress, + // policyDataLength, policyData] + cursor += 52 + length; + } else { + console2.log("else"); + policyData = _userOp.signature[cursor:cursor]; + } + + console2.log("policyData"); + console2.logBytes(policyData); + ValidationData policyValidation = PolicyConfigLib.getAddress(policy).checkUserOpPolicy( + msg.sender, permissionId, _userOp, policyData + ); + validationData = _intersectValidationData(validationData, policyValidation); + policy = nextPolicy[permissionId][policy][msg.sender]; + } + ValidationData signatureValidation = permission.signer.validateUserOp( + msg.sender, permissionId, _userOpHash, _userOp.signature[cursor:] + ); + validationData = _intersectValidationData(validationData, signatureValidation); + } + + struct ValidationSigMemory { + address caller; + bytes32 permissionId; + bytes32 rawHash; + uint256 cursor; + PolicyConfig policy; + } + + function validateSignature( + bytes32 hash, + bytes calldata signature + ) + external + view + returns (ERC7579ValidatorBase.ValidationData validationData) + { + ValidationSigMemory memory sigMemory; + sigMemory.permissionId = bytes32(signature[0:32]); + if ( + nonces[msg.sender].revoked > permissions[sigMemory.permissionId][msg.sender].nonce + || permissions[sigMemory.permissionId][msg.sender].flag & toFlag(2) == toFlag(0) + ) { + return VALIDATION_FAILED; + } + Permission memory permission = permissions[sigMemory.permissionId][msg.sender]; + // signature should be packed with + // (permissionId, [proof || signature]) + // (permissionId, [ (policyAddress) + (policyProof) || signature] + bytes calldata proofAndSignature; //) = abi.decode(signature[32:], (bytes, bytes)); + assembly { + proofAndSignature.offset := add(signature.offset, 32) + proofAndSignature.length := sub(signature.length, 32) + } + + sigMemory.cursor = 0; + sigMemory.policy = permission.firstPolicy; + sigMemory.caller = address(bytes20(msg.data[msg.data.length - 20:])); + sigMemory.rawHash = bytes32(msg.data[msg.data.length - 52:msg.data.length - 20]); + while (address(PolicyConfigLib.getAddress(sigMemory.policy)) != address(0)) { + if (PolicyConfigLib.skipOnValidateSignature(sigMemory.policy)) { + sigMemory.policy = nextPolicy[sigMemory.permissionId][sigMemory.policy][msg.sender]; + continue; + } + bytes calldata policyData; + if ( + address(bytes20(proofAndSignature[sigMemory.cursor:sigMemory.cursor + 20])) + == address(PolicyConfigLib.getAddress(sigMemory.policy)) + ) { + // only when policy address is same as the one in signature + uint256 length = + uint256(bytes32(proofAndSignature[sigMemory.cursor + 20:sigMemory.cursor + 52])); + policyData = proofAndSignature[sigMemory.cursor + 52:]; // [policyAddress, + // policyDataLength, policyData] + sigMemory.cursor += 52 + length; + } else { + policyData = proofAndSignature[sigMemory.cursor:sigMemory.cursor]; + // not move cursor here + } + ValidationData policyValidation = PolicyConfigLib.getAddress(sigMemory.policy) + .validateSignature( + msg.sender, + sigMemory.caller, + sigMemory.permissionId, + hash, + sigMemory.rawHash, + policyData + ); + validationData = _intersectValidationData(validationData, policyValidation); + sigMemory.policy = nextPolicy[sigMemory.permissionId][sigMemory.policy][msg.sender]; + } + ValidationData signatureValidation = permission.signer.validateSignature( + msg.sender, sigMemory.permissionId, hash, proofAndSignature[sigMemory.cursor:] + ); + validationData = _intersectValidationData(validationData, signatureValidation); + } + + function version() external pure virtual returns (string memory) { + return "1.0.0"; + } + + function name() external pure virtual returns (string memory) { + return "PermissionManager"; + } + + function isModuleType(uint256 isType) external pure virtual override returns (bool) { + return isType == TYPE_VALIDATOR; + } + + function onInstall(bytes calldata data) external override { } + function onUninstall(bytes calldata data) external override { } + + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata data + ) + external + view + virtual + override + returns (bytes4) + { } + + function isInitialized(address smartAccount) external view returns (bool) { + return false; + } +} diff --git a/packages/PermissionManager/src/PolicyConfig.sol b/packages/PermissionManager/src/PolicyConfig.sol new file mode 100644 index 00000000..9ac1da26 --- /dev/null +++ b/packages/PermissionManager/src/PolicyConfig.sol @@ -0,0 +1,67 @@ +pragma solidity ^0.8.0; + +import "./IPolicy.sol"; + +type PolicyConfig is bytes32; + +function toFlag(uint256 x) pure returns (bytes12) { + return bytes12(bytes32(x << 160)); +} + +function toPermissionFlag(uint256 x) pure returns (bytes12) { + bytes12 ret = bytes12(bytes32(x << 160)); + assembly { + ret := not(ret) + } + return ret; +} + +bytes12 constant MAX_FLAG = 0xffffffffffffffffffffffff; +// PolicyData is a 32 bytes array that contains the address of the policy +// [flags(12 bytes), address(20 bytes)] +// flags is 96 bits that contains the following information +// from last to first bit +// 1 bit : not for validatUserOp +// 1 bit : not for validateSignature +// 1 bit : not for validateCaller + +library PolicyConfigLib { + function pack(IPolicy addr, bytes12 flag) internal pure returns (PolicyConfig data) { + assembly { + data := or(addr, flag) + } + } + + function getAddress(PolicyConfig data) internal pure returns (IPolicy policy) { + assembly { + policy := and(data, 0xffffffffffffffffffffffffffffffffffffffff) + } + } + + function getFlags(PolicyConfig data) internal pure returns (bytes12 flags) { + assembly { + flags := shr(160, data) + } + } + + function skipOnValidateUserOp(PolicyConfig data) internal pure returns (bool result) { + assembly { + let flags := shr(160, data) + result := and(flags, 0x1) + } + } + + function skipOnValidateSignature(PolicyConfig data) internal pure returns (bool result) { + assembly { + let flags := shr(161, data) + result := and(flags, 0x1) + } + } + + function skipOnValidateCaller(PolicyConfig data) internal pure returns (bool result) { + assembly { + let flags := shr(162, data) + result := and(flags, 0x1) + } + } +} diff --git a/packages/PermissionManager/src/policies/ExecutionPolicy.sol.bak b/packages/PermissionManager/src/policies/ExecutionPolicy.sol.bak new file mode 100644 index 00000000..cc54c509 --- /dev/null +++ b/packages/PermissionManager/src/policies/ExecutionPolicy.sol.bak @@ -0,0 +1,70 @@ +pragma solidity ^0.8.0; + +import "../IPolicy.sol"; +import { ValidAfter } from "../PermissionManager.sol"; + +struct ExecutionConfig { + uint48 interval; + uint48 count; + ValidAfter startAt; +} + +contract ExecutePolicy is IPolicy { + mapping( + address permissionValidator + => mapping(bytes32 permissionId => mapping(address kernel => ExecutionConfig)) + ) public executionConfigs; + + function registerPolicy( + address kernel, + bytes32 permissionId, + bytes calldata policyData + ) + external + payable + override + { + uint48 delay = uint48(bytes6(policyData[0:6])); + uint48 count = uint48(bytes6(policyData[6:12])); + uint48 startAt = uint48(bytes6(policyData[12:18])); + executionConfigs[msg.sender][permissionId][kernel] = + ExecutionConfig(delay, count, ValidAfter.wrap(startAt)); + } + + function checkUserOpPolicy( + address kernel, + bytes32 permissionId, + UserOperation calldata, + bytes calldata + ) + external + payable + override + returns (ValidationData) + { + ExecutionConfig memory config = executionConfigs[msg.sender][permissionId][kernel]; + if (config.count == 0) { + return SIG_VALIDATION_FAILED; + } + executionConfigs[msg.sender][permissionId][kernel].count = config.count - 1; + executionConfigs[msg.sender][permissionId][kernel].startAt = + ValidAfter.wrap(ValidAfter.unwrap(config.startAt) + config.interval); + return packValidationData(config.startAt, ValidUntil.wrap(0)); + } + + function validateSignature( + address, + address, + bytes32, + bytes32, + bytes32, + bytes calldata + ) + external + view + override + returns (ValidationData) + { + return ValidationData.wrap(0); + } +} diff --git a/packages/PermissionManager/src/policies/SudoPolicy.sol b/packages/PermissionManager/src/policies/SudoPolicy.sol new file mode 100644 index 00000000..67e00805 --- /dev/null +++ b/packages/PermissionManager/src/policies/SudoPolicy.sol @@ -0,0 +1,46 @@ +pragma solidity ^0.8.0; + +import { IPolicy, PackedUserOperation } from "../IPolicy.sol"; +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; + +contract SudoPolicy is IPolicy { + function registerPolicy( + address kernel, + bytes32 permissionId, + bytes calldata data + ) + external + payable + override + { } + + function checkUserOpPolicy( + address kernel, + bytes32 permissionId, + PackedUserOperation calldata userOp, + bytes calldata + ) + external + payable + override + returns (ERC7579ValidatorBase.ValidationData) + { + return ERC7579ValidatorBase.ValidationData.wrap(0); + } + + function validateSignature( + address kernel, + address caller, + bytes32 permissionId, + bytes32 messageHash, + bytes32 rawHash, + bytes calldata signature + ) + external + view + override + returns (ERC7579ValidatorBase.ValidationData) + { + return ERC7579ValidatorBase.ValidationData.wrap(0); + } +} diff --git a/packages/PermissionManager/src/signers/ECDSASigner.sol b/packages/PermissionManager/src/signers/ECDSASigner.sol new file mode 100644 index 00000000..83ac37d1 --- /dev/null +++ b/packages/PermissionManager/src/signers/ECDSASigner.sol @@ -0,0 +1,80 @@ +pragma solidity ^0.8.0; + +import { ECDSA } from "solady/src/utils/ECDSA.sol"; +import { ERC7579ValidatorBase } from "@rhinestone/modulekit/src/modules/ERC7579ValidatorBase.sol"; +import { ISigner } from "../ISigner.sol"; + +import "forge-std/console2.sol"; + +contract ECDSASigner is ISigner { + using ECDSA for bytes32; + + mapping( + address caller => mapping(bytes32 permissionId => mapping(address smartAccount => address)) + ) public signer; + + function registerSigner( + address smartAccount, + bytes32 permissionId, + bytes calldata data + ) + external + payable + override + { + console2.logBytes(data); + require( + signer[msg.sender][permissionId][smartAccount] == address(0), + "ECDSASigner: smartAccount already registered" + ); + require(data.length == 20, "ECDSASigner: invalid signer address"); + address signerAddress = address(bytes20(data[0:20])); + signer[msg.sender][permissionId][smartAccount] = signerAddress; + } + + function validateUserOp( + address smartAccount, + bytes32 permissionId, + bytes32 userOpHash, + bytes calldata signature + ) + external + payable + override + returns (ERC7579ValidatorBase.ValidationData) + { + require( + signer[msg.sender][permissionId][smartAccount] != address(0), + "ECDSASigner: smartAccount not registered" + ); + address recovered = ECDSA.toEthSignedMessageHash(userOpHash).recover(signature); + if (recovered == signer[msg.sender][permissionId][smartAccount]) { + return ERC7579ValidatorBase.ValidationData.wrap(0); + } + return ERC7579ValidatorBase.ValidationData.wrap(1); + } + + function validateSignature( + address smartAccount, + bytes32 permissionId, + bytes32 messageHash, + bytes calldata signature + ) + external + view + override + returns (ERC7579ValidatorBase.ValidationData) + { + address signerAddress = signer[msg.sender][permissionId][smartAccount]; + require(signerAddress != address(0), "ECDSASigner: smartAccount not registered"); + if (messageHash.recover(signature) == signerAddress) { + return ERC7579ValidatorBase.ValidationData.wrap(0); + } + bytes32 ethHash = ECDSA.toEthSignedMessageHash(messageHash); + address recovered = ethHash.recover(signature); + if (recovered == signerAddress) { + return ERC7579ValidatorBase.ValidationData.wrap(0); + } + return ERC7579ValidatorBase.ValidationData.wrap(1); + } +} diff --git a/packages/PermissionManager/src/signers/WebAuthnSigner.sol.bak b/packages/PermissionManager/src/signers/WebAuthnSigner.sol.bak new file mode 100644 index 00000000..40c8cd57 --- /dev/null +++ b/packages/PermissionManager/src/signers/WebAuthnSigner.sol.bak @@ -0,0 +1,108 @@ +pragma solidity ^0.8.0; + +import { WebAuthn } from "p256-verifier/WebAuthn.sol"; +import { ISigner } from "../ISigner.sol"; +import { ValidationData } from "../../../common/Types.sol"; +import { SIG_VALIDATION_FAILED } from "../../../common/Constants.sol"; + +struct WebAuthnValidatorData { + uint256 x; + uint256 y; +} + +contract WebAuthnSigner is ISigner { + mapping( + address caller + => mapping(bytes32 permissionId => mapping(address kernel => WebAuthnValidatorData)) + ) public webAuthnValidatorStorage; + + function registerSigner( + address kernel, + bytes32 permissionId, + bytes calldata data + ) + external + payable + override + { + WebAuthnValidatorData memory pubKey = abi.decode(data, (WebAuthnValidatorData)); + require( + webAuthnValidatorStorage[msg.sender][permissionId][kernel].x == 0 + && webAuthnValidatorStorage[msg.sender][permissionId][kernel].y == 0, + "WebAuthnSigner: kernel already registered" + ); + require(pubKey.x != 0 && pubKey.y != 0, "WebAuthnSigner: invalid public key"); + webAuthnValidatorStorage[msg.sender][permissionId][kernel] = pubKey; + } + + function validateUserOp( + address kernel, + bytes32 permissionId, + bytes32 userOpHash, + bytes calldata signature + ) + external + payable + override + returns (ValidationData) + { + return _verifySignature(kernel, permissionId, userOpHash, signature); + } + + function validateSignature( + address kernel, + bytes32 permissionId, + bytes32 messageHash, + bytes calldata signature + ) + external + view + override + returns (ValidationData) + { + return _verifySignature(kernel, permissionId, messageHash, signature); + } + + function _verifySignature( + address sender, + bytes32 permissionId, + bytes32 hash, + bytes calldata signature + ) + private + view + returns (ValidationData) + { + ( + bytes memory authenticatorData, + string memory clientDataJSON, + uint256 challengeLocation, + uint256 responseTypeLocation, + uint256 r, + uint256 s + ) = abi.decode(signature, (bytes, string, uint256, uint256, uint256, uint256)); + + WebAuthnValidatorData memory pubKey = + webAuthnValidatorStorage[msg.sender][permissionId][sender]; + require(pubKey.x != 0 && pubKey.y != 0, "WebAuthnSigner: kernel not registered"); + + bool isValid = WebAuthn.verifySignature( + abi.encodePacked(hash), + authenticatorData, + true, // TODO: check if this needs to be true always + clientDataJSON, + challengeLocation, + responseTypeLocation, + r, + s, + pubKey.x, + pubKey.y + ); + + if (isValid) { + return ValidationData.wrap(0); + } + + return SIG_VALIDATION_FAILED; + } +} diff --git a/packages/PermissionManager/test/PermissionValidator.t.sol b/packages/PermissionManager/test/PermissionValidator.t.sol new file mode 100644 index 00000000..31239dea --- /dev/null +++ b/packages/PermissionManager/test/PermissionValidator.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "forge-std/Test.sol"; +import "@rhinestone/modulekit/src/ModuleKit.sol"; +import "@rhinestone/modulekit/src/Helpers.sol"; +import "@rhinestone/modulekit/src/Core.sol"; + +import { MODULE_TYPE_VALIDATOR } from "@rhinestone/modulekit/src/external/ERC7579.sol"; + +import "src/PermissionValidator.sol"; +import "src/policies/SudoPolicy.sol"; +import "src/signers/ECDSASigner.sol"; + +contract PermissionValidatorTest is RhinestoneModuleKit, Test { + using ModuleKitHelpers for *; + using ModuleKitSCM for *; + using ModuleKitUserOp for *; + + AccountInstance internal instance; + Account internal signer = makeAccount("signer"); + PermissionValidator internal permissionValidator; + + SudoPolicy internal sudoPolicy; + ECDSASigner internal ecdsaSigner; + + bytes32 permissionId; + + function setUp() public { + vm.warp(123_123_123); + instance = makeAccountInstance("instance"); + vm.deal(instance.account, 100 ether); + + permissionValidator = new PermissionValidator(); + vm.label(address(permissionValidator), "PermissionValidator"); + sudoPolicy = new SudoPolicy(); + vm.label(address(sudoPolicy), "SudoPolicy"); + ecdsaSigner = new ECDSASigner(); + vm.label(address(ecdsaSigner), "ECDSASigner"); + + _setupPermission(); + instance.installModule({ + module: address(permissionValidator), + moduleTypeId: MODULE_TYPE_VALIDATOR, + data: "" + }); + } + + function _setupPermission() internal { + vm.prank(instance.account); + + bytes12 flag = MAX_FLAG; + bytes memory signerData = abi.encodePacked(signer.addr); + PolicyConfig[] memory policyConfigs = new PolicyConfig[](1); + policyConfigs[0] = PolicyConfigLib.pack({ addr: IPolicy(address(sudoPolicy)), flag: flag }); + bytes[] memory policyDatas = new bytes[](1); + policyDatas[0] = hex"41414141"; + permissionId = permissionValidator.registerPermission({ + nonce: 0, + flag: flag, + signer: ISigner(address(ecdsaSigner)), + validAfter: ValidAfter.wrap(uint48(block.timestamp - 1)), + validUntil: ValidUntil.wrap(type(uint48).max), + policy: policyConfigs, + signerData: signerData, + policyData: policyDatas + }); + } + + function test_sendETH() public { + address target = makeAddr("target"); + uint256 balanceBefore = target.balance; + uint256 value = 1 ether; + bytes memory callData = ""; + + // instance.exec({ target: address(target), value: value, callData: callData }); + + UserOpData memory userOpData = + instance.getExecOps(target, value, callData, address(permissionValidator)); + // sign userOp with default signature + userOpData.userOp.signature = abi.encodePacked( + permissionId, ecdsaSign(signer.key, ECDSA.toEthSignedMessageHash(userOpData.userOpHash)) + ); + + // send userOp to entrypoint + userOpData.execUserOps(); + assertEq(target.balance, balanceBefore + value); + } +} diff --git a/packages/modulekit/src/modules/ERC7579HookDestruct.sol b/packages/modulekit/src/modules/ERC7579HookDestruct.sol index 923ddae1..b42c9dd2 100644 --- a/packages/modulekit/src/modules/ERC7579HookDestruct.sol +++ b/packages/modulekit/src/modules/ERC7579HookDestruct.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.23; import { ERC7579HookBase } from "./ERC7579HookBase.sol"; import { IERC7579Account } from "../Accounts.sol"; import { ExecutionLib, Execution } from "erc7579/lib/ExecutionLib.sol"; +import { PackedUserOperation } from "../external/ERC4337.sol"; import { ModeLib, CallType, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7de6ea5..d4afe7e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,6 +135,60 @@ importers: specifier: github:transmissions11/solmate version: github.com/transmissions11/solmate/c892309933b25c03d32b1b0d674df7ae292ba925 + packages/PermissionManager: + devDependencies: + '@ERC4337/account-abstraction': + specifier: github:kopy-kat/account-abstraction#develop + version: github.com/kopy-kat/account-abstraction/396bda190ddc00919339986c72c580c160a15587(ethers@5.4.0)(hardhat@2.20.1)(lodash@4.17.21)(typechain@5.2.0) + '@ERC4337/account-abstraction-v0.6': + specifier: github:eth-infinitism/account-abstraction#v0.6.0 + version: github.com/eth-infinitism/account-abstraction/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.4.0)(hardhat@2.20.1)(lodash@4.17.21)(typechain@5.2.0) + '@openzeppelin/contracts': + specifier: 5.0.1 + version: 5.0.1 + '@prb/math': + specifier: ^4.0.2 + version: 4.0.2 + '@rhinestone/modulekit': + specifier: workspace:* + version: link:../modulekit + '@rhinestone/safe7579': + specifier: workspace:* + version: link:../../accounts/safe7579 + '@rhinestone/sessionkeymanager': + specifier: workspace:* + version: link:../SessionKeyManager + '@safe-global/safe-contracts': + specifier: ^1.4.1 + version: 1.4.1(ethers@5.4.0) + ds-test: + specifier: github:dapphub/ds-test + version: github.com/dapphub/ds-test/e282159d5170298eb2455a6c05280ab5a73a4ef0 + erc4337-validation: + specifier: github:rhinestonewtf/erc4337-validation + version: github.com/rhinestonewtf/erc4337-validation/5bc7fa2dec4ed815d389c41df0c93151e1ff6cae + erc7579: + specifier: github:erc7579/erc7579-implementation#feature/hookRevert + version: github.com/erc7579/erc7579-implementation/240e114638eaa10facc01cd8cb6d7a32e1c63269 + forge-std: + specifier: github:foundry-rs/forge-std + version: github.com/foundry-rs/forge-std/2f6762e4f73f3d835457c220b5f62dfeeb6f6341 + p256-verifier: + specifier: https://github.com/daimo-eth/p256-verifier + version: github.com/daimo-eth/p256-verifier/29475ae300ec95d98d5c7cc34c094846f0aa2dcd + prettier: + specifier: ^2.8.8 + version: 2.8.8 + sentinellist: + specifier: github:zeroknots/sentinellist + version: github.com/zeroknots/sentinellist/5f851f29b5d5e0fd4f5cdc63a2ccd7865ab1802d + solady: + specifier: github:vectorized/solady + version: github.com/vectorized/solady/d699161248fdb571a35fe12f4bd3077032f33806 + solhint: + specifier: ^4.1.1 + version: 4.1.1(typescript@5.3.3) + packages/SessionKeyManager: devDependencies: '@ERC4337/account-abstraction': @@ -261,15 +315,15 @@ packages: /@ethersproject/abi@5.4.0: resolution: {integrity: sha512-9gU2H+/yK1j2eVMdzm6xvHSnMxk8waIHQGYCZg5uvAyH0rsAzxkModzBSpbAkAuhKFEovC2S9hM4nPuLym8IZw==} dependencies: - '@ethersproject/address': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/constants': 5.4.0 - '@ethersproject/hash': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/strings': 5.4.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 dev: true /@ethersproject/abi@5.7.0: @@ -289,13 +343,13 @@ packages: /@ethersproject/abstract-provider@5.4.0: resolution: {integrity: sha512-vPBR7HKUBY0lpdllIn7tLIzNN7DrVnhCLKSzY0l8WAwxz686m/aL7ASDzrVxV93GJtIub6N2t4dfZ29CkPOxgA==} dependencies: - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/networks': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/transactions': 5.4.0 - '@ethersproject/web': 5.4.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/properties': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 dev: true /@ethersproject/abstract-provider@5.7.0: @@ -313,11 +367,11 @@ packages: /@ethersproject/abstract-signer@5.4.0: resolution: {integrity: sha512-AieQAzt05HJZS2bMofpuxMEp81AHufA5D6M4ScKwtolj041nrfIbIi8ciNW7+F59VYxXq+V4c3d568Q6l2m8ew==} dependencies: - '@ethersproject/abstract-provider': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 dev: true /@ethersproject/abstract-signer@5.7.0: @@ -333,11 +387,11 @@ packages: /@ethersproject/address@5.4.0: resolution: {integrity: sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==} dependencies: - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/rlp': 5.4.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/rlp': 5.7.0 dev: true /@ethersproject/address@5.7.0: @@ -353,7 +407,7 @@ packages: /@ethersproject/base64@5.4.0: resolution: {integrity: sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==} dependencies: - '@ethersproject/bytes': 5.4.0 + '@ethersproject/bytes': 5.7.0 dev: true /@ethersproject/base64@5.7.0: @@ -365,8 +419,8 @@ packages: /@ethersproject/basex@5.4.0: resolution: {integrity: sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/properties': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/properties': 5.7.0 dev: true /@ethersproject/basex@5.7.0: @@ -379,8 +433,8 @@ packages: /@ethersproject/bignumber@5.4.0: resolution: {integrity: sha512-OXUu9f9hO3vGRIPxU40cignXZVaYyfx6j9NNMjebKdnaCL3anCLSSy8/b8d03vY6dh7duCC0kW72GEC4tZer2w==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 bn.js: 4.12.0 dev: true @@ -395,7 +449,7 @@ packages: /@ethersproject/bytes@5.4.0: resolution: {integrity: sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==} dependencies: - '@ethersproject/logger': 5.4.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/bytes@5.7.0: @@ -407,7 +461,7 @@ packages: /@ethersproject/constants@5.4.0: resolution: {integrity: sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==} dependencies: - '@ethersproject/bignumber': 5.4.0 + '@ethersproject/bignumber': 5.7.0 dev: true /@ethersproject/constants@5.7.0: @@ -419,16 +473,16 @@ packages: /@ethersproject/contracts@5.4.0: resolution: {integrity: sha512-hkO3L3IhS1Z3ZtHtaAG/T87nQ7KiPV+/qnvutag35I0IkiQ8G3ZpCQ9NNOpSCzn4pWSW4CfzmtE02FcqnLI+hw==} dependencies: - '@ethersproject/abi': 5.4.0 - '@ethersproject/abstract-provider': 5.4.0 - '@ethersproject/abstract-signer': 5.4.0 - '@ethersproject/address': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/constants': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/transactions': 5.4.0 + '@ethersproject/abi': 5.7.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/transactions': 5.7.0 dev: true /@ethersproject/contracts@5.7.0: @@ -449,14 +503,14 @@ packages: /@ethersproject/hash@5.4.0: resolution: {integrity: sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==} dependencies: - '@ethersproject/abstract-signer': 5.4.0 - '@ethersproject/address': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/strings': 5.4.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 dev: true /@ethersproject/hash@5.7.0: @@ -476,18 +530,18 @@ packages: /@ethersproject/hdnode@5.4.0: resolution: {integrity: sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==} dependencies: - '@ethersproject/abstract-signer': 5.4.0 - '@ethersproject/basex': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/pbkdf2': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/sha2': 5.4.0 - '@ethersproject/signing-key': 5.4.0 - '@ethersproject/strings': 5.4.0 - '@ethersproject/transactions': 5.4.0 - '@ethersproject/wordlists': 5.4.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/basex': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/pbkdf2': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/wordlists': 5.7.0 dev: true /@ethersproject/hdnode@5.7.0: @@ -510,17 +564,17 @@ packages: /@ethersproject/json-wallets@5.4.0: resolution: {integrity: sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==} dependencies: - '@ethersproject/abstract-signer': 5.4.0 - '@ethersproject/address': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/hdnode': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/pbkdf2': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/random': 5.4.0 - '@ethersproject/strings': 5.4.0 - '@ethersproject/transactions': 5.4.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/hdnode': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/pbkdf2': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/random': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 aes-js: 3.0.0 scrypt-js: 3.0.1 dev: true @@ -546,7 +600,7 @@ packages: /@ethersproject/keccak256@5.4.0: resolution: {integrity: sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==} dependencies: - '@ethersproject/bytes': 5.4.0 + '@ethersproject/bytes': 5.7.0 js-sha3: 0.5.7 dev: true @@ -568,7 +622,7 @@ packages: /@ethersproject/networks@5.4.0: resolution: {integrity: sha512-5fywtKRDcnaVeA5SjxXH3DOQqe/IbeD/plwydi94SdPps1fbDUrnO6SzDExaruBZXxpxJcO9upG9UComsei4bg==} dependencies: - '@ethersproject/logger': 5.4.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/networks@5.7.1: @@ -580,8 +634,8 @@ packages: /@ethersproject/pbkdf2@5.4.0: resolution: {integrity: sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/sha2': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/sha2': 5.7.0 dev: true /@ethersproject/pbkdf2@5.7.0: @@ -594,7 +648,7 @@ packages: /@ethersproject/properties@5.4.0: resolution: {integrity: sha512-7jczalGVRAJ+XSRvNA6D5sAwT4gavLq3OXPuV/74o3Rd2wuzSL035IMpIMgei4CYyBdialJMrTqkOnzccLHn4A==} dependencies: - '@ethersproject/logger': 5.4.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/properties@5.7.0: @@ -606,23 +660,23 @@ packages: /@ethersproject/providers@5.4.0: resolution: {integrity: sha512-XRmI9syLnkNdLA8ikEeg0duxmwSWTTt9S+xabnTOyI51JPJyhQ0QUNT+wvmod218ebb7rLupHDPQ7UVe2/+Tjg==} dependencies: - '@ethersproject/abstract-provider': 5.4.0 - '@ethersproject/abstract-signer': 5.4.0 - '@ethersproject/address': 5.4.0 - '@ethersproject/basex': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/constants': 5.4.0 - '@ethersproject/hash': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/networks': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/random': 5.4.0 - '@ethersproject/rlp': 5.4.0 - '@ethersproject/sha2': 5.4.0 - '@ethersproject/strings': 5.4.0 - '@ethersproject/transactions': 5.4.0 - '@ethersproject/web': 5.4.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/basex': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/properties': 5.7.0 + '@ethersproject/random': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 bech32: 1.1.4 ws: 7.4.6 transitivePeerDependencies: @@ -661,8 +715,8 @@ packages: /@ethersproject/random@5.4.0: resolution: {integrity: sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/random@5.7.0: @@ -675,8 +729,8 @@ packages: /@ethersproject/rlp@5.4.0: resolution: {integrity: sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/rlp@5.7.0: @@ -689,8 +743,8 @@ packages: /@ethersproject/sha2@5.4.0: resolution: {integrity: sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 hash.js: 1.1.7 dev: true @@ -705,9 +759,9 @@ packages: /@ethersproject/signing-key@5.4.0: resolution: {integrity: sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 bn.js: 4.12.0 elliptic: 6.5.4 hash.js: 1.1.7 @@ -727,11 +781,11 @@ packages: /@ethersproject/solidity@5.4.0: resolution: {integrity: sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==} dependencies: - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/sha2': 5.4.0 - '@ethersproject/strings': 5.4.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/strings': 5.7.0 dev: true /@ethersproject/solidity@5.7.0: @@ -748,9 +802,9 @@ packages: /@ethersproject/strings@5.4.0: resolution: {integrity: sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/constants': 5.4.0 - '@ethersproject/logger': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/strings@5.7.0: @@ -764,15 +818,15 @@ packages: /@ethersproject/transactions@5.4.0: resolution: {integrity: sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==} dependencies: - '@ethersproject/address': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/constants': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/rlp': 5.4.0 - '@ethersproject/signing-key': 5.4.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/signing-key': 5.7.0 dev: true /@ethersproject/transactions@5.7.0: @@ -792,9 +846,9 @@ packages: /@ethersproject/units@5.4.0: resolution: {integrity: sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==} dependencies: - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/constants': 5.4.0 - '@ethersproject/logger': 5.4.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 dev: true /@ethersproject/units@5.7.0: @@ -808,21 +862,21 @@ packages: /@ethersproject/wallet@5.4.0: resolution: {integrity: sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==} dependencies: - '@ethersproject/abstract-provider': 5.4.0 - '@ethersproject/abstract-signer': 5.4.0 - '@ethersproject/address': 5.4.0 - '@ethersproject/bignumber': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/hash': 5.4.0 - '@ethersproject/hdnode': 5.4.0 - '@ethersproject/json-wallets': 5.4.0 - '@ethersproject/keccak256': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/random': 5.4.0 - '@ethersproject/signing-key': 5.4.0 - '@ethersproject/transactions': 5.4.0 - '@ethersproject/wordlists': 5.4.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/hdnode': 5.7.0 + '@ethersproject/json-wallets': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/random': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/wordlists': 5.7.0 dev: true /@ethersproject/wallet@5.7.0: @@ -848,11 +902,11 @@ packages: /@ethersproject/web@5.4.0: resolution: {integrity: sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==} dependencies: - '@ethersproject/base64': 5.4.0 - '@ethersproject/bytes': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/strings': 5.4.0 + '@ethersproject/base64': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 dev: true /@ethersproject/web@5.7.1: @@ -868,11 +922,11 @@ packages: /@ethersproject/wordlists@5.4.0: resolution: {integrity: sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==} dependencies: - '@ethersproject/bytes': 5.4.0 - '@ethersproject/hash': 5.4.0 - '@ethersproject/logger': 5.4.0 - '@ethersproject/properties': 5.4.0 - '@ethersproject/strings': 5.4.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 dev: true /@ethersproject/wordlists@5.7.0: @@ -3784,7 +3838,7 @@ packages: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true dependencies: - glob: 7.2.0 + glob: 7.2.3 dev: true /ripemd160@2.0.2: @@ -4456,6 +4510,12 @@ packages: ethers: 5.7.2 dev: true + github.com/daimo-eth/p256-verifier/29475ae300ec95d98d5c7cc34c094846f0aa2dcd: + resolution: {tarball: https://codeload.github.com/daimo-eth/p256-verifier/tar.gz/29475ae300ec95d98d5c7cc34c094846f0aa2dcd} + name: p256-verifier + version: 0.0.0 + dev: true + github.com/dapphub/ds-test/e282159d5170298eb2455a6c05280ab5a73a4ef0: resolution: {tarball: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0} name: ds-test @@ -4554,6 +4614,12 @@ packages: version: 1.7.6 dev: true + github.com/foundry-rs/forge-std/2f6762e4f73f3d835457c220b5f62dfeeb6f6341: + resolution: {tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/2f6762e4f73f3d835457c220b5f62dfeeb6f6341} + name: forge-std + version: 1.7.6 + dev: true + github.com/kopy-kat/account-abstraction/396bda190ddc00919339986c72c580c160a15587(ethers@5.4.0)(hardhat@2.20.1)(lodash@4.17.21)(typechain@5.2.0): resolution: {tarball: https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/396bda190ddc00919339986c72c580c160a15587} id: github.com/kopy-kat/account-abstraction/396bda190ddc00919339986c72c580c160a15587 @@ -4659,6 +4725,15 @@ packages: solady: github.com/vectorized/solady/21009ce09f02c0e20ce4750b63577e8c0cc7ced8 dev: true + github.com/rhinestonewtf/erc4337-validation/5bc7fa2dec4ed815d389c41df0c93151e1ff6cae: + resolution: {tarball: https://codeload.github.com/rhinestonewtf/erc4337-validation/tar.gz/5bc7fa2dec4ed815d389c41df0c93151e1ff6cae} + name: '@rhinestone/erc4337-validation' + version: 0.0.1-alpha + dependencies: + '@openzeppelin/contracts': 5.0.1 + solady: github.com/vectorized/solady/e7024bee47b1623f436ee491ca9458a6dc8abce9 + dev: true + github.com/sablier-labs/solarray/6bf10cb34cdace52a3ba5fe437e78cc82df92684: resolution: {tarball: https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684} name: solarray @@ -4683,6 +4758,18 @@ packages: version: 0.0.170 dev: true + github.com/vectorized/solady/d699161248fdb571a35fe12f4bd3077032f33806: + resolution: {tarball: https://codeload.github.com/vectorized/solady/tar.gz/d699161248fdb571a35fe12f4bd3077032f33806} + name: solady + version: 0.0.172 + dev: true + + github.com/vectorized/solady/e7024bee47b1623f436ee491ca9458a6dc8abce9: + resolution: {tarball: https://codeload.github.com/vectorized/solady/tar.gz/e7024bee47b1623f436ee491ca9458a6dc8abce9} + name: solady + version: 0.0.173 + dev: true + github.com/zeroknots/sentinellist/5f851f29b5d5e0fd4f5cdc63a2ccd7865ab1802d: resolution: {tarball: https://codeload.github.com/zeroknots/sentinellist/tar.gz/5f851f29b5d5e0fd4f5cdc63a2ccd7865ab1802d} name: sentinellist