diff --git a/.gas-snapshot b/.gas-snapshot deleted file mode 100644 index f7b80914..00000000 --- a/.gas-snapshot +++ /dev/null @@ -1,37 +0,0 @@ -ERC7484SecurityPolicyPluginTest:testShouldAllowModuleInstallationIfEnoughAttestationsExists() (gas: 784547) -ERC7484SecurityPolicyPluginTest:testShouldNotAllowModuleInstallationIfAttestationsAreRevoked() (gas: 821945) -ERC7484SecurityPolicyPluginTest:testShouldNotAllowModuleInstallationIfInsufficientAttestationsExists() (gas: 622095) -ERC7484SecurityPolicyPluginTest:testShouldNotAllowModuleInstallationIfNotConfigured() (gas: 777734) -ERC7484SecurityPolicyPluginTest:testShouldSetConfiguration() (gas: 233126) -SABasicsTest:testDeploySAWithDefaultModule() (gas: 307004) -SABasicsTest:testExecuteBatch() (gas: 291378) -SecurityPolicyManagerPluginModuleInstallationTest:testModuleInstallation() (gas: 277072) -SecurityPolicyManagerPluginModuleInstallationTest:testModuleInstallationWithSetup() (gas: 304682) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationIfExecTxFromModuleFailsWithSetup() (gas: 117576) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationIfExecTxFromModuleInstallationFails() (gas: 241143) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationIfModuleInstallationFails() (gas: 252445) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationIfModuleInstallationFailsWithSetup() (gas: 129503) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationIfModuleIsNotAContract() (gas: 116774) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationIfSecurityPolicyIsNotSatisifedOnInstalledPlugin() (gas: 145516) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationWithSetupIfModuleIsNotAContract() (gas: 130605) -SecurityPolicyManagerPluginModuleInstallationTest:testShouldRevertModuleInstallationWithSetupIfSecurityPolicyIsNotSatisifedOnInstalledPlugin() (gas: 223417) -SecurityPolicyManagerPluginPluginManagementTest:testAddAndRemoveAllPolicies() (gas: 416523) -SecurityPolicyManagerPluginPluginManagementTest:testDisableSingleSecurityPolicyPlugin() (gas: 279805) -SecurityPolicyManagerPluginPluginManagementTest:testDisableSingleSecurityPolicyPluginsRange() (gas: 292542) -SecurityPolicyManagerPluginPluginManagementTest:testEnableMultipleSecurityPolicyPlugins() (gas: 310086) -SecurityPolicyManagerPluginPluginManagementTest:testEnableSingleSecurityPolicyPlugin() (gas: 165357) -SecurityPolicyManagerPluginPluginManagementTest:testSecurityPoliciesQueryPaginated() (gas: 289892) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingAlreadyDisabledPolicySingleDisable() (gas: 285580) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingAlreadyEnabledPolicySingleDisable() (gas: 130847) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingRangeWithInvalidPointer() (gas: 309745) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingRangeWithInvalidRange() (gas: 319277) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingSentinelAddressPolicySingleDisable() (gas: 308603) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingWithInvalidPointerSingleDisable() (gas: 309880) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowDisablingZeroAddressPolicySingleDisable() (gas: 308690) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowEmptyEnableList() (gas: 131153) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowEnablingAlreadyEnabledPolicySMultiEnable() (gas: 220217) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowEnablingAlreadyEnabledPolicySingleEnable() (gas: 217556) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowPolicyAdditionWithSentinelAddressMulti() (gas: 135152) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowPolicyAdditionWithSentinelAddressSingle() (gas: 131252) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowPolicyAdditionWithZeroAddressMulti() (gas: 135107) -SecurityPolicyManagerPluginPluginManagementTest:testShouldNotAllowPolicyAdditionWithZeroAddressSingle() (gas: 131339) \ No newline at end of file diff --git a/.gitignore b/.gitignore index a25a226b..7e7cd101 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ deployments/localhost typings forge-cache out +.env.bak diff --git a/.solhint.json b/.solhint.json index d43fa7ee..1245d88d 100644 --- a/.solhint.json +++ b/.solhint.json @@ -21,7 +21,7 @@ "code-complexity": ["error", 7], "function-max-lines": ["error", 80], "max-line-length": ["warn", 120], - "max-states-count": ["error", 15], + "max-states-count": ["error", 20], "no-empty-blocks": "error", "no-unused-vars": "error", "payable-fallback": "off", diff --git a/audits/Biconomy Session Key Manager V2 Kawach.pdf b/audits/Biconomy Session Key Manager V2 Kawach.pdf new file mode 100644 index 00000000..a1e1e272 Binary files /dev/null and b/audits/Biconomy Session Key Manager V2 Kawach.pdf differ diff --git a/contracts/smart-account/interfaces/modules/IBaseAuthorizationModule.sol b/contracts/smart-account/interfaces/modules/IBaseAuthorizationModule.sol index bede17ca..815006aa 100644 --- a/contracts/smart-account/interfaces/modules/IBaseAuthorizationModule.sol +++ b/contracts/smart-account/interfaces/modules/IBaseAuthorizationModule.sol @@ -8,6 +8,4 @@ import {ISignatureValidator} from "../../interfaces/ISignatureValidator.sol"; interface IBaseAuthorizationModule is IAuthorizationModule, ISignatureValidator -{ - -} +{} diff --git a/contracts/smart-account/interfaces/modules/ISessionKeyManagerModule.sol b/contracts/smart-account/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModule.sol similarity index 100% rename from contracts/smart-account/interfaces/modules/ISessionKeyManagerModule.sol rename to contracts/smart-account/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModule.sol diff --git a/contracts/smart-account/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol b/contracts/smart-account/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol new file mode 100644 index 00000000..4c0a1ae9 --- /dev/null +++ b/contracts/smart-account/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/** + * @title Session Key Manager module for Biconomy Modular Smart Accounts. + * @dev Stores the Session Information explicity in the storage, instead of maintainting + * a merkle tree. + * This reduces the amount of calldata required to validate a session key, making it cheaper on + * L2s. + * Allows for a session to be enabled explicity, or being batched with the first usage of said session + * @author Ankur Dubey - + */ +interface ISessionKeyManagerModuleHybrid { + struct SessionData { + uint48 validUntil; + uint48 validAfter; + address sessionValidationModule; + bytes sessionKeyData; + } + + event SessionCreated( + address indexed sa, + bytes32 indexed sessionDataDigest, + SessionData data + ); + + event SessionDisabled( + address indexed sa, + bytes32 indexed sessionDataDigest + ); + + /** + * @dev creates a session for a smart account + * @param sessionData session data + */ + function enableSession(SessionData calldata sessionData) external; + + /** + * @dev creates multiple sessions for a smart account + * @param sessionDatas session data corresponding to multiple sessions to be enabled + */ + function enableSessions(SessionData[] calldata sessionDatas) external; + + /** + * @notice Explicitly disable a session. Can be useful in situations where a session + * needs to be disabled before it expires. + * @param _sessionDigest digest of session key data + */ + function disableSession(bytes32 _sessionDigest) external; + + /** + * @notice Explicitly disable multiple sessions. Can be useful in situations where a session + * needs to be disabled before it expires. + * @param _sessionDigests digests of session key datas to be disabled + */ + function disableSessions(bytes32[] calldata _sessionDigests) external; + + /** + * @notice Returns session data for a given session digest and smart account + * @param _sessionDataDigest digest of session key data + * @param _sa smart account for which session data is to be fetched + * @return data SessionData struct + */ + function enabledSessionsData( + bytes32 _sessionDataDigest, + address _sa + ) external view returns (SessionData memory data); + + /** + * @dev Returns session data digest + * @param _data session data + * @return digest of session data + */ + function sessionDataDigest( + SessionData calldata _data + ) external pure returns (bytes32); +} diff --git a/contracts/smart-account/modules/BaseAuthorizationModule.sol b/contracts/smart-account/modules/BaseAuthorizationModule.sol index 51b9a0a0..415e8d71 100644 --- a/contracts/smart-account/modules/BaseAuthorizationModule.sol +++ b/contracts/smart-account/modules/BaseAuthorizationModule.sol @@ -10,6 +10,4 @@ import {AuthorizationModulesConstants} from "./AuthorizationModulesConstants.sol abstract contract BaseAuthorizationModule is IBaseAuthorizationModule, AuthorizationModulesConstants -{ - -} +{} diff --git a/contracts/smart-account/modules/BatchedSessionRouterModule.sol b/contracts/smart-account/modules/BatchedSessionRouterModule.sol index 571412d2..52fc0559 100644 --- a/contracts/smart-account/modules/BatchedSessionRouterModule.sol +++ b/contracts/smart-account/modules/BatchedSessionRouterModule.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.23; import {BaseAuthorizationModule} from "./BaseAuthorizationModule.sol"; import {ISessionValidationModule} from "../interfaces/modules/ISessionValidationModule.sol"; -import {ISessionKeyManagerModule} from "../interfaces/modules/ISessionKeyManagerModule.sol"; +import {ISessionKeyManagerModule} from "../interfaces/modules/SessionKeyManagers/ISessionKeyManagerModule.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {_packValidationData} from "@account-abstraction/contracts/core/Helpers.sol"; import {UserOperation} from "@account-abstraction/contracts/interfaces/UserOperation.sol"; diff --git a/contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerHybrid.sol b/contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerHybrid.sol new file mode 100644 index 00000000..0e6c546a --- /dev/null +++ b/contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerHybrid.sol @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/* solhint-disable function-max-lines*/ +/* solhint-disable ordering*/ + +import {_packValidationData} from "@account-abstraction/contracts/core/Helpers.sol"; +import {UserOperation, UserOperationLib} from "@account-abstraction/contracts/interfaces/UserOperation.sol"; +import {ISessionValidationModule} from "../../interfaces/modules/ISessionValidationModule.sol"; +import {ISessionKeyManagerModuleHybrid} from "../../interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol"; +import {ISignatureValidator, EIP1271_MAGIC_VALUE} from "../../interfaces/ISignatureValidator.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {BaseAuthorizationModule} from "../BaseAuthorizationModule.sol"; +import {IAuthorizationModule} from "../../interfaces/IAuthorizationModule.sol"; +import {ISmartAccount} from "../../interfaces/ISmartAccount.sol"; + +/** + * @title Session Key Manager module for Biconomy Modular Smart Accounts. + * @dev Stores the Session Information explicity in the storage, instead of maintainting + * a merkle tree. + * This reduces the amount of calldata required to validate a session key, making it cheaper on + * L2s. + * Allows for a session to be enabled explicity, or being batched with the first usage of said session + * @author Ankur Dubey - + * @author Fil Makarov - + */ +contract SessionKeyManagerHybrid is + BaseAuthorizationModule, + ISessionKeyManagerModuleHybrid +{ + using UserOperationLib for UserOperation; + + // Byte offset in userOp.signature where the module signature starts + uint256 private constant MODULE_SIGNATURE_OFFSET = 96; + + // For a given Session Data Digest and Smart Account, stores + // - the corresponding Session Data if the Session is enabled + // - nothing otherwise + mapping(bytes32 sessionDataDigest => mapping(address sa => SessionData data)) + internal _enabledSessionsData; + + /// @inheritdoc IAuthorizationModule + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash + ) external virtual override returns (uint256) { + if (_isBatchExecuteCall(userOp)) { + return _validateUserOpBatchExecute(userOp, userOpHash); + } else { + return _validateUserOpSingleExecute(userOp, userOpHash); + } + } + + /***************************** Single Call Handler ***********************************/ + + function _validateUserOpSingleExecute( + UserOperation calldata userOp, + bytes32 userOpHash + ) internal returns (uint256 rv) { + /* + * Module Signature Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x1 | 0x01 if sessionEnableTransaction, 0x00 otherwise + * 0x1 | -- | Data depending on the above flag + */ + bytes calldata moduleSignature = userOp + .signature[MODULE_SIGNATURE_OFFSET:]; + + if (_isSessionEnableTransaction(moduleSignature)) { + ( + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata sessionEnableData, + bytes calldata sessionEnableSignature, + bytes calldata sessionKeySignature + ) = _parseSessionEnableSignatureSingleCall(moduleSignature); + + _verifySessionEnableDataSignature( + sessionEnableData, + sessionEnableSignature, + userOp.getSender() + ); + + _validateSessionKeySessionEnableTransaction( + validUntil, + validAfter, + sessionKeyIndex, + sessionValidationModule, + sessionKeyData, + sessionEnableData + ); + + rv = _packValidationData( + //_packValidationData expects true if sig validation has failed, false otherwise + !ISessionValidationModule(sessionValidationModule) + .validateSessionUserOp( + userOp, + userOpHash, + sessionKeyData, + sessionKeySignature + ), + validUntil, + validAfter + ); + } else { + ( + bytes32 sessionDataDigest_, + bytes calldata sessionKeySignature + ) = _parseSessionDataPreEnabledSignatureSingleCall(moduleSignature); + + SessionData storage sessionData = _validateSessionKeyPreEnabled( + userOp.getSender(), + sessionDataDigest_ + ); + + rv = _packValidationData( + //_packValidationData expects true if sig validation has failed, false otherwise + !ISessionValidationModule(sessionData.sessionValidationModule) + .validateSessionUserOp( + userOp, + userOpHash, + sessionData.sessionKeyData, + sessionKeySignature + ), + sessionData.validUntil, + sessionData.validAfter + ); + } + } + + /***************************** Single Call Parsers ***********************************/ + + function _parseSessionEnableSignatureSingleCall( + bytes calldata _moduleSignature + ) + internal + pure + returns ( + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata sessionEnableData, + bytes calldata sessionEnableSignature, + bytes calldata sessionKeySignature + ) + { + /* + * Session Enable Signature Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x1 | Is Session Enable Transaction Flag + * 0x1 | 0x1 | Index of Session Key in Session Enable Data + * 0x2 | 0x6 | Valid Until + * 0x8 | 0x6 | Valid After + * 0xe | 0x14 | Session Validation Module Address + * 0x22 | -- | abi.encode(sessionKeyData, sessionEnableData, + * | | sessionEnableSignature, sessionKeySignature) + */ + assembly ("memory-safe") { + let offset := add(_moduleSignature.offset, 0x1) + + // Parse the closely packed non-abi encoded data + // offset refers to the starting byte of the data in msg.data + + // Extract the session key index + sessionKeyIndex := shr(248, calldataload(offset)) + offset := add(offset, 0x1) + + // Extract the validUntil and validAfter + validUntil := shr(208, calldataload(offset)) + offset := add(offset, 0x6) + + validAfter := shr(208, calldataload(offset)) + offset := add(offset, 0x6) + + // Extract the session validation module address + sessionValidationModule := shr(96, calldataload(offset)) + offset := add(offset, 0x14) + + // Parse the abi encoded data + // baseOffset refers to the starting byte of this section, which starts with a list of offsets to the actual data + // dataPointer refers to the starting byte of the actual data + // offset refers to the offset of the "offset" to the actual data in the list of offsets + + let baseOffset := offset + let dataPointer := add(baseOffset, calldataload(offset)) + + // Extract the session key data + sessionKeyData.offset := add(dataPointer, 0x20) + sessionKeyData.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the session enable data + dataPointer := add(baseOffset, calldataload(offset)) + sessionEnableData.offset := add(dataPointer, 0x20) + sessionEnableData.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the session enable signature + dataPointer := add(baseOffset, calldataload(offset)) + sessionEnableSignature.offset := add(dataPointer, 0x20) + sessionEnableSignature.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the session key signature + dataPointer := add(baseOffset, calldataload(offset)) + sessionKeySignature.offset := add(dataPointer, 0x20) + sessionKeySignature.length := calldataload(dataPointer) + } + } + + function _parseSessionDataPreEnabledSignatureSingleCall( + bytes calldata _moduleSignature + ) + internal + pure + returns (bytes32 sessionDataDigest_, bytes calldata sessionKeySignature) + { + /* + * Session Data Pre Enabled Signature Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x1 | Is Session Enable Transaction Flag + * 0x1 | -- | abi.encode(bytes32 sessionDataDigest, sessionKeySignature) + */ + assembly ("memory-safe") { + let offset := add(_moduleSignature.offset, 0x1) + let baseOffset := offset + + // Extract the session data digest + sessionDataDigest_ := calldataload(offset) + offset := add(offset, 0x20) + + let dataPointer := add(baseOffset, calldataload(offset)) + + // Extract the session key signature + sessionKeySignature.offset := add(dataPointer, 0x20) + sessionKeySignature.length := calldataload(dataPointer) + } + } + + /***************************** Batch Call Handler ***********************************/ + + function _validateUserOpBatchExecute( + UserOperation calldata userOp, + bytes32 userOpHash + ) internal returns (uint256) { + // Parse session enable data, signature list and main session signature + ( + bytes[] calldata sessionEnableDataList, + bytes[] calldata sessionEnableSignatureList, + bytes[] calldata sessionInfos, + bytes calldata sessionKeySignature + ) = _parseValidateUserOpBatchSignature( + userOp.signature[MODULE_SIGNATURE_OFFSET:] + ); + + // Pre-verify all session enable data signatures + address userOpSender = userOp.getSender(); + _verifySessionEnableDataSignatures( + sessionEnableDataList, + sessionEnableSignatureList, + userOpSender + ); + + // Calcuate the expected common signer of each operation + address expectedSessionKeySigner = ECDSA.recover( + ECDSA.toEthSignedMessageHash(userOpHash), + sessionKeySignature + ); + + // Parse batch call calldata to get to,value,calldatas for every operation + uint256 length = sessionInfos.length; + ( + address[] calldata destinations, + uint256[] calldata callValues, + bytes[] calldata operationCalldatas + ) = _parseBatchCallCalldata(userOp.callData); + require( + destinations.length == length, + "SKM: SessionInfo length mismatch" + ); + + // For each operation in the batch, verify it using the corresponding session key + // Also find the earliest validUntil and latest validAfter + uint48 earliestValidUntil = type(uint48).max; + uint48 latestValidAfter; + for (uint256 i = 0; i < length; ++i) { + bytes calldata sessionInfo = sessionInfos[i]; + uint48 validUntil; + uint48 validAfter; + address sessionKeyReturned; + + if (_isSessionEnableTransaction(sessionInfo)) { + ( + validUntil, + validAfter, + sessionKeyReturned + ) = _validateUserOpBatchExecuteSessionEnableTransaction( + sessionInfo, + sessionEnableDataList, + destinations[i], + callValues[i], + operationCalldatas[i] + ); + } else { + ( + validUntil, + validAfter, + sessionKeyReturned + ) = _validateUserOpBatchExecutePreEnabledTransaction( + sessionInfo, + destinations[i], + callValues[i], + operationCalldatas[i], + userOpSender + ); + } + + // compare if userOp was signed with the proper session key + if (expectedSessionKeySigner != sessionKeyReturned) + return SIG_VALIDATION_FAILED; + + // intersect validUntils and validAfters + if (validUntil != 0 && validUntil < earliestValidUntil) { + earliestValidUntil = validUntil; + } + if (validAfter > latestValidAfter) { + latestValidAfter = validAfter; + } + } + + return + _packValidationData( + false, // sig validation failed = false; if we are here, it is valid + earliestValidUntil, + latestValidAfter + ); + } + + function _validateUserOpBatchExecuteSessionEnableTransaction( + bytes calldata _sessionInfo, + bytes[] calldata _sessionEnableDataList, + address _destination, + uint256 _callValue, + bytes calldata _operationCalldata + ) + internal + returns ( + uint48 validUntil, + uint48 validAfter, + address sessionKeyReturned + ) + { + ( + uint256 sessionKeyEnableDataIndex, + uint256 sessionKeyIndex, + uint48 _validUntil, + uint48 _validAfter, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata callSpecificData + ) = _parseSessionEnableSignatureBatchCall(_sessionInfo); + + validUntil = _validUntil; + validAfter = _validAfter; + + if (sessionKeyEnableDataIndex >= _sessionEnableDataList.length) { + revert("SKM: SKEnableDataIndexInvalid"); + } + + _validateSessionKeySessionEnableTransaction( + validUntil, + validAfter, + sessionKeyIndex, + sessionValidationModule, + sessionKeyData, + _sessionEnableDataList[sessionKeyEnableDataIndex] // The signature has already been verified + ); + + sessionKeyReturned = ISessionValidationModule(sessionValidationModule) + .validateSessionParams( + _destination, + _callValue, + _operationCalldata, + sessionKeyData, + callSpecificData + ); + } + + function _validateUserOpBatchExecutePreEnabledTransaction( + bytes calldata _sessionInfo, + address _destination, + uint256 _callValue, + bytes calldata _operationCalldata, + address userOpSender + ) + internal + returns ( + uint48 validUntil, + uint48 validAfter, + address sessionKeyReturned + ) + { + ( + bytes32 sessionDataDigest_, + bytes calldata callSpecificData + ) = _parseSessionDataPreEnabledSignatureBatchCall(_sessionInfo); + + SessionData storage sessionData = _validateSessionKeyPreEnabled( + userOpSender, + sessionDataDigest_ + ); + + validUntil = sessionData.validUntil; + validAfter = sessionData.validAfter; + + sessionKeyReturned = ISessionValidationModule( + sessionData.sessionValidationModule + ).validateSessionParams( + _destination, + _callValue, + _operationCalldata, + sessionData.sessionKeyData, + callSpecificData + ); + } + + function _verifySessionEnableDataSignatures( + bytes[] calldata sessionEnableDataList, + bytes[] calldata sessionEnableSignatureList, + address userOpSender + ) internal view { + uint256 length = sessionEnableDataList.length; + if (length != sessionEnableSignatureList.length) { + revert("SKM: EDListLengthMismatch"); + } + for (uint256 i = 0; i < length; ) { + _verifySessionEnableDataSignature( + sessionEnableDataList[i], + sessionEnableSignatureList[i], + userOpSender + ); + unchecked { + ++i; + } + } + } + + /***************************** Batch Call Parsers ***********************************/ + + function _parseValidateUserOpBatchSignature( + bytes calldata _moduleSignature + ) + internal + pure + returns ( + bytes[] calldata sessionEnableDataList, + bytes[] calldata sessionEnableSignatureList, + bytes[] calldata sessionInfos, + bytes calldata sessionKeySignature + ) + { + { + /* + * Module Signature Layout + * abi.encode(bytes[] sessionEnableDataList, bytes[] sessionEnableSignatureList, + * bytes[] sessionInfo, bytes sessionKeySignature) + */ + assembly ("memory-safe") { + let offset := _moduleSignature.offset + let baseOffset := offset + + let dataPointer := add(baseOffset, calldataload(offset)) + + // Extract the session enable data list + sessionEnableDataList.offset := add(dataPointer, 0x20) + sessionEnableDataList.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the session enable signature list + dataPointer := add(baseOffset, calldataload(offset)) + sessionEnableSignatureList.offset := add(dataPointer, 0x20) + sessionEnableSignatureList.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the session info list + dataPointer := add(baseOffset, calldataload(offset)) + sessionInfos.offset := add(dataPointer, 0x20) + sessionInfos.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the session key signature + dataPointer := add(baseOffset, calldataload(offset)) + sessionKeySignature.offset := add(dataPointer, 0x20) + sessionKeySignature.length := calldataload(dataPointer) + } + } + } + + function _parseSessionDataPreEnabledSignatureBatchCall( + bytes calldata _moduleSignature + ) + internal + pure + returns (bytes32 sessionDataDigest_, bytes calldata callSpecificData) + { + /* + * Session Data Pre Enabled Signature Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x1 | Is Session Enable Transaction Flag + * 0x1 | 0x20 | bytes32 sessionDataDigest + * 0x21 | --- | abi.encode(callSpecificData) + */ + assembly ("memory-safe") { + let offset := add(_moduleSignature.offset, 0x1) + + // Extract the session data digest + sessionDataDigest_ := calldataload(offset) + offset := add(offset, 0x20) + + let baseOffset := offset + let dataPointer := add(baseOffset, calldataload(offset)) + + // Extract the session key signature + callSpecificData.offset := add(dataPointer, 0x20) + callSpecificData.length := calldataload(dataPointer) + } + } + + function _parseSessionEnableSignatureBatchCall( + bytes calldata _moduleSignature + ) + internal + pure + returns ( + uint256 sessionEnableDataIndex, + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata callSpecificData + ) + { + /* + * Session Enable Signature Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x1 | Is Session Enable Transaction Flag + * 0x1 | 0x1 | Index of Session Enable Data in Session Enable Data List + * 0x2 | 0x1 | Index of Session Key in Session Enable Data + * 0x3 | 0x6 | Valid Until + * 0x9 | 0x6 | Valid After + * 0xf | 0x14 | Session Validation Module Address + * 0x23 | -- | abi.encode(sessionKeyData, callSpecificData) + */ + assembly ("memory-safe") { + let offset := add(_moduleSignature.offset, 0x1) + + // Extract the session enable data index + sessionEnableDataIndex := shr(248, calldataload(offset)) + offset := add(offset, 0x1) + + // Extract the session key index + sessionKeyIndex := shr(248, calldataload(offset)) + offset := add(offset, 0x1) + + // Extract the validUntil and validAfter + validUntil := shr(208, calldataload(offset)) + offset := add(offset, 0x6) + + validAfter := shr(208, calldataload(offset)) + offset := add(offset, 0x6) + + // Extract the session validation module address + sessionValidationModule := shr(96, calldataload(offset)) + offset := add(offset, 0x14) + + let baseOffset := offset + let dataPointer := add(baseOffset, calldataload(offset)) + + // Extract the session key data + sessionKeyData.offset := add(dataPointer, 0x20) + sessionKeyData.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the call specific data + dataPointer := add(baseOffset, calldataload(offset)) + callSpecificData.offset := add(dataPointer, 0x20) + callSpecificData.length := calldataload(dataPointer) + } + } + + function _parseBatchCallCalldata( + bytes calldata _userOpCalldata + ) + internal + pure + returns ( + address[] calldata destinations, + uint256[] calldata callValues, + bytes[] calldata operationCalldatas + ) + { + /* + * Batch Call Calldata Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x4 | bytes4 function selector + * 0x4 | - | abi.encode(destinations, callValues, operationCalldatas) + */ + assembly ("memory-safe") { + let offset := add(_userOpCalldata.offset, 0x4) + let baseOffset := offset + + let dataPointer := add(baseOffset, calldataload(offset)) + + // Extract the destinations + destinations.offset := add(dataPointer, 0x20) + destinations.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the call values + dataPointer := add(baseOffset, calldataload(offset)) + callValues.offset := add(dataPointer, 0x20) + callValues.length := calldataload(dataPointer) + offset := add(offset, 0x20) + + // Extract the operation calldatas + dataPointer := add(baseOffset, calldataload(offset)) + operationCalldatas.offset := add(dataPointer, 0x20) + operationCalldatas.length := calldataload(dataPointer) + } + } + + /*********************** Session Management *******************************/ + + /// @inheritdoc ISessionKeyManagerModuleHybrid + function disableSession(bytes32 _sessionDigest) external override { + delete _enabledSessionsData[_sessionDigest][msg.sender]; + emit SessionDisabled(msg.sender, _sessionDigest); + } + + /// @inheritdoc ISessionKeyManagerModuleHybrid + function disableSessions( + bytes32[] calldata _sessionDigests + ) external override { + uint256 length = _sessionDigests.length; + for (uint256 i = 0; i < length; ++i) { + delete _enabledSessionsData[_sessionDigests[i]][msg.sender]; + emit SessionDisabled(msg.sender, _sessionDigests[i]); + } + } + + /// @inheritdoc ISessionKeyManagerModuleHybrid + function enableSession(SessionData calldata sessionData) external override { + bytes32 sessionDataDigest_ = sessionDataDigest(sessionData); + _enabledSessionsData[sessionDataDigest_][msg.sender] = sessionData; + emit SessionCreated(msg.sender, sessionDataDigest_, sessionData); + } + + /// @inheritdoc ISessionKeyManagerModuleHybrid + function enableSessions( + SessionData[] calldata _sessionDatas + ) external override { + uint256 length = _sessionDatas.length; + for (uint256 i = 0; i < length; ++i) { + SessionData calldata sessionData = _sessionDatas[i]; + + bytes32 sessionDataDigest_ = sessionDataDigest(sessionData); + _enabledSessionsData[sessionDataDigest_][msg.sender] = sessionData; + emit SessionCreated(msg.sender, sessionDataDigest_, sessionData); + } + } + + /// @inheritdoc ISessionKeyManagerModuleHybrid + function enabledSessionsData( + bytes32 _sessionDataDigest, + address _sa + ) external view override returns (SessionData memory data) { + data = _enabledSessionsData[_sessionDataDigest][_sa]; + } + + /********************** ISignatureValidator ****************************/ + + /// @inheritdoc ISignatureValidator + function isValidSignature( + bytes32, + bytes memory + ) public pure virtual override returns (bytes4) { + return 0xffffffff; // do not support it here + } + + /// @inheritdoc ISignatureValidator + function isValidSignatureUnsafe( + bytes32, + bytes memory + ) public pure virtual override returns (bytes4) { + return 0xffffffff; // do not support it here + } + + /***************************** Common ***********************************/ + + function _verifySessionEnableDataSignature( + bytes calldata _sessionEnableData, + bytes calldata _sessionEnableSignature, + address _smartAccount + ) internal view { + // Verify the signature on the session enable data + bytes32 sessionEnableDataDigest = keccak256(_sessionEnableData); + if ( + ISignatureValidator(_smartAccount).isValidSignature( + sessionEnableDataDigest, + _sessionEnableSignature + ) != EIP1271_MAGIC_VALUE + ) { + revert("SKM: SessionNotApproved"); + } + } + + function _validateSessionKeySessionEnableTransaction( + uint48 validUntil, + uint48 validAfter, + uint256 sessionKeyIndex, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata sessionEnableData + ) internal { + ( + uint64 sessionChainId, + bytes32 sessionDigest + ) = _parseSessionFromSessionEnableData( + sessionEnableData, + sessionKeyIndex + ); + + if (sessionChainId != block.chainid) { + revert("SKM: SessionChainIdMismatch"); + } + + bytes32 computedDigest = _sessionDataDigestUnpacked( + validUntil, + validAfter, + sessionValidationModule, + sessionKeyData + ); + + if (sessionDigest != computedDigest) { + revert("SKM: SessionKeyDataHashMismatch"); + } + + // Cache the session key data in the smart account storage for next validation + SessionData memory sessionData = SessionData({ + validUntil: validUntil, + validAfter: validAfter, + sessionValidationModule: sessionValidationModule, + sessionKeyData: sessionKeyData + }); + _enabledSessionsData[computedDigest][msg.sender] = sessionData; + emit SessionCreated(msg.sender, computedDigest, sessionData); + } + + function _validateSessionKeyPreEnabled( + address smartAccount, + bytes32 sessionKeyDataDigest + ) internal view returns (SessionData storage sessionData) { + sessionData = _enabledSessionsData[sessionKeyDataDigest][smartAccount]; + require( + sessionData.sessionValidationModule != address(0), + "SKM: Session key is not enabled" + ); + } + + function _isSessionEnableTransaction( + bytes calldata _moduleSignature + ) internal pure returns (bool isSessionEnableTransaction) { + assembly ("memory-safe") { + isSessionEnableTransaction := shr( + 248, + calldataload(_moduleSignature.offset) + ) + } + } + + /// @inheritdoc ISessionKeyManagerModuleHybrid + function sessionDataDigest( + SessionData calldata _data + ) public pure override returns (bytes32) { + return + keccak256( + abi.encodePacked( + _data.validUntil, + _data.validAfter, + _data.sessionValidationModule, + _data.sessionKeyData + ) + ); + } + + function _sessionDataDigestUnpacked( + uint48 _validUntil, + uint48 _validAfter, + address _sessionValidationModule, + bytes calldata _sessionKeyData + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + _validUntil, + _validAfter, + _sessionValidationModule, + _sessionKeyData + ) + ); + } + + function _isBatchExecuteCall( + UserOperation calldata _userOp + ) internal pure returns (bool isBatchExecuteCall) { + bytes4 selector = bytes4(_userOp.callData[0:4]); + isBatchExecuteCall = + selector == ISmartAccount.executeBatch_y6U.selector || + selector == ISmartAccount.executeBatch.selector; + } + + function _parseSessionFromSessionEnableData( + bytes calldata _sessionEnableData, + uint256 _sessionKeyIndex + ) internal pure returns (uint64 sessionChainId, bytes32 sessionDigest) { + uint8 enabledKeysCount; + + /* + * Session Enable Data Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x1 | No of session keys enabled + * 0x1 | 0x8 x count | Chain IDs + * 0x1 + 0x8 x count | 0x20 x count | Session Data Hash + */ + assembly ("memory-safe") { + let offset := _sessionEnableData.offset + + // Extract the number of session keys enabled + enabledKeysCount := shr(248, calldataload(offset)) + offset := add(offset, 0x1) + + // Extract the session chain ID + sessionChainId := shr( + 192, + calldataload(add(offset, mul(0x8, _sessionKeyIndex))) + ) + offset := add(offset, mul(0x8, enabledKeysCount)) + + // Extract the session data hash + sessionDigest := calldataload( + add(offset, mul(0x20, _sessionKeyIndex)) + ) + } + + if (_sessionKeyIndex >= enabledKeysCount) { + revert("SKM: SessionKeyIndexInvalid"); + } + } +} diff --git a/contracts/smart-account/modules/SessionKeyManagerModule.sol b/contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerModule.sol similarity index 91% rename from contracts/smart-account/modules/SessionKeyManagerModule.sol rename to contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerModule.sol index c3b73686..5f703ca0 100644 --- a/contracts/smart-account/modules/SessionKeyManagerModule.sol +++ b/contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerModule.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {BaseAuthorizationModule} from "./BaseAuthorizationModule.sol"; +import {BaseAuthorizationModule} from "../BaseAuthorizationModule.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {_packValidationData} from "@account-abstraction/contracts/core/Helpers.sol"; import {UserOperation} from "@account-abstraction/contracts/interfaces/UserOperation.sol"; -import {ISessionValidationModule} from "../interfaces/modules/ISessionValidationModule.sol"; -import {ISessionKeyManagerModule} from "../interfaces/modules/ISessionKeyManagerModule.sol"; -import {IAuthorizationModule} from "../interfaces/IAuthorizationModule.sol"; -import {ISignatureValidator} from "../interfaces/ISignatureValidator.sol"; +import {ISessionValidationModule} from "../../interfaces/modules/ISessionValidationModule.sol"; +import {ISessionKeyManagerModule} from "../../interfaces/modules/SessionKeyManagers/ISessionKeyManagerModule.sol"; +import {IAuthorizationModule} from "../../interfaces/IAuthorizationModule.sol"; +import {ISignatureValidator} from "../../interfaces/ISignatureValidator.sol"; /** * @title Session Key Manager module for Biconomy Modular Smart Accounts. diff --git a/contracts/smart-account/test/mocks/MockEthSender.sol b/contracts/smart-account/test/mocks/MockEthSender.sol index 093b4571..bba9d9fe 100644 --- a/contracts/smart-account/test/mocks/MockEthSender.sol +++ b/contracts/smart-account/test/mocks/MockEthSender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.20; +pragma solidity ^0.8.23; import {ISmartAccount} from "../../interfaces/ISmartAccount.sol"; diff --git a/contracts/smart-account/test/mocks/MockSessionValidationModule.sol b/contracts/smart-account/test/mocks/MockSessionValidationModule.sol index f89bed8a..086bd2b5 100644 --- a/contracts/smart-account/test/mocks/MockSessionValidationModule.sol +++ b/contracts/smart-account/test/mocks/MockSessionValidationModule.sol @@ -6,12 +6,20 @@ import {UserOperation} from "@account-abstraction/contracts/interfaces/UserOpera import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract MockSessionValidationModule is ISessionValidationModule { + event ValidateSessionParams( + address destinationContract, + uint256 callValue, + bytes funcCallData, + bytes sessionKeyData, + bytes callSpecificData + ); + function validateSessionUserOp( UserOperation calldata _op, bytes32 _userOpHash, bytes calldata _data, bytes calldata _sig - ) external view override returns (bool) { + ) external pure override returns (bool) { (_op); address sessionKey = address(bytes20(_data[0:20])); return @@ -25,7 +33,15 @@ contract MockSessionValidationModule is ISessionValidationModule { bytes calldata funcCallData, bytes calldata sessionKeyData, bytes calldata callSpecificData - ) external pure override returns (address) { + ) external override returns (address) { + emit ValidateSessionParams( + destinationContract, + callValue, + funcCallData, + sessionKeyData, + callSpecificData + ); + address sessionKey = address(bytes20(sessionKeyData[0:20])); return sessionKey; } diff --git a/foundry.toml b/foundry.toml index d1968344..57b28c93 100644 --- a/foundry.toml +++ b/foundry.toml @@ -21,3 +21,6 @@ auto_detect_solc = true [profile.coverage] src = 'test/foundry' + +[fuzz] +runs = 256 diff --git a/hardhat.config.ts b/hardhat.config.ts index d4eed1c4..28f632cd 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -66,7 +66,7 @@ const config: HardhatUserConfig = { // Forking Config for Deployment Testing chainId: 5000, forking: { - url: process.env.MANTLE_MAINNET_URL, + url: process.env.MANTLE_MAINNET_URL!, }, accounts: [ { @@ -401,6 +401,9 @@ const config: HardhatUserConfig = { }, ], }, + tracer: { + gasCost: true, + }, }; export default config; diff --git a/lcov.info b/lcov.info index 56e6f495..db297e4a 100644 --- a/lcov.info +++ b/lcov.info @@ -4,8 +4,8 @@ FN:36,BaseSmartAccount.nonce FNDA:0,BaseSmartAccount.nonce DA:39,0 FN:53,BaseSmartAccount._payPrefund -FNDA:45,BaseSmartAccount._payPrefund -DA:54,45 +FNDA:119,BaseSmartAccount._payPrefund +DA:54,119 BRDA:54,0,0,- BRDA:54,0,1,- DA:55,0 @@ -19,9 +19,9 @@ end_of_record TN: SF:contracts/smart-account/Proxy.sol FN:20,Proxy. -FNDA:119,Proxy. -DA:21,119 -DA:23,119 +FNDA:372,Proxy. +DA:21,372 +DA:23,372 FNF:1 FNH:1 LF:2 @@ -32,37 +32,37 @@ end_of_record TN: SF:contracts/smart-account/SmartAccount.sol FN:79,SmartAccount.init -FNDA:1,SmartAccount.init -DA:90,1 -DA:91,1 +FNDA:55,SmartAccount.init +DA:90,55 +DA:91,55 BRDA:89,0,0,- -BRDA:89,0,1,1 +BRDA:89,0,1,55 DA:92,0 -DA:93,1 -DA:94,1 +DA:93,55 +DA:94,55 FN:98,SmartAccount.execute -FNDA:45,SmartAccount.execute -DA:103,45 +FNDA:116,SmartAccount.execute +DA:103,116 FN:107,SmartAccount.executeBatch FNDA:0,SmartAccount.executeBatch DA:112,0 FN:116,SmartAccount.validateUserOp -FNDA:45,SmartAccount.validateUserOp -DA:126,45 +FNDA:124,SmartAccount.validateUserOp +DA:126,124 BRDA:126,1,0,- -BRDA:126,1,1,45 +BRDA:126,1,1,124 DA:127,0 -DA:130,45 -DA:134,45 -BRDA:134,2,0,- -BRDA:134,2,1,45 -DA:135,45 +DA:130,124 +DA:134,124 +BRDA:134,2,0,5 +BRDA:134,2,1,119 +DA:135,124 DA:138,0 -DA:141,45 +DA:141,119 FN:145,SmartAccount.enableModule -FNDA:1,SmartAccount.enableModule -DA:148,1 -DA:149,1 +FNDA:35,SmartAccount.enableModule +DA:148,35 +DA:149,35 FN:153,SmartAccount.setupAndEnableModule FNDA:7,SmartAccount.setupAndEnableModule DA:162,7 @@ -92,8 +92,8 @@ DA:203,0 DA:206,0 FN:212,SmartAccount.execute_ncC FNDA:0,SmartAccount.execute_ncC -DA:217,45 -DA:218,45 +DA:217,116 +DA:218,116 FN:222,SmartAccount.executeBatch_y6U FNDA:0,SmartAccount.executeBatch_y6U DA:227,0 @@ -119,55 +119,64 @@ DA:269,0 DA:270,0 FN:274,SmartAccount.entryPoint FNDA:0,SmartAccount.entryPoint -DA:281,98 +DA:281,282 FN:285,SmartAccount.getDeposit FNDA:0,SmartAccount.getDeposit DA:286,0 FN:290,SmartAccount.isValidSignature -FNDA:0,SmartAccount.isValidSignature -DA:294,0 -DA:298,0 +FNDA:9,SmartAccount.isValidSignature +DA:294,9 +DA:298,9 BRDA:298,6,0,- -BRDA:298,6,1,- -DA:299,0 -DA:300,0 +BRDA:298,6,1,9 +DA:299,9 +DA:300,9 DA:305,0 -FN:316,SmartAccount._call -FNDA:45,SmartAccount._call -DA:329,45 -BRDA:329,7,0,21 -FN:341,SmartAccount._requireFromEntryPointOrSelf -FNDA:8,SmartAccount._requireFromEntryPointOrSelf -DA:343,8 -BRDA:342,8,0,- -BRDA:342,8,1,8 -DA:345,0 -FN:355,SmartAccount._requireFromEntryPoint -FNDA:45,SmartAccount._requireFromEntryPoint -DA:356,45 -BRDA:356,9,0,- -BRDA:356,9,1,45 -DA:357,0 -FNF:21 -FNH:8 -LF:56 -LH:20 -BRF:19 -BRH:6 +FN:310,SmartAccount.isValidSignatureUnsafe +FNDA:0,SmartAccount.isValidSignatureUnsafe +DA:314,0 +DA:318,0 +BRDA:318,7,0,- +BRDA:318,7,1,- +DA:319,0 +DA:320,0 +DA:325,0 +FN:336,SmartAccount._call +FNDA:116,SmartAccount._call +DA:349,116 +BRDA:349,8,0,25 +FN:361,SmartAccount._requireFromEntryPointOrSelf +FNDA:42,SmartAccount._requireFromEntryPointOrSelf +DA:363,42 +BRDA:362,9,0,- +BRDA:362,9,1,42 +DA:365,0 +FN:375,SmartAccount._requireFromEntryPoint +FNDA:116,SmartAccount._requireFromEntryPoint +DA:376,116 +BRDA:376,10,0,- +BRDA:376,10,1,116 +DA:377,0 +FNF:22 +FNH:9 +LF:61 +LH:24 +BRF:21 +BRH:8 end_of_record TN: SF:contracts/smart-account/base/Executor.sol FN:18,Executor._execute -FNDA:8,Executor._execute -DA:25,8 +FNDA:10,Executor._execute +DA:25,10 BRDA:25,0,0,- -BRDA:25,0,1,8 +BRDA:25,0,1,10 DA:27,0 -DA:38,8 -DA:49,8 +DA:38,10 +DA:49,10 BRDA:49,1,0,7 -BRDA:49,1,1,1 -DA:50,1 +BRDA:49,1,1,3 +DA:50,3 FNF:1 FNH:1 LF:5 @@ -186,15 +195,15 @@ DA:42,0 BRDA:42,1,0,- FN:53,FallbackManager.getFallbackHandler FNDA:0,FallbackManager.getFallbackHandler -DA:60,1 +DA:60,55 FN:69,FallbackManager._setFallbackHandler -FNDA:1,FallbackManager._setFallbackHandler -DA:70,1 +FNDA:55,FallbackManager._setFallbackHandler +DA:70,55 BRDA:70,2,0,- -BRDA:70,2,1,1 -DA:71,1 -DA:74,1 -DA:77,1 +BRDA:70,2,1,55 +DA:71,55 +DA:74,55 +DA:77,55 FNF:3 FNH:1 LF:8 @@ -218,23 +227,23 @@ DA:44,0 DA:46,0 FN:55,ModuleManager.execTransactionFromModule FNDA:0,ModuleManager.execTransactionFromModule -DA:64,8 +DA:64,10 BRDA:63,0,0,- -BRDA:63,0,1,8 +BRDA:63,0,1,10 DA:65,0 -DA:68,8 -DA:69,8 +DA:68,10 +DA:69,10 FN:79,ModuleManager.execTransactionFromModule -FNDA:1,ModuleManager.execTransactionFromModule -DA:85,1 +FNDA:2,ModuleManager.execTransactionFromModule +DA:85,2 FN:89,ModuleManager.execTransactionFromModuleReturnData FNDA:0,ModuleManager.execTransactionFromModuleReturnData -DA:96,7 -DA:109,7 +DA:96,8 +DA:109,8 FN:114,ModuleManager.execTransactionFromModuleReturnData -FNDA:7,ModuleManager.execTransactionFromModuleReturnData -DA:120,7 -DA:121,7 +FNDA:8,ModuleManager.execTransactionFromModuleReturnData +DA:120,8 +DA:121,8 FN:125,ModuleManager.execBatchTransactionFromModule FNDA:0,ModuleManager.execBatchTransactionFromModule DA:132,0 @@ -252,71 +261,76 @@ DA:150,0 DA:152,0 DA:160,0 FN:166,ModuleManager.isModuleEnabled -FNDA:11,ModuleManager.isModuleEnabled -DA:169,11 +FNDA:15,ModuleManager.isModuleEnabled +DA:169,15 FN:178,ModuleManager._enableModule -FNDA:8,ModuleManager._enableModule -DA:180,8 +FNDA:42,ModuleManager._enableModule +DA:180,42 BRDA:180,3,0,1 -BRDA:180,3,1,7 +BRDA:180,3,1,41 DA:181,1 -DA:184,7 +DA:184,41 BRDA:184,4,0,- -BRDA:184,4,1,7 -DA:186,7 -DA:187,7 -DA:189,7 +BRDA:184,4,1,41 +DA:186,41 +DA:187,41 +DA:189,41 FN:196,ModuleManager._setupAndEnableModule FNDA:7,ModuleManager._setupAndEnableModule DA:200,7 DA:201,7 DA:202,6 -FN:212,ModuleManager._disableModule +FN:217,ModuleManager._disableModule FNDA:0,ModuleManager._disableModule -DA:217,0 -BRDA:217,5,0,- -BRDA:217,5,1,- -DA:218,0 -DA:220,0 -BRDA:220,6,0,- -BRDA:220,6,1,- -DA:221,0 +DA:222,0 +BRDA:222,5,0,- +BRDA:222,5,1,- +DA:223,0 +DA:226,0 DA:227,0 +BRDA:225,6,0,- +BRDA:225,6,1,- DA:228,0 DA:229,0 -FN:240,ModuleManager._executeFromModule -FNDA:8,ModuleManager._executeFromModule -DA:247,8 -DA:248,8 -BRDA:248,7,0,7 -BRDA:248,7,1,1 -DA:249,7 -DA:250,7 -DA:252,1 -FN:261,ModuleManager._initialSetupModules -FNDA:1,ModuleManager._initialSetupModules -DA:265,1 -DA:272,1 -DA:273,1 -BRDA:271,8,0,- -BRDA:271,8,1,1 -DA:275,0 -DA:278,1 -DA:279,1 -DA:280,1 -FN:291,ModuleManager._setupModule -FNDA:8,ModuleManager._setupModule -DA:295,8 -BRDA:295,9,0,- -BRDA:295,9,1,8 -DA:308,8 -BRDA:308,10,0,- -DA:311,8 +BRDA:229,7,0,- +BRDA:229,7,1,- +DA:230,0 +DA:236,0 +DA:237,0 +DA:238,0 +FN:249,ModuleManager._executeFromModule +FNDA:10,ModuleManager._executeFromModule +DA:256,10 +DA:257,10 +BRDA:257,8,0,7 +BRDA:257,8,1,3 +DA:258,7 +DA:259,7 +DA:261,3 +FN:270,ModuleManager._initialSetupModules +FNDA:55,ModuleManager._initialSetupModules +DA:274,55 +DA:281,55 +DA:282,55 +BRDA:280,9,0,- +BRDA:280,9,1,55 +DA:284,0 +DA:287,55 +DA:288,55 +DA:289,55 +FN:300,ModuleManager._setupModule +FNDA:62,ModuleManager._setupModule +DA:304,62 +BRDA:304,10,0,- +BRDA:304,10,1,62 +DA:317,62 +BRDA:317,11,0,- +DA:320,62 FNF:13 FNH:8 -LF:61 +LF:64 LH:32 -BRF:21 +BRF:23 BRH:8 end_of_record TN: @@ -370,51 +384,45 @@ DA:47,0 DA:50,0 DA:53,0 FN:57,SmartAccountFactory.deployCounterFactualAccount -FNDA:1,SmartAccountFactory.deployCounterFactualAccount -DA:63,1 -DA:67,1 -DA:71,1 -DA:77,1 -DA:84,1 +FNDA:55,SmartAccountFactory.deployCounterFactualAccount +DA:63,55 +DA:67,55 +DA:71,55 +DA:77,55 +DA:84,55 BRDA:84,0,0,- -BRDA:84,0,1,1 -DA:86,1 -DA:88,1 -BRDA:88,1,0,- -BRDA:88,1,1,1 -DA:101,1 -BRDA:101,2,0,- -DA:104,1 -DA:107,1 -FN:111,SmartAccountFactory.deployAccount +BRDA:84,0,1,55 +DA:86,55 +DA:100,55 +BRDA:100,1,0,- +DA:103,55 +DA:105,55 +FN:109,SmartAccountFactory.deployAccount FNDA:0,SmartAccountFactory.deployAccount -DA:115,0 -DA:121,0 +DA:113,0 +DA:119,0 +DA:125,0 +BRDA:125,2,0,- +BRDA:125,2,1,- DA:127,0 -BRDA:127,3,0,- -BRDA:127,3,1,- -DA:129,0 -DA:133,0 -DA:135,0 -BRDA:135,4,0,- -BRDA:135,4,1,- +DA:131,0 +DA:145,0 +BRDA:145,3,0,- DA:148,0 -BRDA:148,5,0,- -DA:151,0 -DA:154,0 -FN:158,SmartAccountFactory.accountCreationCode +DA:150,0 +FN:154,SmartAccountFactory.accountCreationCode FNDA:0,SmartAccountFactory.accountCreationCode -DA:159,0 -FN:168,SmartAccountFactory._getInitializer -FNDA:1,SmartAccountFactory._getInitializer -DA:172,1 -DA:173,1 +DA:155,0 +FN:164,SmartAccountFactory._getInitializer +FNDA:55,SmartAccountFactory._getInitializer +DA:168,55 +DA:169,55 FNF:5 FNH:2 -LF:27 -LH:12 -BRF:10 -BRH:2 +LF:25 +LH:11 +BRF:6 +BRH:1 end_of_record TN: SF:contracts/smart-account/handler/DefaultCallbackHandler.sol @@ -463,16 +471,16 @@ end_of_record TN: SF:contracts/smart-account/modules/EcdsaOwnershipRegistryModule.sol FN:38,EcdsaOwnershipRegistryModule.initForSmartAccount -FNDA:7,EcdsaOwnershipRegistryModule.initForSmartAccount -DA:41,7 +FNDA:61,EcdsaOwnershipRegistryModule.initForSmartAccount +DA:41,61 BRDA:41,0,0,- -BRDA:41,0,1,7 +BRDA:41,0,1,61 DA:42,0 -DA:44,7 +DA:44,61 BRDA:44,1,0,- -BRDA:44,1,1,7 -DA:45,7 -DA:46,7 +BRDA:44,1,1,61 +DA:45,61 +DA:46,61 FN:50,EcdsaOwnershipRegistryModule.transferOwnership FNDA:0,EcdsaOwnershipRegistryModule.transferOwnership DA:51,0 @@ -486,69 +494,80 @@ FN:57,EcdsaOwnershipRegistryModule.renounceOwnership FNDA:0,EcdsaOwnershipRegistryModule.renounceOwnership DA:58,0 FN:62,EcdsaOwnershipRegistryModule.getOwner -FNDA:1,EcdsaOwnershipRegistryModule.getOwner -DA:65,1 -DA:66,1 +FNDA:10,EcdsaOwnershipRegistryModule.getOwner +DA:65,10 +DA:66,10 BRDA:66,4,0,- -BRDA:66,4,1,1 +BRDA:66,4,1,10 DA:67,0 -DA:69,1 +DA:69,10 FN:73,EcdsaOwnershipRegistryModule.validateUserOp -FNDA:45,EcdsaOwnershipRegistryModule.validateUserOp -DA:77,45 -DA:81,45 -BRDA:81,5,0,45 +FNDA:109,EcdsaOwnershipRegistryModule.validateUserOp +DA:77,109 +DA:81,109 +BRDA:81,5,0,109 BRDA:81,5,1,- -DA:82,45 +DA:82,109 DA:84,0 -FN:95,EcdsaOwnershipRegistryModule.isValidSignature -FNDA:0,EcdsaOwnershipRegistryModule.isValidSignature -DA:99,0 -DA:100,0 -FN:104,EcdsaOwnershipRegistryModule.isValidSignatureForAddress +FN:96,EcdsaOwnershipRegistryModule.isValidSignature +FNDA:9,EcdsaOwnershipRegistryModule.isValidSignature +DA:100,9 +DA:101,9 +FN:105,EcdsaOwnershipRegistryModule.isValidSignatureForAddress FNDA:0,EcdsaOwnershipRegistryModule.isValidSignatureForAddress -DA:109,0 -BRDA:109,6,0,- -BRDA:109,6,1,- -DA:110,0 -DA:112,0 -FN:119,EcdsaOwnershipRegistryModule._transferOwnership -FNDA:0,EcdsaOwnershipRegistryModule._transferOwnership -DA:123,0 -DA:124,0 +DA:111,9 +BRDA:110,6,0,9 +BRDA:110,6,1,- +DA:123,9 DA:125,0 -FN:139,EcdsaOwnershipRegistryModule._verifySignature -FNDA:45,EcdsaOwnershipRegistryModule._verifySignature -DA:144,45 -DA:145,45 -BRDA:145,7,0,- -BRDA:145,7,1,45 -DA:146,0 -DA:148,45 -BRDA:148,8,0,- -BRDA:148,8,1,45 -DA:149,45 -DA:152,45 -BRDA:152,9,0,- -BRDA:152,9,1,45 -DA:153,0 -DA:155,45 -DA:156,45 -BRDA:156,10,0,45 -BRDA:156,10,1,- -DA:157,45 -DA:159,0 -FN:166,EcdsaOwnershipRegistryModule._isSmartContract +FN:129,EcdsaOwnershipRegistryModule.isValidSignatureUnsafe +FNDA:0,EcdsaOwnershipRegistryModule.isValidSignatureUnsafe +DA:133,0 +DA:134,0 +FN:142,EcdsaOwnershipRegistryModule.isValidSignatureForAddressUnsafe +FNDA:0,EcdsaOwnershipRegistryModule.isValidSignatureForAddressUnsafe +DA:147,0 +BRDA:147,7,0,- +BRDA:147,7,1,- +DA:148,0 +DA:150,0 +FN:157,EcdsaOwnershipRegistryModule._transferOwnership +FNDA:0,EcdsaOwnershipRegistryModule._transferOwnership +DA:161,0 +DA:162,0 +DA:163,0 +FN:177,EcdsaOwnershipRegistryModule._verifySignature +FNDA:118,EcdsaOwnershipRegistryModule._verifySignature +DA:182,118 +DA:183,118 +BRDA:183,8,0,- +BRDA:183,8,1,118 +DA:184,0 +DA:186,118 +BRDA:186,9,0,- +BRDA:186,9,1,118 +DA:187,118 +DA:190,118 +BRDA:190,10,0,- +BRDA:190,10,1,118 +DA:191,0 +DA:193,118 +DA:194,118 +BRDA:194,11,0,118 +BRDA:194,11,1,- +DA:195,118 +DA:197,0 +FN:204,EcdsaOwnershipRegistryModule._isSmartContract FNDA:0,EcdsaOwnershipRegistryModule._isSmartContract -DA:167,0 -DA:169,0 -DA:171,0 -FNF:10 -FNH:4 -LF:39 -LH:18 -BRF:22 -BRH:8 +DA:205,0 +DA:207,0 +DA:209,0 +FNF:12 +FNH:5 +LF:44 +LH:22 +BRF:24 +BRH:9 end_of_record TN: SF:contracts/smart-account/modules/MultichainECDSAValidator.sol @@ -580,22 +599,22 @@ BRH:0 end_of_record TN: SF:contracts/smart-account/modules/SecurityPolicies/ERC7484SecurityPolicy.sol -FN:21,ERC7484SecurityPolicyPlugin.setConfiguration -FNDA:2,ERC7484SecurityPolicyPlugin.setConfiguration -DA:24,2 -DA:25,2 -FN:29,ERC7484SecurityPolicyPlugin.validateSecurityPolicy +FN:23,ERC7484SecurityPolicyPlugin.setConfiguration +FNDA:7,ERC7484SecurityPolicyPlugin.setConfiguration +DA:26,7 +DA:27,7 +FN:31,ERC7484SecurityPolicyPlugin.validateSecurityPolicy FNDA:4,ERC7484SecurityPolicyPlugin.validateSecurityPolicy -DA:33,4 -DA:36,4 -DA:37,3 -BRDA:35,0,0,1 -BRDA:35,0,1,3 -DA:39,1 -DA:42,3 -FN:50,ERC7484SecurityPolicyPlugin.configuration +DA:35,4 +DA:38,4 +DA:39,3 +BRDA:37,0,0,1 +BRDA:37,0,1,3 +DA:41,1 +DA:44,3 +FN:52,ERC7484SecurityPolicyPlugin.configuration FNDA:9,ERC7484SecurityPolicyPlugin.configuration -DA:53,9 +DA:55,9 FNF:3 FNH:3 LF:8 @@ -605,168 +624,311 @@ BRH:2 end_of_record TN: SF:contracts/smart-account/modules/SecurityPolicyManagerPlugin.sol -FN:22,SecurityPolicyManagerPlugin.checkSetupAndEnableModule -FNDA:7,SecurityPolicyManagerPlugin.checkSetupAndEnableModule -DA:28,7 -DA:29,7 -DA:55,7 -DA:59,7 -DA:63,7 -DA:69,7 -DA:73,7 -DA:77,7 -DA:90,7 -DA:107,7 -BRDA:107,0,0,1 -BRDA:107,0,1,6 -DA:109,1 -DA:113,6 -BRDA:113,1,0,- -BRDA:113,1,1,6 -DA:114,0 -DA:118,6 -DA:120,2 -FN:124,SecurityPolicyManagerPlugin.checkAndEnableModule -FNDA:3,SecurityPolicyManagerPlugin.checkAndEnableModule -DA:128,3 -BRDA:128,2,0,1 -BRDA:128,2,1,2 -DA:129,1 -DA:133,2 -DA:136,1 -DA:137,1 -DA:143,1 -BRDA:143,3,0,- -BRDA:143,3,1,1 -DA:144,0 -DA:147,1 -FN:153,SecurityPolicyManagerPlugin.enableSecurityPolicy -FNDA:6,SecurityPolicyManagerPlugin.enableSecurityPolicy -DA:157,6 -DA:158,5 -BRDA:156,4,0,2 -BRDA:156,4,1,4 -DA:160,2 -DA:163,4 -DA:168,4 -BRDA:168,5,0,1 -BRDA:168,5,1,3 -DA:169,1 -DA:172,3 -DA:173,3 -DA:176,3 -DA:178,3 -FN:182,SecurityPolicyManagerPlugin.enableSecurityPolicies -FNDA:17,SecurityPolicyManagerPlugin.enableSecurityPolicies -DA:185,17 -DA:190,17 -DA:192,17 -BRDA:192,6,0,1 -BRDA:192,6,1,16 -DA:193,1 -DA:196,16 -DA:198,16 -DA:199,50 -DA:201,50 -BRDA:201,7,0,2 -BRDA:201,7,1,48 -DA:202,2 -DA:205,48 -BRDA:205,8,0,1 -BRDA:205,8,1,47 -DA:206,1 -DA:209,47 -DA:212,47 -DA:214,47 -DA:217,47 -DA:221,13 -FN:225,SecurityPolicyManagerPlugin.disableSecurityPolicy +FN:23,SecurityPolicyManagerPlugin.checkSetupAndEnableModule +FNDA:9,SecurityPolicyManagerPlugin.checkSetupAndEnableModule +DA:29,9 +DA:30,9 +DA:55,9 +DA:58,9 +DA:61,9 +DA:66,9 +DA:69,9 +DA:72,9 +DA:83,9 +DA:100,8 +BRDA:100,0,0,2 +BRDA:100,0,1,6 +DA:101,2 +DA:105,6 +BRDA:105,1,0,- +BRDA:105,1,1,6 +DA:106,0 +DA:110,6 +DA:112,2 +FN:116,SecurityPolicyManagerPlugin.checkAndEnableModule +FNDA:5,SecurityPolicyManagerPlugin.checkAndEnableModule +DA:120,5 +BRDA:120,2,0,1 +BRDA:120,2,1,4 +DA:121,1 +DA:125,4 +DA:140,3 +DA:141,3 +DA:148,3 +DA:151,3 +DA:154,3 +DA:157,3 +DA:160,3 +DA:165,3 +DA:168,3 +DA:171,3 +DA:183,3 +DA:198,2 +BRDA:198,3,0,1 +BRDA:198,3,1,1 +DA:199,1 +DA:202,1 +FN:208,SecurityPolicyManagerPlugin.enableSecurityPolicy +FNDA:11,SecurityPolicyManagerPlugin.enableSecurityPolicy +DA:212,11 +DA:213,10 +BRDA:211,4,0,2 +BRDA:211,4,1,9 +DA:215,2 +DA:218,9 +DA:223,9 +BRDA:223,5,0,1 +BRDA:223,5,1,8 +DA:224,1 +DA:227,8 +DA:228,8 +DA:231,8 +DA:233,8 +FN:237,SecurityPolicyManagerPlugin.enableSecurityPolicies +FNDA:27,SecurityPolicyManagerPlugin.enableSecurityPolicies +DA:240,27 +DA:245,27 +DA:247,27 +BRDA:247,6,0,1 +BRDA:247,6,1,26 +DA:248,1 +DA:251,26 +DA:253,26 +DA:254,90 +DA:256,90 +BRDA:256,7,0,2 +BRDA:256,7,1,88 +DA:257,2 +DA:260,88 +BRDA:260,8,0,1 +BRDA:260,8,1,87 +DA:261,1 +DA:264,87 +DA:267,87 +DA:269,87 +DA:272,87 +DA:276,23 +FN:280,SecurityPolicyManagerPlugin.disableSecurityPolicy FNDA:6,SecurityPolicyManagerPlugin.disableSecurityPolicy -DA:230,6 -DA:231,5 -BRDA:229,9,0,2 -BRDA:229,9,1,4 -DA:233,2 -DA:236,4 -DA:241,4 -BRDA:241,10,0,2 -BRDA:241,10,1,2 -DA:242,2 -DA:245,2 -BRDA:245,11,0,1 -BRDA:245,11,1,1 -DA:246,1 -DA:249,1 -DA:252,1 -DA:254,1 -FN:259,SecurityPolicyManagerPlugin.disableSecurityPoliciesRange +DA:285,6 +DA:286,5 +BRDA:284,9,0,2 +BRDA:284,9,1,4 +DA:288,2 +DA:291,4 +DA:296,4 +BRDA:296,10,0,2 +BRDA:296,10,1,2 +DA:297,2 +DA:300,2 +BRDA:300,11,0,1 +BRDA:300,11,1,1 +DA:301,1 +DA:304,1 +DA:307,1 +DA:309,1 +FN:314,SecurityPolicyManagerPlugin.disableSecurityPoliciesRange FNDA:6,SecurityPolicyManagerPlugin.disableSecurityPoliciesRange -DA:264,6 -DA:269,6 -BRDA:269,12,0,1 -BRDA:269,12,1,5 -DA:270,1 -DA:274,5 -DA:275,5 -BRDA:273,13,0,- -BRDA:273,13,1,5 -DA:277,0 -DA:281,5 -DA:282,5 -BRDA:280,14,0,- -BRDA:280,14,1,5 -DA:284,0 -DA:287,5 -DA:291,5 -DA:292,5 -DA:294,14 -DA:295,14 -DA:297,14 -DA:299,14 -BRDA:299,15,0,4 -BRDA:299,15,1,10 -DA:300,4 -DA:301,4 -DA:304,10 -BRDA:304,16,0,1 -BRDA:304,16,1,9 -DA:305,1 -DA:308,9 -DA:311,5 -BRDA:311,17,0,1 -BRDA:311,17,1,4 -DA:312,1 -FN:317,SecurityPolicyManagerPlugin.securityPoliciesPaginated +DA:319,6 +DA:324,6 +BRDA:324,12,0,1 +BRDA:324,12,1,5 +DA:325,1 +DA:329,5 +DA:330,5 +BRDA:328,13,0,- +BRDA:328,13,1,5 +DA:332,0 +DA:336,5 +DA:337,5 +BRDA:335,14,0,- +BRDA:335,14,1,5 +DA:339,0 +DA:342,5 +DA:346,5 +DA:347,5 +DA:349,14 +DA:350,14 +DA:352,14 +DA:354,14 +BRDA:354,15,0,4 +BRDA:354,15,1,10 +DA:355,4 +DA:356,4 +DA:359,10 +BRDA:359,16,0,1 +BRDA:359,16,1,9 +DA:360,1 +DA:363,9 +DA:366,5 +BRDA:366,17,0,1 +BRDA:366,17,1,4 +DA:367,1 +FN:372,SecurityPolicyManagerPlugin.securityPoliciesPaginated FNDA:29,SecurityPolicyManagerPlugin.securityPoliciesPaginated -DA:327,29 -DA:328,29 -DA:330,29 -DA:335,29 -BRDA:335,18,0,23 -BRDA:335,18,1,29 -DA:336,23 -DA:339,29 -DA:351,79 -DA:352,71 -DA:353,55 -BRDA:341,19,0,- -BRDA:341,19,1,56 -FN:363,SecurityPolicyManagerPlugin._validateSecurityPolicies -FNDA:8,SecurityPolicyManagerPlugin._validateSecurityPolicies -DA:364,8 -DA:369,8 -DA:373,8 -DA:378,17 -DA:390,14 -BRDA:390,20,0,5 -DA:397,9 -DA:400,3 +DA:382,29 +DA:383,29 +DA:385,29 +DA:390,29 +BRDA:390,18,0,23 +BRDA:390,18,1,29 +DA:391,23 +DA:394,29 +DA:406,79 +DA:407,71 +DA:408,55 +BRDA:396,19,0,- +BRDA:396,19,1,56 +FN:418,SecurityPolicyManagerPlugin._validateSecurityPolicies +FNDA:10,SecurityPolicyManagerPlugin._validateSecurityPolicies +DA:419,10 +DA:424,10 +DA:428,10 +DA:433,27 +DA:445,22 +BRDA:445,20,0,5 +DA:452,17 +DA:455,5 FNF:8 FNH:8 -LF:99 -LH:95 +LF:108 +LH:105 BRF:41 -BRH:36 +BRH:37 +end_of_record +TN: +SF:contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerHybrid.sol +FN:26,SessionKeyManagerHybrid.validateUserOp +FNDA:12,SessionKeyManagerHybrid.validateUserOp +DA:32,12 +DA:37,12 +DA:39,12 +DA:42,12 +BRDA:42,0,0,- +BRDA:42,0,1,6 +DA:43,9 +DA:53,9 +DA:68,9 +DA:79,6 +DA:92,3 +DA:96,3 +DA:98,3 +DA:100,2 +DA:104,2 +FN:120,SessionKeyManagerHybrid.validateSessionKeySessionEnableTransaction +FNDA:0,SessionKeyManagerHybrid.validateSessionKeySessionEnableTransaction +DA:131,9 +DA:133,9 +BRDA:132,1,0,- +BRDA:132,1,1,9 +DA:138,0 +DA:148,9 +DA:149,9 +DA:150,9 +DA:155,9 +DA:157,9 +DA:162,9 +DA:170,9 +BRDA:170,2,0,1 +BRDA:170,2,1,8 +DA:171,1 +DA:174,8 +BRDA:174,3,0,1 +BRDA:174,3,1,7 +DA:175,1 +DA:178,7 +DA:185,7 +BRDA:185,4,0,1 +BRDA:185,4,1,6 +DA:186,1 +DA:190,6 +DA:196,6 +DA:197,6 +FN:201,SessionKeyManagerHybrid.validateSessionKeyPreEnabled +FNDA:0,SessionKeyManagerHybrid.validateSessionKeyPreEnabled +DA:205,3 +BRDA:205,5,0,1 +BRDA:205,5,1,2 +FNF:3 +FNH:1 +LF:33 +LH:32 +BRF:12 +BRH:10 +end_of_record +TN: +SF:contracts/smart-account/modules/SessionKeyManagers/SessionKeyManagerStateful.sol +FN:23,SessionKeyManagerStateful.validateUserOp +FNDA:3,SessionKeyManagerStateful.validateUserOp +DA:28,3 +DA:33,3 +DA:36,3 +DA:38,2 +DA:42,2 +FN:57,SessionKeyManagerStateful.enableSession +FNDA:4,SessionKeyManagerStateful.enableSession +DA:58,4 +DA:59,4 +DA:60,4 +FN:64,SessionKeyManagerStateful.validateSessionKey +FNDA:0,SessionKeyManagerStateful.validateSessionKey +DA:68,3 +BRDA:68,0,0,1 +BRDA:68,0,1,2 +FNF:3 +FNH:2 +LF:9 +LH:9 +BRF:2 +BRH:2 +end_of_record +TN: +SF:contracts/smart-account/modules/SessionKeyManagers/StatefulSessionKeyManagerBase.sol +FN:32,StatefulSessionKeyManagerBase.disableSession +FNDA:2,StatefulSessionKeyManagerBase.disableSession +DA:33,2 +DA:34,2 +FN:38,StatefulSessionKeyManagerBase.enabledSessionsData +FNDA:9,StatefulSessionKeyManagerBase.enabledSessionsData +DA:42,9 +FN:46,StatefulSessionKeyManagerBase.sessionDataDigest +FNDA:32,StatefulSessionKeyManagerBase.sessionDataDigest +DA:49,36 +DA:50,36 +FN:61,StatefulSessionKeyManagerBase.isValidSignature +FNDA:512,StatefulSessionKeyManagerBase.isValidSignature +DA:65,512 +FN:69,StatefulSessionKeyManagerBase.isValidSignatureUnsafe +FNDA:512,StatefulSessionKeyManagerBase.isValidSignatureUnsafe +DA:73,512 +FN:76,StatefulSessionKeyManagerBase._sessionDataDigestUnpacked +FNDA:7,StatefulSessionKeyManagerBase._sessionDataDigestUnpacked +DA:82,7 +DA:83,7 +FNF:6 +FNH:6 +LF:9 +LH:9 +BRF:0 +BRH:0 +end_of_record +TN: +SF:contracts/smart-account/test/mocks/MockSessionValidationModule.sol +FN:9,MockSessionValidationModule.validateSessionUserOp +FNDA:10,MockSessionValidationModule.validateSessionUserOp +DA:16,10 +DA:17,10 +DA:18,10 +FN:22,MockSessionValidationModule.validateSessionParams +FNDA:0,MockSessionValidationModule.validateSessionParams +DA:29,0 +DA:30,0 +FNF:2 +FNH:1 +LF:5 +LH:3 +BRF:0 +BRH:0 end_of_record TN: SF:contracts/smart-account/test/mocks/MockToken.sol @@ -799,127 +961,128 @@ BRH:0 end_of_record TN: SF:test/foundry/base/SATestBase.sol -FN:74,SATestBase.getNextPrivateKey +FN:75,SATestBase.getNextPrivateKey FNDA:0,SATestBase.getNextPrivateKey -DA:75,0 -FN:78,SATestBase.setUp +DA:76,0 +FN:79,SATestBase.setUp FNDA:0,SATestBase.setUp -DA:80,0 DA:81,0 DA:82,0 +DA:83,0 DA:86,0 -DA:90,0 -DA:91,0 +DA:88,0 +DA:92,0 DA:93,0 -DA:94,0 +DA:95,0 DA:96,0 -DA:97,0 -DA:102,0 -DA:103,0 +DA:98,0 +DA:99,0 +DA:104,0 DA:105,0 -DA:106,0 +DA:107,0 DA:108,0 -DA:109,0 +DA:110,0 DA:111,0 -DA:112,0 +DA:113,0 DA:114,0 -DA:115,0 +DA:116,0 DA:117,0 -DA:118,0 -DA:121,0 -DA:122,0 -DA:125,0 -DA:126,0 -DA:129,0 -DA:130,0 +DA:119,0 +DA:120,0 +DA:123,0 +DA:124,0 +DA:127,0 +DA:128,0 +DA:131,0 DA:132,0 -DA:133,0 +DA:134,0 DA:135,0 -DA:139,0 -DA:142,0 -DA:143,0 -FN:150,SATestBase.getSmartAccountWithModule +DA:137,0 +DA:141,0 +DA:144,0 +DA:145,0 +FN:152,SATestBase.getSmartAccountWithModule FNDA:0,SATestBase.getSmartAccountWithModule -DA:156,0 -DA:165,0 -FN:168,SATestBase.getSmartAccountExecuteCalldata +DA:158,0 +DA:167,0 +FN:170,SATestBase.getSmartAccountExecuteCalldata FNDA:0,SATestBase.getSmartAccountExecuteCalldata -DA:173,0 -FN:176,SATestBase.getUserOperationEventData +DA:175,0 +FN:178,SATestBase.getUserOperationEventData FNDA:0,SATestBase.getUserOperationEventData -DA:179,0 -DA:180,0 -BRDA:180,0,0,- -BRDA:180,0,1,- DA:181,0 +DA:182,0 +BRDA:182,0,0,- +BRDA:182,0,1,- DA:183,0 -DA:184,0 DA:185,0 DA:186,0 -DA:192,0 +DA:187,0 +DA:188,0 DA:194,0 -FN:197,SATestBase.getUserOperationRevertReasonEventData +DA:196,0 +FN:199,SATestBase.getUserOperationRevertReasonEventData FNDA:0,SATestBase.getUserOperationRevertReasonEventData -DA:200,0 -DA:201,0 -BRDA:201,1,0,- -BRDA:201,1,1,- DA:202,0 +DA:203,0 +BRDA:203,1,0,- +BRDA:203,1,1,- DA:204,0 -DA:205,0 DA:206,0 -DA:210,0 +DA:207,0 +DA:208,0 DA:212,0 -FN:215,SATestBase.arraifyOps -FNDA:0,SATestBase.arraifyOps -DA:218,0 -DA:219,0 +DA:214,0 +FN:217,SATestBase.toArray +FNDA:0,SATestBase.toArray DA:220,0 -FN:223,SATestBase.arraifyOps -FNDA:0,SATestBase.arraifyOps -DA:227,0 -DA:228,0 +DA:221,0 +DA:222,0 +FN:225,SATestBase.toArray +FNDA:0,SATestBase.toArray DA:229,0 DA:230,0 -FN:233,SATestBase.arraifyOps -FNDA:0,SATestBase.arraifyOps -DA:238,0 -DA:239,0 +DA:231,0 +DA:232,0 +FN:235,SATestBase.toArray +FNDA:0,SATestBase.toArray DA:240,0 DA:241,0 DA:242,0 -FN:246,SATestBase.getEcdsaOwnershipRegistryModuleSetupData +DA:243,0 +DA:244,0 +FN:248,SATestBase.getEcdsaOwnershipRegistryModuleSetupData FNDA:0,SATestBase.getEcdsaOwnershipRegistryModuleSetupData -DA:249,0 -DA:250,0 -FN:257,SATestBase.makeEcdsaModuleUserOp +DA:251,0 +DA:252,0 +FN:259,SATestBase.makeEcdsaModuleUserOp FNDA:0,SATestBase.makeEcdsaModuleUserOp -DA:263,0 -DA:278,0 -DA:279,0 -DA:283,0 +DA:265,0 +DA:280,0 +DA:281,0 +DA:285,0 FNF:11 FNH:0 -LF:73 +LF:74 LH:0 BRF:4 BRH:0 end_of_record TN: SF:test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.ModuleInstallation.t.sol -FN:42,TestSetupContractBlacklistReturn.initForSmartAccount -FNDA:0,TestSetupContractBlacklistReturn.initForSmartAccount -DA:43,0 FN:26,TestSecurityPolicyPlugin.validateSecurityPolicy -FNDA:10,TestSecurityPolicyPlugin.validateSecurityPolicy -DA:30,10 -DA:31,10 +FNDA:18,TestSecurityPolicyPlugin.validateSecurityPolicy +DA:30,18 +DA:31,18 BRDA:31,0,0,2 -BRDA:31,0,1,8 +BRDA:31,0,1,16 DA:32,2 FN:36,TestSecurityPolicyPlugin.setShouldRevert FNDA:2,TestSecurityPolicyPlugin.setShouldRevert DA:37,2 +FN:42,TestSetupContractBlacklistReturn.initForSmartAccount +FNDA:0,TestSetupContractBlacklistReturn.initForSmartAccount +DA:43,0 FNF:3 FNH:2 LF:5 @@ -941,3 +1104,27 @@ LH:0 BRF:2 BRH:0 end_of_record +TN: +SF:test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.t.sol +FN:861,Stub.emitMessage +FNDA:7,Stub.emitMessage +DA:862,7 +FNF:1 +FNH:1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/foundry/module/SessionKeyManager/SessionKeyManagerStateful.t.sol +FN:401,Stub.emitMessage +FNDA:0,Stub.emitMessage +DA:402,0 +FNF:1 +FNH:0 +LF:1 +LH:0 +BRF:0 +BRH:0 +end_of_record diff --git a/package.json b/package.json index 2af381f8..be7e95b1 100644 --- a/package.json +++ b/package.json @@ -38,12 +38,17 @@ "license": "MIT", "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "@nomicfoundation/hardhat-foundry": "^1.1.1", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-etherscan": "^3.1.7", "@nomiclabs/hardhat-waffle": "^2.0.1", "@openzeppelin/contracts": "^4.9.3", "@typechain/ethers-v5": "^10.2.0", + "@typechain/ethers-v6": "^0.5.0", "@typechain/hardhat": "^6.1.5", "@types/chai": "^4.3.4", "@types/mocha": "^10.0.1", @@ -84,9 +89,10 @@ "@ethersproject/constants": "^5.6.1", "@types/elliptic": "^6.4.18", "axios": "^1.4.0", + "ethereumjs-util": "^7.1.0", + "hardhat-tracer": "^2.7.0", "bn.js": "^5.2.1", "dotenv": "^16.0.3", - "elliptic": "^6.5.4", - "ethereumjs-util": "^7.1.0" + "elliptic": "^6.5.4" } } diff --git a/test/bundler-integration/environment/docker-compose.yml b/test/bundler-integration/environment/docker-compose.yml index afc154fc..e306f280 100644 --- a/test/bundler-integration/environment/docker-compose.yml +++ b/test/bundler-integration/environment/docker-compose.yml @@ -3,7 +3,7 @@ version: "2" services: bundler: ports: ["3000:3000"] - image: ankurdubeybiconomy/bundler:latest # Image based off accountabstraction/bundler:0.6.1 with fixes for debug_bundler_clearState + image: accountabstraction/bundler:0.6.2 command: --network http://geth-dev:8545 --entryPoint ${ENTRYPOINT} --show-stack-traces volumes: - ./workdir:/app/workdir:ro diff --git a/test/bundler-integration/module/HybridSessionKeyManager.Module.specs.ts b/test/bundler-integration/module/HybridSessionKeyManager.Module.specs.ts new file mode 100644 index 00000000..3761cfaa --- /dev/null +++ b/test/bundler-integration/module/HybridSessionKeyManager.Module.specs.ts @@ -0,0 +1,555 @@ +import { expect } from "chai"; +import { ethers, deployments } from "hardhat"; +import { makeEcdsaModuleUserOp } from "../../utils/userOp"; +import { encodeTransfer } from "../../utils/testUtils"; +import { defaultAbiCoder, hexZeroPad } from "ethers/lib/utils"; +import { + getEntryPoint, + getSmartAccountImplementation, + getSmartAccountFactory, + getMockToken, + getEcdsaOwnershipRegistryModule, + getSmartAccountWithModule, +} from "../../utils/setupHelper"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { BundlerTestEnvironment } from "../environment/bundlerEnvironment"; +import { + SessionKeyManagerHybrid__factory, + ISessionKeyManagerModuleHybrid, +} from "../../../typechain-types"; +import { + HybridSKMBatchCallUtils, + HybridSKMSingleCallUtils, +} from "../../utils/hybridSessionKeyManager"; + +describe("Hybrid Session Key Manager", async () => { + let [deployer, smartAccountOwner, charlie, sessionKey, alice] = + [] as SignerWithAddress[]; + + let environment: BundlerTestEnvironment; + + before(async function () { + const chainId = (await ethers.provider.getNetwork()).chainId; + if (chainId !== BundlerTestEnvironment.BUNDLER_ENVIRONMENT_CHAIN_ID) { + this.skip(); + } + + environment = await BundlerTestEnvironment.getDefaultInstance(); + }); + + beforeEach(async function () { + [deployer, smartAccountOwner, charlie, sessionKey, alice] = + await ethers.getSigners(); + }); + + afterEach(async function () { + const chainId = (await ethers.provider.getNetwork()).chainId; + if (chainId !== BundlerTestEnvironment.BUNDLER_ENVIRONMENT_CHAIN_ID) { + this.skip(); + } + + await Promise.all([ + environment.revert(environment.defaultSnapshot!), + environment.resetBundler(), + ]); + }); + + const setupTests = deployments.createFixture(async ({ deployments }) => { + await deployments.fixture(); + + const entryPoint = await getEntryPoint(); + const mockToken = await getMockToken(); + const ecdsaModule = await getEcdsaOwnershipRegistryModule(); + const EcdsaOwnershipRegistryModule = await ethers.getContractFactory( + "EcdsaOwnershipRegistryModule" + ); + const ecdsaOwnershipSetupData = + EcdsaOwnershipRegistryModule.interface.encodeFunctionData( + "initForSmartAccount", + [await smartAccountOwner.getAddress()] + ); + const smartAccountDeploymentIndex = 0; + const userSA = await getSmartAccountWithModule( + ecdsaModule.address, + ecdsaOwnershipSetupData, + smartAccountDeploymentIndex + ); + + // send funds to userSA and mint tokens + await deployer.sendTransaction({ + to: userSA.address, + value: ethers.utils.parseEther("10"), + }); + await mockToken.mint(userSA.address, ethers.utils.parseEther("1000000")); + + const hybridSessionKeyManager = await new SessionKeyManagerHybrid__factory( + alice + ).deploy(); + const userOp = await makeEcdsaModuleUserOp( + "enableModule", + [hybridSessionKeyManager.address], + userSA.address, + smartAccountOwner, + entryPoint, + ecdsaModule.address + ); + await entryPoint.handleOps([userOp], alice.address); + + const mockSessionValidationModule1 = await ( + await ethers.getContractFactory("MockSessionValidationModule") + ).deploy(); + const mockSessionValidationModule2 = await ( + await ethers.getContractFactory("MockSessionValidationModule") + ).deploy(); + + const validUntil = 0; + const validAfter = 0; + const sessionKeyData = hexZeroPad(sessionKey.address, 20); + const sessionData: ISessionKeyManagerModuleHybrid.SessionDataStruct = { + validUntil, + validAfter, + sessionKeyData, + sessionValidationModule: mockSessionValidationModule1.address, + }; + const sessionData2: ISessionKeyManagerModuleHybrid.SessionDataStruct = { + ...sessionData, + sessionValidationModule: mockSessionValidationModule2.address, + }; + + const hybridSKMSingleCallUtils = new HybridSKMSingleCallUtils( + entryPoint, + hybridSessionKeyManager, + ecdsaModule + ); + const hybridSKMBatchCallUtils = new HybridSKMBatchCallUtils( + entryPoint, + hybridSessionKeyManager, + ecdsaModule + ); + + return { + entryPoint: entryPoint, + smartAccountImplementation: await getSmartAccountImplementation(), + smartAccountFactory: await getSmartAccountFactory(), + mockToken: mockToken, + ecdsaModule: ecdsaModule, + userSA: userSA, + hybridSessionKeyManager, + mockSessionValidationModule: mockSessionValidationModule1, + mockSessionValidationModule2, + sessionKeyData: sessionKeyData, + sessionData, + sessionData2, + hybridSKMSingleCallUtils, + hybridSKMBatchCallUtils, + smartAccountOwner, + }; + }); + + describe("Single Call", async () => { + it("Should process signed user operation from Session when enabling and use is batched", async () => { + const { + entryPoint, + userSA, + hybridSKMSingleCallUtils: utils, + mockToken, + sessionData, + smartAccountOwner, + } = await setupTests(); + const tokenAmountToTransfer = ethers.utils.parseEther("0.834"); + + const chainId = (await ethers.provider.getNetwork()).chainId; + + const { sessionEnableData, sessionEnableSignature } = + await utils.makeSessionEnableData( + [chainId], + [sessionData], + userSA.address, + smartAccountOwner + ); + + const transferUserOp = + await utils.makeEcdsaSessionKeySignedUserOpForEnableAndUseSession( + userSA.address, + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + sessionKey, + sessionData, + sessionEnableData, + sessionEnableSignature, + 0, + { + preVerificationGas: 51000, + } + ); + + const charlieTokenBalanceBefore = await mockToken.balanceOf( + charlie.address + ); + + await environment.sendUserOperation(transferUserOp, entryPoint.address); + + expect(await mockToken.balanceOf(charlie.address)).to.equal( + charlieTokenBalanceBefore.add(tokenAmountToTransfer) + ); + }); + + it("Should process signed user operation from Session when session is pre-enabled", async () => { + const { + entryPoint, + userSA, + hybridSKMSingleCallUtils: utils, + mockToken, + sessionData, + smartAccountOwner, + } = await setupTests(); + const tokenAmountToTransfer = ethers.utils.parseEther("0.834"); + + const chainId = (await ethers.provider.getNetwork()).chainId; + + const { sessionEnableData, sessionEnableSignature } = + await utils.makeSessionEnableData( + [chainId], + [sessionData], + userSA.address, + smartAccountOwner + ); + + const transferUserOp = + await utils.makeEcdsaSessionKeySignedUserOpForEnableAndUseSession( + userSA.address, + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + sessionKey, + sessionData, + sessionEnableData, + sessionEnableSignature, + 0, + { + preVerificationGas: 51000, + } + ); + + await environment.sendUserOperation(transferUserOp, entryPoint.address); + + // Use the session again but in pre-enable mode + const transferUserOp2 = + await utils.makeEcdsaSessionKeySignedUserOpForPreEnabledSession( + userSA.address, + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + sessionKey, + sessionData, + { + preVerificationGas: 51000, + } + ); + + const charlieTokenBalanceBefore = await mockToken.balanceOf( + charlie.address + ); + + await environment.sendUserOperation(transferUserOp2, entryPoint.address); + + expect(await mockToken.balanceOf(charlie.address)).to.equal( + charlieTokenBalanceBefore.add(tokenAmountToTransfer) + ); + }); + }); + + describe("Batch Call", async () => { + it("Should process signed user operation from Session for 2 batch items when enabling and use is batched", async () => { + const { + entryPoint, + userSA, + hybridSKMBatchCallUtils: utils, + mockToken, + sessionData, + sessionData2, + smartAccountOwner, + } = await setupTests(); + const tokenAmountToTransfer = ethers.utils.parseEther("0.834"); + + const chainId = (await ethers.provider.getNetwork()).chainId; + + const { sessionEnableData, sessionEnableSignature } = + await utils.makeSessionEnableData( + [chainId, chainId], + [sessionData, sessionData2], + userSA.address, + smartAccountOwner + ); + + const callSpecificData = defaultAbiCoder.encode( + ["string"], + ["hello world"] + ); + + const sessionInfo1 = utils.makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + const sessionInfo2 = utils.makeSessionEnableSessionInfo( + 0, + 1, + sessionData2, + callSpecificData + ); + + const transferUserOp = await utils.makeEcdsaSessionKeySignedUserOp( + userSA.address, + [ + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + ], + sessionKey, + [sessionEnableData], + [sessionEnableSignature], + [sessionInfo1, sessionInfo2], + { + preVerificationGas: 60000, + } + ); + + const charlieTokenBalanceBefore = await mockToken.balanceOf( + charlie.address + ); + + await environment.sendUserOperation(transferUserOp, entryPoint.address); + + expect(await mockToken.balanceOf(charlie.address)).to.equal( + charlieTokenBalanceBefore.add(tokenAmountToTransfer.mul(2)) + ); + }); + + it("Should process signed user operation from Session for 2 batch items when one is fresh and other is pre-enabled", async () => { + const { + entryPoint, + userSA, + hybridSKMBatchCallUtils: utils, + mockToken, + sessionData, + sessionData2, + smartAccountOwner, + ecdsaModule, + hybridSessionKeyManager, + } = await setupTests(); + const tokenAmountToTransfer = ethers.utils.parseEther("0.834"); + + const chainId = (await ethers.provider.getNetwork()).chainId; + + // Pre-enable the 2nd session + const enableSessionOp = await makeEcdsaModuleUserOp( + "execute_ncC", + [ + hybridSessionKeyManager.address, + ethers.utils.parseEther("0"), + hybridSessionKeyManager.interface.encodeFunctionData( + "enableSession", + [sessionData2] + ), + ], + userSA.address, + smartAccountOwner, + entryPoint, + ecdsaModule.address, + { + preVerificationGas: 50000, + } + ); + await environment.sendUserOperation(enableSessionOp, entryPoint.address); + + const { sessionEnableData, sessionEnableSignature } = + await utils.makeSessionEnableData( + [chainId], + [sessionData], + userSA.address, + smartAccountOwner + ); + + const callSpecificData = defaultAbiCoder.encode( + ["string"], + ["hello world"] + ); + + const sessionInfo1 = utils.makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + const sessionInfo2 = await utils.makePreEnabledSessionInfo( + sessionData2, + callSpecificData + ); + + const transferUserOp = await utils.makeEcdsaSessionKeySignedUserOp( + userSA.address, + [ + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + ], + sessionKey, + [sessionEnableData], + [sessionEnableSignature], + [sessionInfo1, sessionInfo2], + { + preVerificationGas: 60000, + } + ); + + const charlieTokenBalanceBefore = await mockToken.balanceOf( + charlie.address + ); + + await environment.sendUserOperation(transferUserOp, entryPoint.address); + + expect(await mockToken.balanceOf(charlie.address)).to.equal( + charlieTokenBalanceBefore.add(tokenAmountToTransfer.mul(2)) + ); + }); + + it("Should process signed user operation from Session for 2 batch items when both are pre-enabled", async () => { + const { + entryPoint, + userSA, + hybridSKMBatchCallUtils: utils, + mockToken, + sessionData, + sessionData2, + smartAccountOwner, + ecdsaModule, + hybridSessionKeyManager, + } = await setupTests(); + const tokenAmountToTransfer = ethers.utils.parseEther("0.834"); + + // Pre-enable both sessions + const enableSessionOp = await makeEcdsaModuleUserOp( + "executeBatch", + [ + [hybridSessionKeyManager.address, hybridSessionKeyManager.address], + [ethers.utils.parseEther("0"), ethers.utils.parseEther("0")], + [ + hybridSessionKeyManager.interface.encodeFunctionData( + "enableSession", + [sessionData] + ), + hybridSessionKeyManager.interface.encodeFunctionData( + "enableSession", + [sessionData2] + ), + ], + ], + userSA.address, + smartAccountOwner, + entryPoint, + ecdsaModule.address, + { + preVerificationGas: 60000, + } + ); + await environment.sendUserOperation(enableSessionOp, entryPoint.address); + + const callSpecificData = defaultAbiCoder.encode( + ["string"], + ["hello world"] + ); + + const sessionInfo1 = await utils.makePreEnabledSessionInfo( + sessionData, + callSpecificData + ); + const sessionInfo2 = await utils.makePreEnabledSessionInfo( + sessionData2, + callSpecificData + ); + + const transferUserOp = await utils.makeEcdsaSessionKeySignedUserOp( + userSA.address, + [ + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + { + to: mockToken.address, + value: ethers.utils.parseEther("0"), + calldata: encodeTransfer( + charlie.address, + tokenAmountToTransfer.toString() + ), + }, + ], + sessionKey, + [], + [], + [sessionInfo1, sessionInfo2], + { + preVerificationGas: 60000, + } + ); + + const charlieTokenBalanceBefore = await mockToken.balanceOf( + charlie.address + ); + + await environment.sendUserOperation(transferUserOp, entryPoint.address); + + expect(await mockToken.balanceOf(charlie.address)).to.equal( + charlieTokenBalanceBefore.add(tokenAmountToTransfer.mul(2)) + ); + }); + }); +}); diff --git a/test/foundry/base/SATestBase.sol b/test/foundry/base/SATestBase.sol index cd208fb5..db5f38e8 100644 --- a/test/foundry/base/SATestBase.sol +++ b/test/foundry/base/SATestBase.sol @@ -8,82 +8,72 @@ import {EntryPoint, IEntryPoint, UserOperation} from "aa-core/EntryPoint.sol"; import {SmartAccountFactory} from "factory/SmartAccountFactory.sol"; import {SmartAccount} from "sa/SmartAccount.sol"; import {EcdsaOwnershipRegistryModule} from "modules/EcdsaOwnershipRegistryModule.sol"; - -abstract contract SATestBase is Test { - // Test Environment Configuration - string constant mnemonic = - "test test test test test test test test test test test junk"; - uint256 constant testAccountCount = 10; - uint256 constant initialMainAccountFunds = 100000 ether; - uint256 constant defaultPreVerificationGas = 21000; - // Event Topics - bytes32 constant userOperationEventTopic = - 0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f; - bytes32 constant userOperationRevertReasonTopic = - 0x1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a201; - - uint32 nextKeyIndex; - - struct UserOperationEventData { - bytes32 userOpHash; - address sender; - address paymaster; - uint256 nonce; - bool success; - uint256 actualGasCost; - uint256 actualGasUsed; - } - - struct UserOperationRevertReasonEventData { - bytes32 userOpHash; - address sender; - uint256 nonce; - bytes revertReason; - } - +import {ToArrayUtils} from "./utils/ToArrayUtils.sol"; +import {AssertUtils} from "./utils/AssertUtils.sol"; +import {EntryPointUtils} from "./utils/EntrypointUtils.sol"; + +/* solhint-disable ordering*/ + +abstract contract SATestBase is + Test, + ToArrayUtils, + AssertUtils, + EntryPointUtils +{ // Test Accounts struct TestAccount { address payable addr; uint256 privateKey; } - TestAccount[] testAccounts; - TestAccount alice; - TestAccount bob; - TestAccount charlie; - TestAccount dan; - TestAccount emma; - TestAccount frank; - TestAccount george; - TestAccount henry; - TestAccount ida; - - TestAccount owner; + // Test Environment Configuration + string internal constant MNEMONIC = + "test test test test test test test test test test test junk"; + uint256 internal constant TEST_ACCOUNT_COUNT = 10; + uint256 internal constant INITIAL_MAIN_ACCOUNT_FUNDS = 100000 ether; + uint256 internal constant DEFAULT_PRE_VERIFICATIION_GAS = 21000; + + uint32 internal nextKeyIndex; + + TestAccount[] internal testAccounts; + mapping(address account => TestAccount) internal testAccountsByAddress; + TestAccount internal alice; + TestAccount internal bob; + TestAccount internal charlie; + TestAccount internal dan; + TestAccount internal emma; + TestAccount internal frank; + TestAccount internal george; + TestAccount internal henry; + TestAccount internal ida; + + TestAccount internal owner; // Test Tokens - MockToken token; + MockToken internal token; // ERC4337 Contracts - EntryPoint entryPoint; - SmartAccount saImplementation; - SmartAccountFactory factory; + EntryPoint internal entryPoint; + SmartAccount internal saImplementation; + SmartAccountFactory internal factory; // Modules - EcdsaOwnershipRegistryModule ecdsaOwnershipRegistryModule; + EcdsaOwnershipRegistryModule internal ecdsaOwnershipRegistryModule; function getNextPrivateKey() internal returns (uint256) { - return vm.deriveKey(mnemonic, ++nextKeyIndex); + return vm.deriveKey(MNEMONIC, ++nextKeyIndex); } function setUp() public virtual { // Generate Test Addresses - for (uint256 i = 0; i < testAccountCount; i++) { + for (uint256 i = 0; i < TEST_ACCOUNT_COUNT; i++) { uint256 privateKey = getNextPrivateKey(); testAccounts.push( TestAccount(payable(vm.addr(privateKey)), privateKey) ); + testAccountsByAddress[testAccounts[i].addr] = testAccounts[i]; - deal(testAccounts[i].addr, initialMainAccountFunds); + deal(testAccounts[i].addr, INITIAL_MAIN_ACCOUNT_FUNDS); } // Name Test Addresses @@ -144,6 +134,9 @@ abstract contract SATestBase is Test { address(ecdsaOwnershipRegistryModule), "ECDSA Ownership Registry Module" ); + + // Ensure non zero timestamp + vm.warp(1703452990); // Sunday, December 24, 2023 9:23:10 PM GMT } // Utility Functions @@ -185,75 +178,6 @@ abstract contract SATestBase is Test { ); } - function getUserOperationEventData( - Vm.Log[] memory _entries - ) internal returns (UserOperationEventData memory data) { - for (uint256 i = 0; i < _entries.length; ++i) { - if (_entries[i].topics[0] != userOperationEventTopic) { - continue; - } - data.userOpHash = _entries[i].topics[1]; - data.sender = address(uint160(uint256(_entries[i].topics[2]))); - data.paymaster = address(uint160(uint256(_entries[i].topics[3]))); - ( - data.nonce, - data.success, - data.actualGasCost, - data.actualGasUsed - ) = abi.decode(_entries[i].data, (uint256, bool, uint256, uint256)); - return data; - } - fail("entries does not contain UserOperationEvent"); - } - - function getUserOperationRevertReasonEventData( - Vm.Log[] memory _entries - ) internal returns (UserOperationRevertReasonEventData memory data) { - for (uint256 i = 0; i < _entries.length; ++i) { - if (_entries[i].topics[0] != userOperationRevertReasonTopic) { - continue; - } - data.userOpHash = _entries[i].topics[1]; - data.sender = address(uint160(uint256(_entries[i].topics[2]))); - (data.nonce, data.revertReason) = abi.decode( - _entries[i].data, - (uint256, bytes) - ); - return data; - } - fail("entries does not contain UserOperationRevertReasonEvent"); - } - - function arraifyOps( - UserOperation memory _op - ) internal pure returns (UserOperation[] memory) { - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = _op; - return ops; - } - - function arraifyOps( - UserOperation memory _op1, - UserOperation memory _op2 - ) internal pure returns (UserOperation[] memory) { - UserOperation[] memory ops = new UserOperation[](2); - ops[0] = _op1; - ops[1] = _op2; - return ops; - } - - function arraifyOps( - UserOperation memory _op1, - UserOperation memory _op2, - UserOperation memory _op3 - ) internal pure returns (UserOperation[] memory) { - UserOperation[] memory ops = new UserOperation[](3); - ops[0] = _op1; - ops[1] = _op2; - ops[2] = _op3; - return ops; - } - // Module Setup Data Helpers function getEcdsaOwnershipRegistryModuleSetupData( address _owner @@ -279,7 +203,7 @@ abstract contract SATestBase is Test { callData: _calldata, callGasLimit: gasleft() / 100, verificationGasLimit: gasleft() / 100, - preVerificationGas: defaultPreVerificationGas, + preVerificationGas: DEFAULT_PRE_VERIFICATIION_GAS, maxFeePerGas: tx.gasprice, maxPriorityFeePerGas: tx.gasprice - block.basefee, paymasterAndData: bytes(""), diff --git a/test/foundry/base/utils/AssertUtils.sol b/test/foundry/base/utils/AssertUtils.sol new file mode 100644 index 00000000..0524d838 --- /dev/null +++ b/test/foundry/base/utils/AssertUtils.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {UserOperation} from "aa-core/EntryPoint.sol"; +import {ISessionKeyManagerModuleHybrid} from "sa/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol"; + +abstract contract AssertUtils is Test { + function assertEq( + ISessionKeyManagerModuleHybrid.SessionData memory _a, + ISessionKeyManagerModuleHybrid.SessionData memory _b + ) internal { + assertEq(_a.validUntil, _b.validUntil, "mismatched validUntil"); + assertEq(_a.validAfter, _b.validAfter, "mismatched validAfter"); + assertEq( + _a.sessionValidationModule, + _b.sessionValidationModule, + "mismatched sessionValidationModule" + ); + assertEq( + _a.sessionKeyData, + _b.sessionKeyData, + "mismatched sessionKeyData" + ); + } +} diff --git a/test/foundry/base/utils/EntrypointUtils.sol b/test/foundry/base/utils/EntrypointUtils.sol new file mode 100644 index 00000000..d1784049 --- /dev/null +++ b/test/foundry/base/utils/EntrypointUtils.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {EntryPoint, IEntryPoint, UserOperation} from "aa-core/EntryPoint.sol"; +import {Vm, Test} from "forge-std/Test.sol"; + +abstract contract EntryPointUtils is Test { + // Event Topics + + // keccak256("UserOperationEvent(bytes32 indexed,address indexed,address indexed,uint256,bool,uint256,uint256))" + bytes32 private constant userOperationEventTopic = + 0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f; + + // keccak256("UserOperationRevertReason(bytes32 indexed,address indexed,uint256,bytes)") + bytes32 private constant userOperationRevertReasonTopic = + 0x1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a201; + + struct UserOperationEventData { + bytes32 userOpHash; + address sender; + address paymaster; + uint256 nonce; + bool success; + uint256 actualGasCost; + uint256 actualGasUsed; + } + + struct UserOperationRevertReasonEventData { + bytes32 userOpHash; + address sender; + uint256 nonce; + bytes revertReason; + } + + function getUserOperationEventData( + Vm.Log[] memory _entries + ) internal returns (UserOperationEventData memory data) { + for (uint256 i = 0; i < _entries.length; ++i) { + if (_entries[i].topics[0] != userOperationEventTopic) { + continue; + } + data.userOpHash = _entries[i].topics[1]; + data.sender = address(uint160(uint256(_entries[i].topics[2]))); + data.paymaster = address(uint160(uint256(_entries[i].topics[3]))); + ( + data.nonce, + data.success, + data.actualGasCost, + data.actualGasUsed + ) = abi.decode(_entries[i].data, (uint256, bool, uint256, uint256)); + return data; + } + fail("entries does not contain UserOperationEvent"); + } + + function getUserOperationRevertReasonEventData( + Vm.Log[] memory _entries + ) internal returns (UserOperationRevertReasonEventData memory data) { + for (uint256 i = 0; i < _entries.length; ++i) { + if (_entries[i].topics[0] != userOperationRevertReasonTopic) { + continue; + } + data.userOpHash = _entries[i].topics[1]; + data.sender = address(uint160(uint256(_entries[i].topics[2]))); + (data.nonce, data.revertReason) = abi.decode( + _entries[i].data, + (uint256, bytes) + ); + return data; + } + fail("entries does not contain UserOperationRevertReasonEvent"); + } +} diff --git a/test/foundry/base/utils/ToArrayUtils.sol b/test/foundry/base/utils/ToArrayUtils.sol new file mode 100644 index 00000000..4e4e2d71 --- /dev/null +++ b/test/foundry/base/utils/ToArrayUtils.sol @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {UserOperation} from "aa-core/EntryPoint.sol"; +import {ISessionKeyManagerModuleHybrid} from "sa/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol"; + +abstract contract ToArrayUtils { + // User Operations + function toArray( + UserOperation memory _op + ) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = _op; + return ops; + } + + function toArray( + UserOperation memory _op1, + UserOperation memory _op2 + ) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](2); + ops[0] = _op1; + ops[1] = _op2; + return ops; + } + + function toArray( + UserOperation memory _op1, + UserOperation memory _op2, + UserOperation memory _op3 + ) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](3); + ops[0] = _op1; + ops[1] = _op2; + ops[2] = _op3; + return ops; + } + + // uint64 + function toArrayU64(uint64 _a) internal pure returns (uint64[] memory) { + uint64[] memory arr = new uint64[](1); + arr[0] = _a; + return arr; + } + + function toArrayU64( + uint64 _a, + uint64 _b + ) internal pure returns (uint64[] memory) { + uint64[] memory arr = new uint64[](2); + arr[0] = _a; + arr[1] = _b; + return arr; + } + + function toArrayU64( + uint64 _a, + uint64 _b, + uint64 _c + ) internal pure returns (uint64[] memory) { + uint64[] memory arr = new uint64[](3); + arr[0] = _a; + arr[1] = _b; + arr[2] = _c; + return arr; + } + + // uint256 + function toArray(uint256 _a) internal pure returns (uint256[] memory) { + uint256[] memory arr = new uint256[](1); + arr[0] = _a; + return arr; + } + + function toArray( + uint256 _a, + uint256 _b + ) internal pure returns (uint256[] memory) { + uint256[] memory arr = new uint256[](2); + arr[0] = _a; + arr[1] = _b; + return arr; + } + + function toArray( + uint256 _a, + uint256 _b, + uint256 _c + ) internal pure returns (uint256[] memory) { + uint256[] memory arr = new uint256[](3); + arr[0] = _a; + arr[1] = _b; + arr[2] = _c; + return arr; + } + + // ISessionKeyManagerModuleHybrid.SessionData + function toArray( + ISessionKeyManagerModuleHybrid.SessionData memory _a + ) + internal + pure + returns (ISessionKeyManagerModuleHybrid.SessionData[] memory) + { + ISessionKeyManagerModuleHybrid.SessionData[] + memory arr = new ISessionKeyManagerModuleHybrid.SessionData[](1); + arr[0] = _a; + return arr; + } + + function toArray( + ISessionKeyManagerModuleHybrid.SessionData memory _a, + ISessionKeyManagerModuleHybrid.SessionData memory _b + ) + internal + pure + returns (ISessionKeyManagerModuleHybrid.SessionData[] memory) + { + ISessionKeyManagerModuleHybrid.SessionData[] + memory arr = new ISessionKeyManagerModuleHybrid.SessionData[](2); + arr[0] = _a; + arr[1] = _b; + return arr; + } + + function toArray( + ISessionKeyManagerModuleHybrid.SessionData memory _a, + ISessionKeyManagerModuleHybrid.SessionData memory _b, + ISessionKeyManagerModuleHybrid.SessionData memory _c + ) + internal + pure + returns (ISessionKeyManagerModuleHybrid.SessionData[] memory) + { + ISessionKeyManagerModuleHybrid.SessionData[] + memory arr = new ISessionKeyManagerModuleHybrid.SessionData[](3); + arr[0] = _a; + arr[1] = _b; + arr[2] = _c; + return arr; + } + + // bytes + function toArray(bytes memory _a) internal pure returns (bytes[] memory) { + bytes[] memory arr = new bytes[](1); + arr[0] = _a; + return arr; + } + + function toArray( + bytes memory _a, + bytes memory _b + ) internal pure returns (bytes[] memory) { + bytes[] memory arr = new bytes[](2); + arr[0] = _a; + arr[1] = _b; + return arr; + } + + function toArray( + bytes memory _a, + bytes memory _b, + bytes memory _c + ) internal pure returns (bytes[] memory) { + bytes[] memory arr = new bytes[](3); + arr[0] = _a; + arr[1] = _b; + arr[2] = _c; + return arr; + } + + // address + function toArray(address _a) internal pure returns (address[] memory) { + address[] memory arr = new address[](1); + arr[0] = _a; + return arr; + } + + function toArray( + address _a, + address _b + ) internal pure returns (address[] memory) { + address[] memory arr = new address[](2); + arr[0] = _a; + arr[1] = _b; + return arr; + } + + function toArray( + address _a, + address _b, + address _c + ) internal pure returns (address[] memory) { + address[] memory arr = new address[](3); + arr[0] = _a; + arr[1] = _b; + arr[2] = _c; + return arr; + } + + // bytes32 + function toArray(bytes32 _a) internal pure returns (bytes32[] memory) { + bytes32[] memory arr = new bytes32[](1); + arr[0] = _a; + return arr; + } + + function toArray( + bytes32 _a, + bytes32 _b + ) internal pure returns (bytes32[] memory) { + bytes32[] memory arr = new bytes32[](2); + arr[0] = _a; + arr[1] = _b; + return arr; + } + + function toArray( + bytes32 _a, + bytes32 _b, + bytes32 _c + ) internal pure returns (bytes32[] memory) { + bytes32[] memory arr = new bytes32[](3); + arr[0] = _a; + arr[1] = _b; + arr[2] = _c; + return arr; + } +} diff --git a/test/foundry/module/SecurityPolicy/ERC7484SecurityPolicyPlugin.t.sol b/test/foundry/module/SecurityPolicy/ERC7484SecurityPolicyPlugin.t.sol index 409889fc..5b7f71d3 100644 --- a/test/foundry/module/SecurityPolicy/ERC7484SecurityPolicyPlugin.t.sol +++ b/test/foundry/module/SecurityPolicy/ERC7484SecurityPolicyPlugin.t.sol @@ -58,7 +58,7 @@ contract ERC7484SecurityPolicyPluginTest is 0, alice ); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Deploy ERC7484SecurityPolicyPlugin erc7484SecurityPolicyPlugin = new ERC7484SecurityPolicyPlugin( @@ -79,7 +79,7 @@ contract ERC7484SecurityPolicyPluginTest is 0, alice ); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Prepare and set the default configuration defaultConfig.threshold = 3; @@ -103,7 +103,7 @@ contract ERC7484SecurityPolicyPluginTest is 0, alice ); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Deploy and register MultichainValidator with registry validator = MultichainECDSAValidator( @@ -217,7 +217,7 @@ contract ERC7484SecurityPolicyPluginTest is emit ModuleValidated(address(sa), address(validator)); vm.breakpoint("a"); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); assertTrue(sa.isModuleEnabled(address(validator))); } @@ -286,7 +286,7 @@ contract ERC7484SecurityPolicyPluginTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -357,7 +357,7 @@ contract ERC7484SecurityPolicyPluginTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -440,7 +440,7 @@ contract ERC7484SecurityPolicyPluginTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs diff --git a/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.ModuleInstallation.t.sol b/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.ModuleInstallation.t.sol index c6e9fef8..f060798c 100644 --- a/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.ModuleInstallation.t.sol +++ b/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.ModuleInstallation.t.sol @@ -95,7 +95,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is 0, alice ); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Enable p1, p2, p3, p4 ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( @@ -118,7 +118,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is 0, alice ); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Create MultichainValidator validator = new MultichainECDSAValidator(); @@ -142,7 +142,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is vm.expectEmit(true, true, true, true); emit ModuleValidated(address(sa), address(validator)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); assertTrue(p1.wasCalled()); assertTrue(p2.wasCalled()); @@ -174,7 +174,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is vm.expectEmit(true, true, true, true); emit ModuleValidated(address(sa), address(validator)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); assertTrue(p1.wasCalled()); assertTrue(p2.wasCalled()); @@ -203,7 +203,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is p4.setShouldRevert(true); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -253,7 +253,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is p4.setShouldRevert(true); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -296,7 +296,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -342,7 +342,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -375,7 +375,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -417,7 +417,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -459,7 +459,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -501,7 +501,7 @@ contract SecurityPolicyManagerPluginModuleInstallationTest is ); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs diff --git a/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol b/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol index ba229f2d..65edea37 100644 --- a/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol +++ b/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol @@ -68,7 +68,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is vm.expectEmit(true, true, true, true); emit SecurityPolicyEnabled(address(sa), address(p1)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp .securityPoliciesPaginated(address(sa), address(0), 100); @@ -95,7 +95,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p1 data = getSmartAccountExecuteCalldata( @@ -111,7 +111,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is vm.expectEmit(true, true, true, true); emit SecurityPolicyDisabled(address(sa), address(p1)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp .securityPoliciesPaginated(address(sa), address(0), 100); @@ -141,7 +141,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Page Size 100 ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp @@ -251,7 +251,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p3,p2 data = getSmartAccountExecuteCalldata( @@ -268,7 +268,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is emit SecurityPolicyDisabled(address(sa), address(p3)); emit SecurityPolicyDisabled(address(sa), address(p2)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp .securityPoliciesPaginated(address(sa), address(0), 100); @@ -291,7 +291,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is emit SecurityPolicyDisabled(address(sa), address(p4)); emit SecurityPolicyDisabled(address(sa), address(p1)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); enabledSecurityPolicies = spmp.securityPoliciesPaginated( address(sa), @@ -325,7 +325,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is vm.expectEmit(true, true, true, true); emit SecurityPolicyEnabled(address(sa), address(p2)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp .securityPoliciesPaginated(address(sa), address(0), 100); @@ -353,7 +353,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is vm.expectEmit(true, true, true, true); emit SecurityPolicyEnabled(address(sa), address(p4)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); enabledSecurityPolicies = spmp.securityPoliciesPaginated( address(sa), @@ -386,7 +386,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p1,p2,p3,p4 data = getSmartAccountExecuteCalldata( @@ -405,7 +405,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is emit SecurityPolicyDisabled(address(sa), address(p2)); emit SecurityPolicyDisabled(address(sa), address(p1)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp .securityPoliciesPaginated(address(sa), address(0), 100); @@ -427,7 +427,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p1,p2,p3,p4 data = getSmartAccountExecuteCalldata( @@ -446,7 +446,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is emit SecurityPolicyDisabled(address(sa), address(p2)); emit SecurityPolicyDisabled(address(sa), address(p1)); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); enabledSecurityPolicies = spmp.securityPoliciesPaginated( address(sa), @@ -472,7 +472,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -506,7 +506,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -548,7 +548,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -591,7 +591,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -635,7 +635,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -681,7 +681,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable address(0) data = getSmartAccountExecuteCalldata( @@ -696,7 +696,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( @@ -747,7 +747,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable SENTINEL_MODULE_ADDRESS data = getSmartAccountExecuteCalldata( @@ -762,7 +762,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( @@ -812,7 +812,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p4 data = getSmartAccountExecuteCalldata( @@ -827,7 +827,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( @@ -877,7 +877,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p3 data = getSmartAccountExecuteCalldata( @@ -892,7 +892,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( @@ -938,7 +938,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p1->p4 data = getSmartAccountExecuteCalldata( @@ -952,7 +952,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( @@ -1001,7 +1001,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); // Disable p4->p1 data = getSmartAccountExecuteCalldata( @@ -1015,7 +1015,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( @@ -1055,12 +1055,12 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -1093,7 +1093,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is ) ); UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( 1 @@ -1110,7 +1110,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs @@ -1146,7 +1146,7 @@ contract SecurityPolicyManagerPluginPluginManagementTest is UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); vm.recordLogs(); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); Vm.Log[] memory logs = vm.getRecordedLogs(); UserOperationEventData memory eventData = getUserOperationEventData( logs diff --git a/test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.BatchCall.t.sol b/test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.BatchCall.t.sol new file mode 100644 index 00000000..b2ae1320 --- /dev/null +++ b/test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.BatchCall.t.sol @@ -0,0 +1,1632 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SATestBase, IEntryPoint} from "../../base/SATestBase.sol"; +import {SmartAccount} from "sa/SmartAccount.sol"; +import {UserOperation} from "aa-core/EntryPoint.sol"; +import {SessionKeyManagerHybrid} from "sa/modules/SessionKeyManagers/SessionKeyManagerHybrid.sol"; +import {ISessionKeyManagerModuleHybrid} from "sa/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol"; +import {MockSessionValidationModule} from "sa/test/mocks/MockSessionValidationModule.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {Vm} from "forge-std/Test.sol"; + +contract SessionKeyManagerHybridBatchCallTest is SATestBase { + SmartAccount private sa; + SessionKeyManagerHybrid private sessionKeyManagerHybrid; + MockSessionValidationModule private mockSessionValidationModule1; + MockSessionValidationModule private mockSessionValidationModule2; + Stub private stub = new Stub(); + SKMParserStub private skmParserStub = new SKMParserStub(); + + // Events + event SessionCreated( + address indexed sa, + bytes32 indexed sessionDataDigest, + ISessionKeyManagerModuleHybrid.SessionData data + ); + event SessionDisabled( + address indexed sa, + bytes32 indexed sessionDataDigest + ); + event ValidateSessionParams( + address destinationContract, + uint256 callValue, + bytes funcCallData, + bytes sessionKeyData, + bytes callSpecificData + ); + event Log(string message); + + function setUp() public virtual override { + super.setUp(); + + // Deploy Smart Account with default module + uint256 smartAccountDeploymentIndex = 0; + bytes memory moduleSetupData = getEcdsaOwnershipRegistryModuleSetupData( + alice.addr + ); + sa = getSmartAccountWithModule( + address(ecdsaOwnershipRegistryModule), + moduleSetupData, + smartAccountDeploymentIndex, + "aliceSA" + ); + + // Deploy Session Key Modules + sessionKeyManagerHybrid = new SessionKeyManagerHybrid(); + vm.label(address(sessionKeyManagerHybrid), "sessionKeyManagerHybrid"); + mockSessionValidationModule1 = new MockSessionValidationModule(); + vm.label( + address(mockSessionValidationModule1), + "mockSessionValidationModule1" + ); + mockSessionValidationModule2 = new MockSessionValidationModule(); + vm.label( + address(mockSessionValidationModule1), + "mockSessionValidationModule2" + ); + + // Enable Session Key Manager Module + UserOperation memory op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sa), + 0, + abi.encodeCall( + sa.enableModule, + address(sessionKeyManagerHybrid) + ) + ), + sa, + 0, + alice + ); + entryPoint.handleOps(toArray(op), owner.addr); + } + + function testEnableAndUseSessionSingleBatchItem() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest = sessionKeyManagerHybrid.sessionDataDigest( + sessionData + ); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest, sessionData); + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData, + sessionData.sessionKeyData, + callSpecificData + ); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey"); + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is enabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest, address(sa)); + assertEq(enabledSessionData, sessionData); + } + + function testEnableAndUseSessionTwoBatchItems() public { + SessionKeyManagerHybrid.SessionData + memory sessionData1 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest1 = sessionKeyManagerHybrid.sessionDataDigest( + sessionData1 + ); + + SessionKeyManagerHybrid.SessionData + memory sessionData2 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule2), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest2 = sessionKeyManagerHybrid.sessionDataDigest( + sessionData2 + ); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid), uint64(block.chainid)), + toArray(sessionData1, sessionData2), + sa + ); + + // Generate Session Info + bytes memory callSpecificData1 = abi.encode("hello world"); + bytes memory sessionInfo1 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 0, // sessionEnableData[0][0] + sessionData1, + callSpecificData1 + ); + bytes memory callSpecificData2 = abi.encode("hello world 2"); + bytes memory sessionInfo2 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 1, // sessionEnableData[0][1] + sessionData2, + callSpecificData2 + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData1 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey1") + ); + bytes memory callData2 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey2") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData1, callData2) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo1, sessionInfo2) + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest1, sessionData1); + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData1, + sessionData1.sessionKeyData, + callSpecificData1 + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest2, sessionData2); + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData2, + sessionData2.sessionKeyData, + callSpecificData2 + ); + + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey1"); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey2"); + + entryPoint.handleOps(toArray(op), owner.addr); + + // Check sessions are enabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest1, address(sa)); + assertEq(enabledSessionData, sessionData1); + + enabledSessionData = sessionKeyManagerHybrid.enabledSessionsData( + sessionDataDigest2, + address(sa) + ); + assertEq(enabledSessionData, sessionData2); + } + + function testUseSessionTwoBatchItemsPostEnable() public { + SessionKeyManagerHybrid.SessionData + memory sessionData1 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + SessionKeyManagerHybrid.SessionData + memory sessionData2 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule2), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid), uint64(block.chainid)), + toArray(sessionData1, sessionData2), + sa + ); + + // Generate Session Info + bytes memory callSpecificData1 = abi.encode("hello world"); + bytes memory sessionInfo1 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 0, // sessionEnableData[0][0] + sessionData1, + callSpecificData1 + ); + bytes memory callSpecificData2 = abi.encode("hello world 2"); + bytes memory sessionInfo2 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 1, // sessionEnableData[0][1] + sessionData2, + callSpecificData2 + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData1 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey1") + ); + bytes memory callData2 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey2") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData1, callData2) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo1, sessionInfo2) + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Use the sessions again, but this time with the session data pre-enabled + op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData1, callData2) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + new bytes[](0), // no need for sessionEnableData + new bytes[](0), // no need for sessionEnableSignature + toArray( + makeSessionPreEnabledSessionInfo( + sessionData1, + sessionKeyManagerHybrid, + callSpecificData1 + ), + makeSessionPreEnabledSessionInfo( + sessionData2, + sessionKeyManagerHybrid, + callSpecificData2 + ) + ) + ); + + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData1, + sessionData1.sessionKeyData, + callSpecificData1 + ); + + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData2, + sessionData2.sessionKeyData, + callSpecificData2 + ); + + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey1"); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey2"); + entryPoint.handleOps(toArray(op), owner.addr); + } + + function testUseSessionTwoBatchItemsOneFreshAndOtherPostEnable() public { + SessionKeyManagerHybrid.SessionData + memory sessionData1 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + SessionKeyManagerHybrid.SessionData + memory sessionData2 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule2), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid), uint64(block.chainid)), + toArray(sessionData1, sessionData2), + sa + ); + + // Generate Session Info + bytes memory callSpecificData1 = abi.encode("hello world"); + bytes memory sessionInfo1 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 0, // sessionEnableData[0][0] + sessionData1, + callSpecificData1 + ); + bytes memory callSpecificData2 = abi.encode("hello world 2"); + bytes memory sessionInfo2 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 1, // sessionEnableData[0][1] + sessionData2, + callSpecificData2 + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData1 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey1") + ); + bytes memory callData2 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey2") + ); + // Use only one session + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData1) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo1) + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Use the sessions again, but this time the first with the session data pre-enabled + // and the second with a freshly enabled session + op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData1, callData2) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray( + makeSessionPreEnabledSessionInfo( + sessionData1, + sessionKeyManagerHybrid, + callSpecificData1 + ), + sessionInfo2 + ) + ); + + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData1, + sessionData1.sessionKeyData, + callSpecificData1 + ); + + vm.expectEmit(); + emit SessionCreated( + address(sa), + sessionKeyManagerHybrid.sessionDataDigest(sessionData2), + sessionData2 + ); + vm.expectEmit(); + emit ValidateSessionParams( + to, + value, + callData2, + sessionData2.sessionKeyData, + callSpecificData2 + ); + + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey1"); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey2"); + entryPoint.handleOps(toArray(op), owner.addr); + } + + function testShouldNotAllowSessionEnableWithInvalidSessionEnableSignature() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Tamper Session Enable Data + sessionEnableData[0] = bytes1(0xFF); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionNotApproved" + ) + ); + } + } + + function testShouldNotAllowSessionEnableWithEnableDataListLengthMismatch() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + // Add extra session enable data to make the list length mismatch + toArray(sessionEnableData, bytes("")), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: EDListLengthMismatch" + ) + ); + } + } + + function testShouldNotAllowSessionInfosLengthAndBatchItemsLengthMismatch() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + // Add extra batch item to make the list length mismatch + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData, callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionInfo length mismatch" + ) + ); + } + } + + function testShouldNotAllowInvalidSessionEnableDataIndex() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + // Tamper session enable data index + 1, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SKEnableDataIndexInvalid" + ) + ); + } + } + + function testShouldNotAllowInvalidChainIdInSessionEnableData() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + // Tamper chain id + toArrayU64(uint64(block.chainid + 1)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionChainIdMismatch" + ) + ); + } + } + + function testShouldNotAllowSessionWithDifferentDigestToBeExecuted() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Tamper Session Data + sessionData.sessionKeyData = abi.encodePacked(alice.addr); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionKeyDataHashMismatch" + ) + ); + } + } + + function testShouldNotAllowInvalidSessionEnableKeyIndex() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + // Tamper session enable key index + 1, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionKeyIndexInvalid" + ) + ); + } + } + + function testShouldNotAllowUsageOfNonEnabledSessionsInPreEnabledFlow() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionPreEnabledSessionInfo( + sessionData, + sessionKeyManagerHybrid, + callSpecificData + ); + + // Use session directly + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + new bytes[](0), + new bytes[](0), + toArray(sessionInfo) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: Session key is not enabled" + ) + ); + } + } + + function testShouldNotAllowUsageOfSessionsNotValidYet() public { + SessionKeyManagerHybrid.SessionData + memory sessionData1 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + SessionKeyManagerHybrid.SessionData + memory sessionData2 = ISessionKeyManagerModuleHybrid.SessionData({ + // set validAfter to be in the future + validUntil: 0, + validAfter: uint48(block.timestamp) + 1, + sessionValidationModule: address(mockSessionValidationModule2), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid), uint64(block.chainid)), + toArray(sessionData1, sessionData2), + sa + ); + + // Generate Session Info + bytes memory callSpecificData1 = abi.encode("hello world"); + bytes memory sessionInfo1 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 0, // sessionEnableData[0][0] + sessionData1, + callSpecificData1 + ); + bytes memory callSpecificData2 = abi.encode("hello world 2"); + bytes memory sessionInfo2 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 1, // sessionEnableData[0][1] + sessionData2, + callSpecificData2 + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData1 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey1") + ); + bytes memory callData2 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey2") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData1, callData2) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo1, sessionInfo2) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA22 expired or not due" + ) + ); + } + } + + function testShouldNotAllowUsageOfExpiredSession() public { + SessionKeyManagerHybrid.SessionData + memory sessionData1 = ISessionKeyManagerModuleHybrid.SessionData({ + // set validUntil to be in the past + validUntil: uint48(block.timestamp) - 1, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + SessionKeyManagerHybrid.SessionData + memory sessionData2 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule2), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid), uint64(block.chainid)), + toArray(sessionData1, sessionData2), + sa + ); + + // Generate Session Info + bytes memory callSpecificData1 = abi.encode("hello world"); + bytes memory sessionInfo1 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 0, // sessionEnableData[0][0] + sessionData1, + callSpecificData1 + ); + bytes memory callSpecificData2 = abi.encode("hello world 2"); + bytes memory sessionInfo2 = makeSessionEnableSessionInfo( + 0, // sessionEnableData[0] + 1, // sessionEnableData[0][1] + sessionData2, + callSpecificData2 + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData1 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey1") + ); + bytes memory callData2 = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey2") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to, to), + toArray(value, value), + toArray(callData1, callData2) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo1, sessionInfo2) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA22 expired or not due" + ) + ); + } + } + + function testShouldNotAllowSessionExecutionIfSVMReturnsDifferentSigner() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule1), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Enable Data + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData( + toArrayU64(uint64(block.chainid)), + toArray(sessionData), + sa + ); + + // Generate Session Info + bytes memory callSpecificData = abi.encode("hello world"); + bytes memory sessionInfo = makeSessionEnableSessionInfo( + 0, + 0, + sessionData, + callSpecificData + ); + + // Enable and Use session + address to = address(stub); + uint256 value = 0; + bytes memory callData = abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ); + UserOperation memory op = makeSessionUserOp( + getSmartAccountBatchExecuteCalldata( + toArray(to), + toArray(value), + toArray(callData) + ), + sa, + 0, + sessionKeyManagerHybrid, + bob, + toArray(sessionEnableData), + toArray(sessionEnableSignature), + toArray(sessionInfo) + ); + + vm.mockCall( + address(mockSessionValidationModule1), + 0, + bytes(""), + abi.encode(charlie.addr) + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA24 signature error" + ) + ); + } + } + + function testShouldNotSupportERC1271SignatureValidation( + uint256 seed + ) public { + bytes32 userOpHash = keccak256(abi.encodePacked(seed)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alice.privateKey, userOpHash); + bytes memory signature = abi.encodePacked(r, s, v); + assertEq( + sessionKeyManagerHybrid.isValidSignature(userOpHash, signature), + bytes4(0xffffffff) + ); + } + + function testShouldNotSupportERC1271SignatureValidationUnsafe( + uint256 seed + ) public { + bytes32 userOpHash = keccak256(abi.encodePacked(seed)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alice.privateKey, userOpHash); + bytes memory signature = abi.encodePacked(r, s, v); + assertEq( + sessionKeyManagerHybrid.isValidSignatureUnsafe( + userOpHash, + signature + ), + bytes4(0xffffffff) + ); + } + + function testShouldParseValidateUserBatchSignature( + bytes[] calldata _sessionEnableDataList, + bytes[] calldata _sessionEnableSignatureList, + bytes[] calldata _sessionInfos, + bytes calldata _sessionKeySignature + ) public { + bytes memory data = abi.encode( + _sessionEnableDataList, + _sessionEnableSignatureList, + _sessionInfos, + _sessionKeySignature + ); + + ( + bytes[] memory sessionEnableDataList, + bytes[] memory sessionEnableSignatureList, + bytes[] memory sessionInfos, + bytes memory sessionKeySignature + ) = skmParserStub.parseValidateUserOpBatchSignature(data); + + assertEq( + abi.encode(sessionEnableDataList), + abi.encode(_sessionEnableDataList), + "mismatched sessionEnableDataList" + ); + + assertEq( + abi.encode(sessionEnableSignatureList), + abi.encode(_sessionEnableSignatureList), + "mismatched sessionEnableSignatureList" + ); + + assertEq( + abi.encode(sessionInfos), + abi.encode(_sessionInfos), + "mismatched sessionInfos" + ); + + assertEq( + sessionKeySignature, + _sessionKeySignature, + "mismatched sessionKeySignature" + ); + } + + function testShouldParseSessionDataPreEnableSignatureBatchCall( + uint8 _isSessionEnableFlag, + bytes32 _sessionDataDigest, + bytes memory _callSpecificData + ) public { + bytes memory data = abi.encodePacked( + _isSessionEnableFlag, + _sessionDataDigest, + abi.encode(_callSpecificData) + ); + + ( + bytes32 sessionDataDigest, + bytes memory callSpecificData + ) = skmParserStub.parseSessionDataPreEnabledSignatureBatchCall(data); + + assertEq( + sessionDataDigest, + _sessionDataDigest, + "mismatched sessionDataDigest" + ); + assertEq( + callSpecificData, + _callSpecificData, + "mismatched callSpecificData" + ); + } + + function testShouldParseSessionEnableSignatureCorrectly( + uint8 _isSessionEnableFlag, + uint8 _sessionEnableDataIndex, + uint8 _sessionKeyIndex, + uint48 _validUntil, + uint48 _validAfter, + address _sessionValidationModule, + bytes calldata _sessionKeyData, + bytes calldata _callSpecificData + ) public { + bytes memory data = abi.encodePacked( + _isSessionEnableFlag, + _sessionEnableDataIndex, + _sessionKeyIndex, + _validUntil, + _validAfter, + _sessionValidationModule, + abi.encode(_sessionKeyData, _callSpecificData) + ); + + ( + uint256 sessionEnableDataIndex, + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes memory sessionKeyData, + bytes memory callSpecificData + ) = skmParserStub.parseSessionEnableSignatureBatchCall(data); + + assertEq(sessionEnableDataIndex, _sessionEnableDataIndex); + assertEq(sessionKeyIndex, _sessionKeyIndex); + assertEq(validUntil, _validUntil); + assertEq(validAfter, _validAfter); + assertEq(sessionValidationModule, _sessionValidationModule); + assertEq(sessionKeyData, _sessionKeyData); + assertEq(callSpecificData, _callSpecificData); + } + + function testShouldParseBatchCallDataCorrectly( + address[] calldata _destinations, + uint256[] calldata _callValues, + bytes[] calldata _operationCalldatas + ) public { + bytes memory data = abi.encodeCall( + SmartAccount.executeBatch, + (_destinations, _callValues, _operationCalldatas) + ); + + ( + address[] memory destinations, + uint256[] memory callValues, + bytes[] memory operationCalldatas + ) = skmParserStub.parseBatchCallCalldata(data); + + assertEq(destinations, _destinations, "mismatched destinations"); + assertEq(callValues, _callValues, "mismatched callValues"); + + assertEq( + abi.encode(operationCalldatas), + abi.encode(_operationCalldatas), + "mismatched operationCalldatas" + ); + } + + function makeSessionEnableData( + uint64[] memory chainIds, + SessionKeyManagerHybrid.SessionData[] memory _sessionDatas, + SmartAccount _signer + ) internal view returns (bytes memory, bytes memory) { + bytes32[] memory sessionDigests = new bytes32[](_sessionDatas.length); + for (uint256 i = 0; i < _sessionDatas.length; i++) { + sessionDigests[i] = sessionKeyManagerHybrid.sessionDataDigest( + _sessionDatas[i] + ); + } + bytes memory sessionEnableData = abi.encodePacked( + uint8(_sessionDatas.length) + ); + for (uint256 i = 0; i < chainIds.length; ++i) { + sessionEnableData = abi.encodePacked( + sessionEnableData, + chainIds[i] + ); + } + sessionEnableData = abi.encodePacked(sessionEnableData, sessionDigests); + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19Ethereum Signed Message:\n52", + keccak256(sessionEnableData), + _signer + ) + ); + TestAccount memory owner = testAccountsByAddress[ + ecdsaOwnershipRegistryModule.getOwner(address(_signer)) + ]; + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner.privateKey, digest); + bytes memory erc1271Signature = abi.encode( + abi.encodePacked(r, s, v), + ecdsaOwnershipRegistryModule + ); + return (sessionEnableData, erc1271Signature); + } + + function makeSessionUserOp( + bytes memory _calldata, + SmartAccount _sa, + uint192 _nonceKey, + SessionKeyManagerHybrid _skm, + TestAccount memory _sessionSigner, + bytes[] memory _sessionEnableDataList, + bytes[] memory _sessionEnableSignatureList, + bytes[] memory _sessionInfos + ) internal view returns (UserOperation memory op) { + op = UserOperation({ + sender: address(_sa), + nonce: entryPoint.getNonce(address(_sa), _nonceKey), + initCode: bytes(""), + callData: _calldata, + callGasLimit: gasleft() / 100, + verificationGasLimit: gasleft() / 100, + preVerificationGas: DEFAULT_PRE_VERIFICATIION_GAS, + maxFeePerGas: tx.gasprice, + maxPriorityFeePerGas: tx.gasprice - block.basefee, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + bytes memory sessionKeySignature; + { + // Sign the UserOp + bytes32 userOpHash = entryPoint.getUserOpHash(op); + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _sessionSigner.privateKey, + ECDSA.toEthSignedMessageHash(userOpHash) + ); + sessionKeySignature = abi.encodePacked(r, s, v); + } + + // Generate Module Signature + bytes memory moduleSignature = abi.encode( + _sessionEnableDataList, + _sessionEnableSignatureList, + _sessionInfos, + sessionKeySignature + ); + op.signature = abi.encode(moduleSignature, _skm); + } + + function makeSessionEnableSessionInfo( + uint8 _sessionEnableDataIndex, + uint8 _sessionKeyIndex, + SessionKeyManagerHybrid.SessionData memory _sessionData, + bytes memory _callSpecificData + ) internal pure returns (bytes memory info) { + info = abi.encodePacked( + uint8(0x1), + _sessionEnableDataIndex, + _sessionKeyIndex, + _sessionData.validUntil, + _sessionData.validAfter, + _sessionData.sessionValidationModule, + abi.encode(_sessionData.sessionKeyData, _callSpecificData) + ); + } + + function makeSessionPreEnabledSessionInfo( + SessionKeyManagerHybrid.SessionData memory _sessionData, + SessionKeyManagerHybrid _skm, + bytes memory _callSpecificData + ) internal pure returns (bytes memory info) { + info = abi.encodePacked( + uint8(0x0), + _skm.sessionDataDigest(_sessionData), + abi.encode(_callSpecificData) + ); + } +} + +contract Stub { + event Log(string message); + + function emitMessage(string calldata _message) public { + emit Log(_message); + } +} + +contract SKMParserStub is SessionKeyManagerHybrid { + function parseBatchCallCalldata( + bytes calldata _userOpCalldata + ) + external + pure + returns ( + address[] calldata destinations, + uint256[] calldata callValues, + bytes[] calldata operationCalldatas + ) + { + return _parseBatchCallCalldata(_userOpCalldata); + } + + function parseSessionEnableSignatureBatchCall( + bytes calldata _moduleSignature + ) + external + pure + returns ( + uint256 sessionEnableDataIndex, + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata callSpecificData + ) + { + return _parseSessionEnableSignatureBatchCall(_moduleSignature); + } + + function parseSessionDataPreEnabledSignatureBatchCall( + bytes calldata _moduleSignature + ) + external + pure + returns (bytes32 sessionDataDigest, bytes calldata callSpecificData) + { + return _parseSessionDataPreEnabledSignatureBatchCall(_moduleSignature); + } + + function parseValidateUserOpBatchSignature( + bytes calldata _moduleSignature + ) + external + pure + returns ( + bytes[] calldata sessionEnableDataList, + bytes[] calldata sessionEnableSignatureList, + bytes[] calldata sessionInfos, + bytes calldata sessionKeySignature + ) + { + return _parseValidateUserOpBatchSignature(_moduleSignature); + } +} diff --git a/test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.SingleCall.t.sol b/test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.SingleCall.t.sol new file mode 100644 index 00000000..1e5c0a0b --- /dev/null +++ b/test/foundry/module/SessionKeyManager/SessionKeyManagerHybrid.SingleCall.t.sol @@ -0,0 +1,1199 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SATestBase, IEntryPoint} from "../../base/SATestBase.sol"; +import {SmartAccount} from "sa/SmartAccount.sol"; +import {UserOperation} from "aa-core/EntryPoint.sol"; +import {SessionKeyManagerHybrid} from "sa/modules/SessionKeyManagers/SessionKeyManagerHybrid.sol"; +import {ISessionKeyManagerModuleHybrid} from "sa/interfaces/modules/SessionKeyManagers/ISessionKeyManagerModuleHybrid.sol"; +import {MockSessionValidationModule} from "sa/test/mocks/MockSessionValidationModule.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {Vm} from "forge-std/Test.sol"; +import "forge-std/console2.sol"; + +contract SessionKeyManagerHybridSingleCallTest is SATestBase { + SmartAccount private sa; + SessionKeyManagerHybrid private sessionKeyManagerHybrid; + MockSessionValidationModule private mockSessionValidationModule; + Stub private stub = new Stub(); + SKMParserStub private skmParserStub = new SKMParserStub(); + + // Events + event SessionCreated( + address indexed sa, + bytes32 indexed sessionDataDigest, + ISessionKeyManagerModuleHybrid.SessionData data + ); + event SessionDisabled( + address indexed sa, + bytes32 indexed sessionDataDigest + ); + event Log(string message); + + function setUp() public virtual override { + super.setUp(); + + // Deploy Smart Account with default module + uint256 smartAccountDeploymentIndex = 0; + bytes memory moduleSetupData = getEcdsaOwnershipRegistryModuleSetupData( + alice.addr + ); + sa = getSmartAccountWithModule( + address(ecdsaOwnershipRegistryModule), + moduleSetupData, + smartAccountDeploymentIndex, + "aliceSA" + ); + + // Deploy Session Key Modules + sessionKeyManagerHybrid = new SessionKeyManagerHybrid(); + vm.label(address(sessionKeyManagerHybrid), "sessionKeyManagerHybrid"); + mockSessionValidationModule = new MockSessionValidationModule(); + vm.label( + address(mockSessionValidationModule), + "mockSessionValidationModule" + ); + + // Enable Session Key Manager Module + UserOperation memory op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sa), + 0, + abi.encodeCall( + sa.enableModule, + address(sessionKeyManagerHybrid) + ) + ), + sa, + 0, + alice + ); + entryPoint.handleOps(toArray(op), owner.addr); + } + + function testEnableSession() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest = sessionKeyManagerHybrid.sessionDataDigest( + sessionData + ); + + // Enable session + UserOperation memory op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sessionKeyManagerHybrid), + 0, + abi.encodeCall( + sessionKeyManagerHybrid.enableSession, + (sessionData) + ) + ), + sa, + 0, + alice + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest, sessionData); + + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is enabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest, address(sa)); + assertEq(enabledSessionData, sessionData); + } + + function testEnableSessions() public { + SessionKeyManagerHybrid.SessionData + memory sessionData1 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + SessionKeyManagerHybrid.SessionData + memory sessionData2 = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 1, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest1 = sessionKeyManagerHybrid.sessionDataDigest( + sessionData1 + ); + bytes32 sessionDataDigest2 = sessionKeyManagerHybrid.sessionDataDigest( + sessionData2 + ); + + // Enable session + UserOperation memory op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sessionKeyManagerHybrid), + 0, + abi.encodeCall( + sessionKeyManagerHybrid.enableSessions, + (toArray(sessionData1, sessionData2)) + ) + ), + sa, + 0, + alice + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest1, sessionData1); + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest2, sessionData2); + + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is enabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest1, address(sa)); + assertEq(enabledSessionData, sessionData1); + enabledSessionData = sessionKeyManagerHybrid.enabledSessionsData( + sessionDataDigest2, + address(sa) + ); + assertEq(enabledSessionData, sessionData2); + } + + function testEnableAndUseSessionInSameTransaction() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest = sessionKeyManagerHybrid.sessionDataDigest( + sessionData + ); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest, sessionData); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey"); + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is enabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest, address(sa)); + assertEq(enabledSessionData, sessionData); + } + + function testExplicitEnableAndUseSessionDifferentOp() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + // Enable session + UserOperation memory op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sessionKeyManagerHybrid), + 0, + abi.encodeCall( + sessionKeyManagerHybrid.enableSession, + (sessionData) + ) + ), + sa, + 0, + alice + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Use session with just digest + op = makeUseExistingSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob + ); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey"); + entryPoint.handleOps(toArray(op), owner.addr); + } + + function testEnableAndUseSessionPostSessionEnable() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session for the first time + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Use session with just digest + op = makeUseExistingSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob + ); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey"); + entryPoint.handleOps(toArray(op), owner.addr); + } + + function testEnableAndUseSessionMultiSessionEnable() public { + // Generate Session Data + uint64[] memory chainIds = new uint64[](5); + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](5); + + for (uint256 i = 0; i < chainIds.length; ++i) { + sessionDatas[i] = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: uint48(block.timestamp + i), + validAfter: uint48(block.timestamp), + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + chainIds[i] = uint64(block.chainid); + } + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionDatas[0], + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + + bytes32 sessionDataDigest = sessionKeyManagerHybrid.sessionDataDigest( + sessionDatas[0] + ); + + vm.expectEmit(); + emit SessionCreated(address(sa), sessionDataDigest, sessionDatas[0]); + vm.expectEmit(); + emit Log("shouldProcessTransactionFromSessionKey"); + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is enabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest, address(sa)); + assertEq(enabledSessionData, sessionDatas[0]); + + // Ensure other sessions are not enabled + for (uint256 i = 1; i < sessionDatas.length; ++i) { + enabledSessionData = sessionKeyManagerHybrid.enabledSessionsData( + sessionKeyManagerHybrid.sessionDataDigest(sessionDatas[i]), + address(sa) + ); + ISessionKeyManagerModuleHybrid.SessionData memory emptyData; + assertEq(enabledSessionData, emptyData); + } + } + + function testDisableSession() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest = sessionKeyManagerHybrid.sessionDataDigest( + sessionData + ); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Disable session + op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sessionKeyManagerHybrid), + 0, + abi.encodeCall( + sessionKeyManagerHybrid.disableSession, + (sessionDataDigest) + ) + ), + sa, + 0, + alice + ); + vm.expectEmit(); + emit SessionDisabled(address(sa), sessionDataDigest); + + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is disabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest, address(sa)); + + ISessionKeyManagerModuleHybrid.SessionData memory emptyData; + assertEq(enabledSessionData, emptyData); + } + + function testDisableSessions() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + bytes32 sessionDataDigest = sessionKeyManagerHybrid.sessionDataDigest( + sessionData + ); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Disable session + op = makeEcdsaModuleUserOp( + getSmartAccountExecuteCalldata( + address(sessionKeyManagerHybrid), + 0, + abi.encodeCall( + sessionKeyManagerHybrid.disableSessions, + (toArray(sessionDataDigest)) + ) + ), + sa, + 0, + alice + ); + vm.expectEmit(); + emit SessionDisabled(address(sa), sessionDataDigest); + + entryPoint.handleOps(toArray(op), owner.addr); + + // Check session is disabled + ISessionKeyManagerModuleHybrid.SessionData + memory enabledSessionData = sessionKeyManagerHybrid + .enabledSessionsData(sessionDataDigest, address(sa)); + + ISessionKeyManagerModuleHybrid.SessionData memory emptyData; + assertEq(enabledSessionData, emptyData); + } + + function testShouldNotValidateTransactionFromNonEnabledSession() public { + // Generate Session Data + uint64[] memory chainIds = new uint64[](5); + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](5); + + for (uint256 i = 0; i < chainIds.length; ++i) { + sessionDatas[i] = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: uint48(block.timestamp + i), + validAfter: uint48(block.timestamp), + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + chainIds[i] = uint64(block.chainid); + } + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Use session not in session enable data + sessionDatas[0].validUntil *= 2; + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionDatas[0], + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionKeyDataHashMismatch" + ) + ); + } + } + + function testShouldNotValidateTransactionFromNonEnabledSessionWithPostCacheFlow() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Do not enable session + + // Use session + UserOperation memory op = makeUseExistingSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob + ); + + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: Session key is not enabled" + ) + ); + } + } + + function testShouldNotValidateTransactionSignedFromInvalidSessionSigner() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + charlie, + 0, + sessionEnableData, + sessionEnableSignature + ); + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA24 signature error" + ) + ); + } + } + + function testShouldNotValidateTransactionWithInvalidSessionIndex() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + chainIds.length, + sessionEnableData, + sessionEnableSignature + ); + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionKeyIndexInvalid" + ) + ); + } + } + + function testShouldNotValidateTransactionWithInvalidChainId() public { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + chainIds[0] += 1; + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA23 reverted: SKM: SessionChainIdMismatch" + ) + ); + } + } + + function testShouldNotValidateTransactionSignedFromInvalidSessionSignerPostSessionEnable() + public + { + SessionKeyManagerHybrid.SessionData + memory sessionData = ISessionKeyManagerModuleHybrid.SessionData({ + validUntil: 0, + validAfter: 0, + sessionValidationModule: address(mockSessionValidationModule), + sessionKeyData: abi.encodePacked(bob.addr) + }); + + // Generate Session Data + uint64[] memory chainIds = new uint64[](1); + chainIds[0] = uint64(block.chainid); + + SessionKeyManagerHybrid.SessionData[] + memory sessionDatas = new SessionKeyManagerHybrid.SessionData[](1); + sessionDatas[0] = sessionData; + + ( + bytes memory sessionEnableData, + bytes memory sessionEnableSignature + ) = makeSessionEnableData(chainIds, sessionDatas, sa); + + // Enable and Use session + UserOperation memory op = makeEnableAndUseSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + bob, + 0, + sessionEnableData, + sessionEnableSignature + ); + entryPoint.handleOps(toArray(op), owner.addr); + + // Use session with just digest but wrong signer + op = makeUseExistingSessionUserOp( + getSmartAccountExecuteCalldata( + address(stub), + 0, + abi.encodeCall( + stub.emitMessage, + ("shouldProcessTransactionFromSessionKey") + ) + ), + sa, + 0, + sessionKeyManagerHybrid, + sessionData, + charlie + ); + try entryPoint.handleOps(toArray(op), owner.addr) { + fail("should have reverted"); + } catch (bytes memory reason) { + assertEq( + reason, + abi.encodeWithSelector( + IEntryPoint.FailedOp.selector, + 0, + "AA24 signature error" + ) + ); + } + } + + function testShouldNotSupportERC1271SignatureValidation( + uint256 seed + ) public { + bytes32 userOpHash = keccak256(abi.encodePacked(seed)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alice.privateKey, userOpHash); + bytes memory signature = abi.encodePacked(r, s, v); + assertEq( + sessionKeyManagerHybrid.isValidSignature(userOpHash, signature), + bytes4(0xffffffff) + ); + } + + function testShouldNotSupportERC1271SignatureValidationUnsafe( + uint256 seed + ) public { + bytes32 userOpHash = keccak256(abi.encodePacked(seed)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alice.privateKey, userOpHash); + bytes memory signature = abi.encodePacked(r, s, v); + assertEq( + sessionKeyManagerHybrid.isValidSignatureUnsafe( + userOpHash, + signature + ), + bytes4(0xffffffff) + ); + } + + function testShouldParseEnableSessionSignatureCorrectly( + uint8 _isSessionEnableTransaction, + uint8 _sessionKeyIndex, + uint48 _validUntil, + uint48 _validAfter, + address _sessionValidationModule, + bytes calldata _sessionKeyData, + bytes calldata _sessionEnableData, + bytes calldata _sessionEnableSignature, + bytes calldata _sessionKeySignature + ) public { + bytes memory encoded = abi.encodePacked( + _isSessionEnableTransaction, + _sessionKeyIndex, + _validUntil, + _validAfter, + _sessionValidationModule, + abi.encode( + _sessionKeyData, + _sessionEnableData, + _sessionEnableSignature, + _sessionKeySignature + ) + ); + ( + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes memory sessionKeyData, + bytes memory sessionEnableData, + bytes memory sessionEnableSignature, + bytes memory sessionKeySignature + ) = skmParserStub.parseSessionEnableSignatureSingleCall(encoded); + + assertEq( + sessionKeyIndex, + _sessionKeyIndex, + "mismatched sessionKeyIndex" + ); + assertEq(validUntil, _validUntil, "mismatched validUntil"); + assertEq(validAfter, _validAfter, "mismatched validAfter"); + assertEq( + sessionValidationModule, + _sessionValidationModule, + "mismatched sessionValidationModule" + ); + assertEq(sessionKeyData, _sessionKeyData, "mismatched sessionKeyData"); + assertEq( + sessionEnableData, + _sessionEnableData, + "mismatched sessionEnableData" + ); + assertEq( + sessionEnableSignature, + _sessionEnableSignature, + "mismatched sessionEnableSignature" + ); + assertEq( + sessionKeySignature, + _sessionKeySignature, + "mismatched sessionKeySignature" + ); + } + + function testShouldParsePreEnabledSignatureCorrectly( + uint8 _isSessionEnableTransaction, + bytes32 _sessionDataDigest, + bytes calldata _sessionKeySignature + ) public { + bytes memory encoded = abi.encodePacked( + _isSessionEnableTransaction, + abi.encode(_sessionDataDigest, _sessionKeySignature) + ); + ( + bytes32 sessionDataDigest, + bytes memory sessionKeySignature + ) = skmParserStub.parseSessionDataPreEnabledSignatureSingleCall( + encoded + ); + + assertEq( + sessionDataDigest, + _sessionDataDigest, + "mismatched sessionDataDigest" + ); + assertEq( + sessionKeySignature, + _sessionKeySignature, + "mismatched sessionKeySignature" + ); + } + + function makeSessionEnableData( + uint64[] memory chainIds, + SessionKeyManagerHybrid.SessionData[] memory _sessionDatas, + SmartAccount _signer + ) internal view returns (bytes memory, bytes memory) { + bytes32[] memory sessionDigests = new bytes32[](_sessionDatas.length); + for (uint256 i = 0; i < _sessionDatas.length; i++) { + sessionDigests[i] = sessionKeyManagerHybrid.sessionDataDigest( + _sessionDatas[i] + ); + } + bytes memory sessionEnableData = abi.encodePacked( + uint8(_sessionDatas.length) + ); + for (uint256 i = 0; i < chainIds.length; ++i) { + sessionEnableData = abi.encodePacked( + sessionEnableData, + chainIds[i] + ); + } + sessionEnableData = abi.encodePacked(sessionEnableData, sessionDigests); + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19Ethereum Signed Message:\n52", + keccak256(sessionEnableData), + _signer + ) + ); + TestAccount memory owner = testAccountsByAddress[ + ecdsaOwnershipRegistryModule.getOwner(address(_signer)) + ]; + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner.privateKey, digest); + bytes memory erc1271Signature = abi.encode( + abi.encodePacked(r, s, v), + ecdsaOwnershipRegistryModule + ); + return (sessionEnableData, erc1271Signature); + } + + function makeEnableAndUseSessionUserOp( + bytes memory _calldata, + SmartAccount _sa, + uint192 _nonceKey, + SessionKeyManagerHybrid _skm, + SessionKeyManagerHybrid.SessionData memory _sessionData, + TestAccount memory _sessionSigner, + uint256 _sessionKeyIndex, + bytes memory _sessionEnableData, + bytes memory _sessionEnableSignature + ) internal view returns (UserOperation memory op) { + op = UserOperation({ + sender: address(_sa), + nonce: entryPoint.getNonce(address(_sa), _nonceKey), + initCode: bytes(""), + callData: _calldata, + callGasLimit: gasleft() / 100, + verificationGasLimit: gasleft() / 100, + preVerificationGas: DEFAULT_PRE_VERIFICATIION_GAS, + maxFeePerGas: tx.gasprice, + maxPriorityFeePerGas: tx.gasprice - block.basefee, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + bytes memory sessionKeySignature; + { + // Sign the UserOp + bytes32 userOpHash = entryPoint.getUserOpHash(op); + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _sessionSigner.privateKey, + ECDSA.toEthSignedMessageHash(userOpHash) + ); + sessionKeySignature = abi.encodePacked(r, s, v); + } + + // Generate Module Signature + bytes memory moduleSignature = abi.encodePacked( + uint8(0x01), + uint8(_sessionKeyIndex), + _sessionData.validUntil, + _sessionData.validAfter, + _sessionData.sessionValidationModule, + abi.encode( + _sessionData.sessionKeyData, + _sessionEnableData, + _sessionEnableSignature, + sessionKeySignature + ) + ); + op.signature = abi.encode(moduleSignature, _skm); + } + + function makeUseExistingSessionUserOp( + bytes memory _calldata, + SmartAccount _sa, + uint192 _nonceKey, + SessionKeyManagerHybrid _skm, + SessionKeyManagerHybrid.SessionData memory _sessionData, + TestAccount memory _sessionSigner + ) internal view returns (UserOperation memory op) { + op = UserOperation({ + sender: address(_sa), + nonce: entryPoint.getNonce(address(_sa), _nonceKey), + initCode: bytes(""), + callData: _calldata, + callGasLimit: gasleft() / 100, + verificationGasLimit: gasleft() / 100, + preVerificationGas: DEFAULT_PRE_VERIFICATIION_GAS, + maxFeePerGas: tx.gasprice, + maxPriorityFeePerGas: tx.gasprice - block.basefee, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + // Sign the UserOp + bytes32 userOpHash = entryPoint.getUserOpHash(op); + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _sessionSigner.privateKey, + ECDSA.toEthSignedMessageHash(userOpHash) + ); + bytes memory sessionKeySignature = abi.encodePacked(r, s, v); + + // Generate Module Signature + bytes memory moduleSignature = abi.encodePacked( + uint8(0x00), + abi.encode( + sessionKeyManagerHybrid.sessionDataDigest(_sessionData), + sessionKeySignature + ) + ); + op.signature = abi.encode(moduleSignature, _skm); + } +} + +contract Stub { + event Log(string message); + + function emitMessage(string calldata _message) public { + emit Log(_message); + } +} + +contract SKMParserStub is SessionKeyManagerHybrid { + function parseSessionEnableSignatureSingleCall( + bytes calldata _moduleSignature + ) + public + pure + returns ( + uint256 sessionKeyIndex, + uint48 validUntil, + uint48 validAfter, + address sessionValidationModule, + bytes calldata sessionKeyData, + bytes calldata sessionEnableData, + bytes calldata sessionEnableSignature, + bytes calldata sessionKeySignature + ) + { + return _parseSessionEnableSignatureSingleCall(_moduleSignature); + } + + function parseSessionDataPreEnabledSignatureSingleCall( + bytes calldata _moduleSignature + ) + public + pure + returns (bytes32 sessionDataDigest, bytes calldata sessionKeySignature) + { + return _parseSessionDataPreEnabledSignatureSingleCall(_moduleSignature); + } +} diff --git a/test/foundry/smart-account/SA.Basics.t.sol b/test/foundry/smart-account/SA.Basics.t.sol index d0ae1144..dd998208 100644 --- a/test/foundry/smart-account/SA.Basics.t.sol +++ b/test/foundry/smart-account/SA.Basics.t.sol @@ -96,7 +96,7 @@ contract SABasicsTest is SATestBase { alice ); vm.breakpoint("a"); - entryPoint.handleOps(arraifyOps(op), owner.addr); + entryPoint.handleOps(toArray(op), owner.addr); } function testReceiveEtherWithGasLimit() external { @@ -116,7 +116,7 @@ contract SABasicsTest is SATestBase { vm.deal(address(mockEthSender), 100 ether); uint256 userSABalanceBefore = address(sa).balance; - + uint256 gasStipend = 0; mockEthSender.send(address(sa), 1 ether, gasStipend); diff --git a/test/utils/hybridSessionKeyManager.ts b/test/utils/hybridSessionKeyManager.ts new file mode 100644 index 00000000..2742fcb2 --- /dev/null +++ b/test/utils/hybridSessionKeyManager.ts @@ -0,0 +1,315 @@ +import { EntryPoint } from "@account-abstraction/contracts"; +import { BigNumberish, BytesLike, Signer } from "ethers"; +import { UserOperation } from "./userOperation"; +import { + ISessionKeyManagerModuleHybrid, + SessionKeyManagerHybrid, + SmartAccount__factory, + EcdsaOwnershipRegistryModule, +} from "../../typechain-types"; +import { + arrayify, + defaultAbiCoder, + hexConcat, + solidityKeccak256, + solidityPack, +} from "ethers/lib/utils"; +import { fillAndSign } from "./userOp"; + +type ExecutionCallParams = { + to: string; + value: BigNumberish; + calldata: BytesLike; +}; + +enum TRANSACTION_MODE { + PRE_ENABLED = 0, + ENABLE_AND_USE = 1, +} + +export class HybridSKMUtils { + // eslint-disable-next-line no-useless-constructor + constructor( + protected readonly entryPoint: EntryPoint, + protected readonly sessionKeyManager: SessionKeyManagerHybrid, + protected readonly ecdsaModule: EcdsaOwnershipRegistryModule + ) {} + + async sessionDigest( + sessionData: ISessionKeyManagerModuleHybrid.SessionDataStruct + ): Promise { + return this.sessionKeyManager.sessionDataDigest(sessionData); + } + + async makeSessionEnableData( + chainIds: number[], + sessionDatas: ISessionKeyManagerModuleHybrid.SessionDataStruct[], + smartAccountAddress: string, + smartAccountSigner: Signer + ): Promise<{ + sessionEnableData: BytesLike; + sessionEnableSignature: BytesLike; + }> { + const sessionDataDigests = await Promise.all( + sessionDatas.map((sessionData) => this.sessionDigest(sessionData)) + ); + + const sessionEnableData = solidityPack( + [ + "uint8", + ...new Array(chainIds.length).fill("uint64"), + ...new Array(sessionDataDigests.length).fill("bytes32"), + ], + [chainIds.length, ...chainIds, ...sessionDataDigests] + ); + + const sessionEnableDataHash = solidityKeccak256( + [ + "uint8", + ...new Array(chainIds.length).fill("uint64"), + ...new Array(sessionDataDigests.length).fill("bytes32"), + ], + [chainIds.length, ...chainIds, ...sessionDataDigests] + ); + + const messageHashAndAddress = arrayify( + hexConcat([sessionEnableDataHash, smartAccountAddress]) + ); + + const signature = await smartAccountSigner.signMessage( + messageHashAndAddress + ); + + const signatureWithModuleAddress = defaultAbiCoder.encode( + ["bytes", "address"], + [signature, this.ecdsaModule.address] + ); + + return { + sessionEnableData, + sessionEnableSignature: signatureWithModuleAddress, + }; + } +} + +export class HybridSKMSingleCallUtils extends HybridSKMUtils { + // eslint-disable-next-line no-useless-constructor + constructor( + entryPoint: EntryPoint, + sessionKeyManager: SessionKeyManagerHybrid, + ecdsaModule: EcdsaOwnershipRegistryModule + ) { + super(entryPoint, sessionKeyManager, ecdsaModule); + } + + async makeEcdsaSessionKeySignedUserOpForEnableAndUseSession( + userOpSender: string, + executionCallParams: ExecutionCallParams, + sessionKey: Signer, + sessionData: ISessionKeyManagerModuleHybrid.SessionDataStruct, + sessionEnableData: BytesLike, + sessionEnableSignature: BytesLike, + sessionIndex: number, + options?: { + preVerificationGas?: number; + } + ): Promise { + const callData = SmartAccount__factory.createInterface().encodeFunctionData( + "execute", + [ + executionCallParams.to, + executionCallParams.value, + executionCallParams.calldata, + ] + ); + + const userOp = await fillAndSign( + { + sender: userOpSender, + callData, + ...options, + }, + sessionKey, + this.entryPoint, + "nonce", + true + ); + + const paddedSig = solidityPack( + ["uint8", "uint8", "uint48", "uint48", "address", "bytes"], + [ + TRANSACTION_MODE.ENABLE_AND_USE, + sessionIndex, + sessionData.validUntil, + sessionData.validAfter, + sessionData.sessionValidationModule, + defaultAbiCoder.encode( + ["bytes", "bytes", "bytes", "bytes"], + [ + sessionData.sessionKeyData, + sessionEnableData, + sessionEnableSignature, + userOp.signature, + ] + ), + ] + ); + + const signatureWithModuleAddress = defaultAbiCoder.encode( + ["bytes", "address"], + [paddedSig, this.sessionKeyManager.address] + ); + userOp.signature = signatureWithModuleAddress; + + return userOp; + } + + async makeEcdsaSessionKeySignedUserOpForPreEnabledSession( + userOpSender: string, + executionCallParams: ExecutionCallParams, + sessionKey: Signer, + sessionData: ISessionKeyManagerModuleHybrid.SessionDataStruct, + options?: { + preVerificationGas?: number; + } + ): Promise { + const callData = SmartAccount__factory.createInterface().encodeFunctionData( + "execute", + [ + executionCallParams.to, + executionCallParams.value, + executionCallParams.calldata, + ] + ); + + const userOp = await fillAndSign( + { + sender: userOpSender, + callData, + ...options, + }, + sessionKey, + this.entryPoint, + "nonce", + true + ); + + const paddedSig = solidityPack( + ["uint8", "bytes"], + [ + TRANSACTION_MODE.PRE_ENABLED, + defaultAbiCoder.encode( + ["bytes32", "bytes"], + [await this.sessionDigest(sessionData), userOp.signature] + ), + ] + ); + + const signatureWithModuleAddress = defaultAbiCoder.encode( + ["bytes", "address"], + [paddedSig, this.sessionKeyManager.address] + ); + userOp.signature = signatureWithModuleAddress; + + return userOp; + } +} + +export class HybridSKMBatchCallUtils extends HybridSKMUtils { + // eslint-disable-next-line no-useless-constructor + constructor( + entryPoint: EntryPoint, + sessionKeyManager: SessionKeyManagerHybrid, + ecdsaModule: EcdsaOwnershipRegistryModule + ) { + super(entryPoint, sessionKeyManager, ecdsaModule); + } + + async makeEcdsaSessionKeySignedUserOp( + userOpSender: string, + executionCallParams: ExecutionCallParams[], + sessionKey: Signer, + sessionEnableDataList: BytesLike[], + sessionEnableSignatureList: BytesLike[], + sessionInfos: BytesLike[], + options?: { + preVerificationGas?: number; + } + ): Promise { + const callData = SmartAccount__factory.createInterface().encodeFunctionData( + "executeBatch", + [ + executionCallParams.map(({ to }) => to), + executionCallParams.map(({ value }) => value), + executionCallParams.map(({ calldata }) => calldata), + ] + ); + + const userOp = await fillAndSign( + { + sender: userOpSender, + callData, + ...options, + }, + sessionKey, + this.entryPoint, + "nonce", + true + ); + + const paddedSig = defaultAbiCoder.encode( + ["bytes[]", "bytes[]", "bytes[]", "bytes"], + [ + sessionEnableDataList, + sessionEnableSignatureList, + sessionInfos, + userOp.signature, + ] + ); + + const signatureWithModuleAddress = defaultAbiCoder.encode( + ["bytes", "address"], + [paddedSig, this.sessionKeyManager.address] + ); + userOp.signature = signatureWithModuleAddress; + + return userOp; + } + + makeSessionEnableSessionInfo( + sessionEnableDataIndex: number, + sessionKeyIndex: number, + sessionData: ISessionKeyManagerModuleHybrid.SessionDataStruct, + callSpecificData: BytesLike + ): BytesLike { + return solidityPack( + ["uint8", "uint8", "uint8", "uint48", "uint48", "address", "bytes"], + [ + TRANSACTION_MODE.ENABLE_AND_USE, + sessionEnableDataIndex, + sessionKeyIndex, + sessionData.validUntil, + sessionData.validAfter, + sessionData.sessionValidationModule, + defaultAbiCoder.encode( + ["bytes", "bytes"], + [sessionData.sessionKeyData, callSpecificData] + ), + ] + ); + } + + async makePreEnabledSessionInfo( + sessionData: ISessionKeyManagerModuleHybrid.SessionDataStruct, + callSpecificData: BytesLike + ): Promise { + return solidityPack( + ["uint8", "bytes32", "bytes"], + [ + TRANSACTION_MODE.PRE_ENABLED, + await this.sessionDigest(sessionData), + defaultAbiCoder.encode(["bytes"], [callSpecificData]), + ] + ); + } +} diff --git a/walletUtils.js b/walletUtils.js index 06313aa6..6266bff7 100644 --- a/walletUtils.js +++ b/walletUtils.js @@ -2,7 +2,9 @@ const ethers = require("ethers"); const fs = require("fs"); -const mnemonic = fs.readFileSync(".secret").toString().trim(); +const mnemonic = fs.existsSync(".secret") + ? fs.readFileSync(".secret").toString().trim() + : "test test test test test test test test test test test junk"; const makeKeyList = ( num = 5, diff --git a/yarn.lock b/yarn.lock index b3dc2a1d..16229abe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -897,6 +897,14 @@ deep-eql "^4.0.1" ordinal "^1.0.3" +"@nomicfoundation/hardhat-ethers@^3.0.0": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.5.tgz#0422c2123dec7c42e7fb2be8e1691f1d9708db56" + integrity sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw== + dependencies: + debug "^4.1.1" + lodash.isequal "^4.5.0" + "@nomicfoundation/hardhat-foundry@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-foundry/-/hardhat-foundry-1.1.1.tgz#db72b1f33f9cfaecc27e67f69ad436f8710162d6" @@ -904,6 +912,33 @@ dependencies: chalk "^2.4.2" +"@nomicfoundation/hardhat-network-helpers@^1.0.0": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.10.tgz#c61042ceb104fdd6c10017859fdef6529c1d6585" + integrity sha512-R35/BMBlx7tWN5V6d/8/19QCwEmIdbnA4ZrsuXgvs8i2qFx5i7h6mH5pBS4Pwi4WigLH+upl6faYusrNPuzMrQ== + dependencies: + ethereumjs-util "^7.1.4" + +"@nomicfoundation/hardhat-toolbox@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-4.0.0.tgz#eb1f619218dd1414fa161dfec92d3e5e53a2f407" + integrity sha512-jhcWHp0aHaL0aDYj8IJl80v4SZXWMS1A2XxXa1CA6pBiFfJKuZinCkO6wb+POAt0LIfXB3gA3AgdcOccrcwBwA== + +"@nomicfoundation/hardhat-verify@^2.0.0": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.4.tgz#65b86787fc7b47d38fd941862266065c7eb9bca4" + integrity sha512-B8ZjhOrmbbRWqJi65jvQblzjsfYktjqj2vmOm+oc2Vu8drZbT2cjeSCRHZKbS7lOtfW78aJZSFvw+zRLCiABJA== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "^5.0.2" + cbor "^8.1.0" + chalk "^2.4.2" + debug "^4.1.1" + lodash.clonedeep "^4.5.0" + semver "^6.3.0" + table "^6.8.0" + undici "^5.14.0" + "@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz#4c858096b1c17fe58a474fe81b46815f93645c15" @@ -1206,6 +1241,14 @@ lodash "^4.17.15" ts-essentials "^7.0.1" +"@typechain/ethers-v6@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz#42fe214a19a8b687086c93189b301e2b878797ea" + integrity sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + "@typechain/hardhat@^6.1.5": version "6.1.6" resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.6.tgz#1a749eb35e5054c80df531cf440819cb347c62ea" @@ -1218,7 +1261,7 @@ resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-7.2.5.tgz#db2cf364c159fb1f12be6cd3549f56387eaf8d73" integrity sha512-/2B0nQF4UdupuxeKTJA2+Rj1D+uDemo6P4kMwKCpbfpnzeVaWSELTsAw4Lxn3VJD6APtRrZOCuYo+4nHUQfTfg== -"@types/bn.js@*": +"@types/bn.js@*", "@types/bn.js@^5.1.0": version "5.1.5" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== @@ -1232,13 +1275,6 @@ dependencies: "@types/node" "*" -"@types/bn.js@^5.1.0": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" - integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== - dependencies: - "@types/node" "*" - "@types/chai-as-promised@^7.1.3": version "7.1.8" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" @@ -3043,7 +3079,7 @@ ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.3, ethereum ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@^5.7.0, ethers@^5.7.1, ethers@^5.7.2: +ethers@^5.6.1, ethers@^5.7.0, ethers@^5.7.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3627,6 +3663,15 @@ hardhat-gas-reporter@^1.0.8: eth-gas-reporter "^0.2.25" sha1 "^1.1.1" +hardhat-tracer@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/hardhat-tracer/-/hardhat-tracer-2.8.0.tgz#ccb0156ce425f7a7d7a58171c9db2cc9da56d734" + integrity sha512-fBHhs7tdpUUndVfgupYc/TJKPgFv0gklICyAxeGZmtvpWBUZi2lXPsnCCeEVgT+9YjjumxPXZw8qZarO+2qM7w== + dependencies: + chalk "^4.1.2" + debug "^4.3.4" + ethers "^5.6.1" + hardhat@^2.9.7: version "2.19.4" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.19.4.tgz#5112c30295d8be2e18e55d847373c50483ed1902" @@ -4430,6 +4475,16 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"