diff --git a/src/Kernel.sol b/src/Kernel.sol index 6275f5cb..9687c120 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -76,6 +76,22 @@ contract Kernel is EIP712, Compatibility, KernelStorage { } } + /// @notice Executes a function call to an external contract with delegatecall + /// @param to The address of the target contract + /// @param data The call data to be sent + function executeDelegateCall(address to, bytes memory data) external payable { + if (msg.sender != address(entryPoint) && msg.sender != address(this) && !_checkCaller()) { + revert NotAuthorizedCaller(); + } + assembly { + let success := delegatecall(gas(), to, add(data, 0x20), mload(data), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch success + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + /// @notice Executes a function call to an external contract batched /// @param calls The calls to be executed, in order /// @dev operation deprecated param, use executeBatch for batched transaction @@ -235,29 +251,51 @@ contract Kernel is EIP712, Compatibility, KernelStorage { } } - function validateSignature(bytes32 hash, bytes calldata signature) public view returns(ValidationData) { + function validateSignature(bytes32 hash, bytes calldata signature) public view returns (ValidationData) { return _validateSignature(hash, signature); } + function _domainSeparator() internal view override returns (bytes32) { + // Obtain the name and version from the _domainNameAndVersion function. + (string memory _name, string memory _version) = _domainNameAndVersion(); + bytes32 nameHash = keccak256(bytes(_name)); + bytes32 versionHash = keccak256(bytes(_version)); + + // Use the proxy address for the EIP-712 domain separator. + address proxyAddress = address(this); + + // Construct the domain separator with name, version, chainId, and proxy address. + bytes32 typeHash = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, proxyAddress)); + } + /// @notice Checks if a signature is valid /// @dev This function checks if a signature is valid based on the hash of the data signed. /// @param hash The hash of the data that was signed /// @param signature The signature to be validated /// @return The magic value 0x1626ba7e if the signature is valid, otherwise returns 0xffffffff. function isValidSignature(bytes32 hash, bytes calldata signature) public view returns (bytes4) { - ValidationData validationData = validateSignature(hash, signature); + // Include the proxy address in the domain separator + bytes32 domainSeparator = _domainSeparator(); + + // Recreate the signed message hash with the correct domain separator + bytes32 signedMessageHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, hash)); + + ValidationData validationData = validateSignature(signedMessageHash, signature); (ValidAfter validAfter, ValidUntil validUntil, address result) = parseValidationData(validationData); - if (ValidAfter.unwrap(validAfter) > block.timestamp) { - return 0xffffffff; - } - if (ValidUntil.unwrap(validUntil) < block.timestamp) { - return 0xffffffff; - } - if (result != address(0)) { + + // Check if the signature is valid within the specified time frame and the result is successful + if ( + ValidAfter.unwrap(validAfter) <= block.timestamp && ValidUntil.unwrap(validUntil) >= block.timestamp + && result == address(0) + ) { + // If all checks pass, return the ERC1271 magic value for a valid signature + return 0x1626ba7e; + } else { + // If any check fails, return the failure magic value return 0xffffffff; } - - return 0x1626ba7e; } function _checkCaller() internal view returns (bool) { diff --git a/src/common/Constants.sol b/src/common/Constants.sol index e65d4328..e85dc7f3 100644 --- a/src/common/Constants.sol +++ b/src/common/Constants.sol @@ -4,7 +4,7 @@ import {ValidationData} from "./Types.sol"; // constants for kernel metadata string constant KERNEL_NAME = "Kernel"; -string constant KERNEL_VERSION = "0.2.2"; +string constant KERNEL_VERSION = "0.2.3"; // ERC4337 constants uint256 constant SIG_VALIDATION_FAILED_UINT = 1; diff --git a/src/validator/SessionKeyValidator.sol b/src/validator/SessionKeyValidator.sol index 2bbc40be..db2d3e02 100644 --- a/src/validator/SessionKeyValidator.sol +++ b/src/validator/SessionKeyValidator.sol @@ -30,11 +30,9 @@ contract SessionKeyValidator is IKernelValidator { } function invalidateNonce(uint128 nonce) public { - require( - nonce > nonces[msg.sender].invalidNonce && nonce <= nonces[msg.sender].lastNonce, - "SessionKeyValidator: invalid nonce" - ); + require(nonce > nonces[msg.sender].invalidNonce, "SessionKeyValidator: invalid nonce"); nonces[msg.sender].invalidNonce = nonce; + nonces[msg.sender].lastNonce = nonce; } function disable(bytes calldata _data) external payable { @@ -113,7 +111,14 @@ contract SessionKeyValidator is IKernelValidator { function _getPermissions(bytes calldata _sig) internal pure returns (Permission[] calldata permissions) { assembly { permissions.offset := add(add(_sig.offset, 0x20), calldataload(_sig.offset)) - permissions.length := calldataload(permissions.offset) + permissions.length := calldataload(add(_sig.offset, calldataload(_sig.offset))) + } + } + + function _getProofs(bytes calldata _sig) internal pure returns (bytes32[][] calldata proofs) { + assembly { + proofs.length := calldataload(add(_sig.offset, calldataload(add(_sig.offset, 0x20)))) + proofs.offset := add(add(_sig.offset, 0x20), calldataload(add(_sig.offset, 0x20))) } } @@ -152,8 +157,7 @@ contract SessionKeyValidator is IKernelValidator { if (bytes4(callData[0:4]) == Kernel.execute.selector) { (Permission calldata permission, bytes32[] calldata merkleProof) = _getPermission(userOp.signature[85:]); require( - address(bytes20(userOp.callData[16:36])) == permission.target, - "SessionKeyValidator: target mismatch" + address(bytes20(userOp.callData[16:36])) == permission.target, "SessionKeyValidator: target mismatch" ); require( uint256(bytes32(userOp.callData[36:68])) <= permission.valueLimit, @@ -178,10 +182,9 @@ contract SessionKeyValidator is IKernelValidator { return _verifyUserOpHash(sessionKey, validAfter, session.validUntil); } else if (bytes4(callData[0:4]) == Kernel.executeBatch.selector) { Permission[] calldata permissions = _getPermissions(userOp.signature[85:]); - (, bytes32[] memory merkleProof, bool[] memory flags, uint256[] memory index) = - abi.decode(userOp.signature[85:], (Permission[], bytes32[], bool[], uint256[])); - (bytes32[] memory leaves, ValidAfter validAfter) = _verifyParams(sessionKey, callData, permissions, index); - if (!MerkleProofLib.verifyMultiProof(merkleProof, session.merkleRoot, leaves, flags)) { + bytes32[][] calldata merkleProof = _getProofs(userOp.signature[85:]); + (ValidAfter validAfter, bool verifyFailed) = _verifyParams(sessionKey, callData, permissions, merkleProof); + if (verifyFailed) { return SIG_VALIDATION_FAILED; } return _verifyUserOpHash(sessionKey, validAfter, session.validUntil); @@ -224,23 +227,20 @@ contract SessionKeyValidator is IKernelValidator { address sessionKey, bytes calldata callData, Permission[] calldata _permissions, - uint256[] memory index - ) internal returns (bytes32[] memory leaves, ValidAfter maxValidAfter) { + bytes32[][] calldata _merkleProof + ) internal returns (ValidAfter maxValidAfter, bool verifyFailed) { Call[] calldata calls; assembly { calls.offset := add(add(callData.offset, 0x24), calldataload(add(callData.offset, 4))) calls.length := calldataload(add(add(callData.offset, 4), calldataload(add(callData.offset, 4)))) } uint256 i = 0; - leaves = _generateLeaves(index); SessionData storage session = sessionData[sessionKey][msg.sender]; maxValidAfter = session.validAfter; for (i = 0; i < calls.length; i++) { Call calldata call = calls[i]; Permission calldata permission = _permissions[i]; - require( - call.to == permission.target, "SessionKeyValidator: target mismatch" - ); + require(call.to == permission.target, "SessionKeyValidator: target mismatch"); require(uint256(bytes32(call.value)) <= permission.valueLimit, "SessionKeyValidator: value limit exceeded"); require(verifyPermission(call.data, permission), "SessionKeyValidator: permission verification failed"); ValidAfter validAfter = @@ -248,19 +248,10 @@ contract SessionKeyValidator is IKernelValidator { if (ValidAfter.unwrap(validAfter) > ValidAfter.unwrap(maxValidAfter)) { maxValidAfter = validAfter; } - leaves[index[i]] = keccak256(abi.encode(permission)); - } - } - - function _generateLeaves(uint256[] memory _indexes) internal pure returns (bytes32[] memory leaves) { - uint256 maxIndex; - uint256 i = 0; - for (i = 0; i < _indexes.length; i++) { - if (_indexes[i] > maxIndex) { - maxIndex = _indexes[i]; + if (!MerkleProofLib.verify(_merkleProof[i], session.merkleRoot, keccak256(abi.encode(permission)))) { + return (maxValidAfter, true); } } - leaves = new bytes32[](maxIndex + 1); } function verifyPermission(bytes calldata data, Permission calldata permission) internal pure returns (bool) { diff --git a/src/validator/WeightedECDSAValidator.sol b/src/validator/WeightedECDSAValidator.sol index 0f1d9de2..faa4f3bc 100644 --- a/src/validator/WeightedECDSAValidator.sol +++ b/src/validator/WeightedECDSAValidator.sol @@ -127,7 +127,7 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { proposal.status = ProposalStatus.Rejected; } - function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingFunds) + function validateUserOp(UserOperation calldata userOp, bytes32, uint256) external payable returns (ValidationData validationData) @@ -149,7 +149,9 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { for (uint256 i = 0; i < sigCount; i++) { // last sig is for userOpHash verification signer = ECDSA.recover( - _hashTypedData(keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash))), + _hashTypedData( + keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash)) + ), sig[i * 65:(i + 1) * 65] ); vote = voteStatus[callDataAndNonceHash][signer][msg.sender]; @@ -173,11 +175,11 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { } } - function validCaller(address, bytes calldata) external view override returns (bool) { + function validCaller(address, bytes calldata) external pure override returns (bool) { return false; } - function validateSignature(bytes32 hash, bytes calldata signature) external view returns (ValidationData) { + function validateSignature(bytes32, bytes calldata) external pure returns (ValidationData) { return SIG_VALIDATION_FAILED; } } diff --git a/test/foundry/KernelLiteECDSA.t.sol b/test/foundry/KernelLiteECDSA.t.sol index c2864d91..e18f21ff 100644 --- a/test/foundry/KernelLiteECDSA.t.sol +++ b/test/foundry/KernelLiteECDSA.t.sol @@ -102,8 +102,7 @@ contract KernelECDSATest is KernelTestBase { function test_transfer_ownership() external { address newOwner = makeAddr("new owner"); UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector(KernelLiteECDSA.transferOwnership.selector, newOwner) + address(kernel), abi.encodeWithSelector(KernelLiteECDSA.transferOwnership.selector, newOwner) ); op.signature = signUserOp(op); UserOperation[] memory ops = new UserOperation[](1); diff --git a/test/foundry/KernelTestBase.sol b/test/foundry/KernelTestBase.sol index baa2d42d..b4501cdd 100644 --- a/test/foundry/KernelTestBase.sol +++ b/test/foundry/KernelTestBase.sol @@ -14,6 +14,7 @@ import {IKernelValidator} from "src/interfaces/IKernelValidator.sol"; import {Call, ExecutionDetail} from "src/common/Structs.sol"; import {ValidationData, ValidUntil, ValidAfter} from "src/common/Types.sol"; +import {KERNEL_VERSION, KERNEL_NAME} from "src/common/Constants.sol"; import {ERC4337Utils} from "test/foundry/utils/ERC4337Utils.sol"; import {Test} from "forge-std/Test.sol"; @@ -97,7 +98,24 @@ abstract contract KernelTestBase is Test { } } + function test_external_call_execute_delegatecall_success() external { + address[] memory validCallers = getOwners(); + for (uint256 i = 0; i < validCallers.length; i++) { + vm.prank(validCallers[i]); + kernel.executeDelegateCall(validCallers[i], ""); + } + } + function test_external_call_execute_delegatecall_fail() external { + address[] memory validCallers = getOwners(); + for (uint256 i = 0; i < validCallers.length; i++) { + vm.prank(address(uint160(validCallers[i]) + 1)); + vm.expectRevert(); + kernel.executeDelegateCall(validCallers[i], ""); + } + } + + function test_external_call_execute_delegatecall_option_fail() external { address[] memory validCallers = getOwners(); for (uint256 i = 0; i < validCallers.length; i++) { vm.prank(validCallers[i]); @@ -143,8 +161,8 @@ abstract contract KernelTestBase is Test { (bytes1 fields, string memory name, string memory version,, address verifyingContract, bytes32 salt,) = kernel.eip712Domain(); assertEq(fields, bytes1(hex"0f")); - assertEq(name, "Kernel"); - assertEq(version, "0.2.2"); + assertEq(name, KERNEL_NAME); + assertEq(version, KERNEL_VERSION); assertEq(verifyingContract, address(kernel)); assertEq(salt, bytes32(0)); } @@ -177,9 +195,16 @@ abstract contract KernelTestBase is Test { } function test_validate_signature() external { + Kernel kernel2 = Kernel(payable(factory.createAccount(address(kernelImpl), getInitializeData(), 3))); bytes32 hash = keccak256(abi.encodePacked("hello world")); - bytes memory sig = signHash(hash); + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), hash + ) + ); + bytes memory sig = signHash(digest); assertEq(kernel.isValidSignature(hash, sig), Kernel.isValidSignature.selector); + assertEq(kernel2.isValidSignature(hash, sig), bytes4(0xffffffff)); } function test_fail_validate_wrongsignature() external { @@ -471,7 +496,7 @@ abstract contract KernelTestBase is Test { return keccak256( abi.encodePacked( "\x19\x01", - ERC4337Utils._buildDomainSeparator("Kernel", "0.2.2", address(kernel)), + ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), ERC4337Utils.getStructHash(sig, validUntil, validAfter, validator, executor, enableData) ) ); diff --git a/test/foundry/utils/Merkle.sol b/test/foundry/utils/Merkle.sol index 55b49b5a..26730161 100644 --- a/test/foundry/utils/Merkle.sol +++ b/test/foundry/utils/Merkle.sol @@ -8,7 +8,7 @@ function _getRoot(bytes32[] memory data) pure returns (bytes32) { return data[0]; } -function _getProof(bytes32[] memory data, uint256 nodeIndex) pure returns (bytes32[] memory) { +function _getProof(bytes32[] memory data, uint256 nodeIndex, bool wrongProof) pure returns (bytes32[] memory) { require(data.length > 1); bytes32[] memory result = new bytes32[](64); @@ -33,6 +33,9 @@ function _getProof(bytes32[] memory data, uint256 nodeIndex) pure returns (bytes assembly { mstore(result, pos) } + if (wrongProof) { + result[0] = result[0] ^ bytes32(uint256(0x01)); + } return result; } diff --git a/test/foundry/validator/SessionKeyValidator.t.sol b/test/foundry/validator/SessionKeyValidator.t.sol index 1683e5a4..bf831ec2 100644 --- a/test/foundry/validator/SessionKeyValidator.t.sol +++ b/test/foundry/validator/SessionKeyValidator.t.sol @@ -5,6 +5,7 @@ import "src/Kernel.sol"; import "src/interfaces/IKernel.sol"; import "src/validator/ECDSAValidator.sol"; import "src/factory/KernelFactory.sol"; +import {Call} from "src/common/Structs.sol"; // test artifacts import "../mock/TestValidator.sol"; import "../mock/TestExecutor.sol"; @@ -49,16 +50,9 @@ contract SessionKeyValidatorTest is KernelECDSATest { for (uint8 i = 0; i < _length; i++) { callees[i] = new TestCallee(); ParamRule[] memory paramRules = new ParamRule[](2); - paramRules[0] = ParamRule({ - offset: 0, - condition: ParamCondition(i % 6), - param: bytes32(uint256(100)) - }); - paramRules[1] = ParamRule({ - offset: 32, - condition: ParamCondition((i+1) % 6), - param: bytes32(uint256(100)) - }); + paramRules[0] = ParamRule({offset: 0, condition: ParamCondition(i % 6), param: bytes32(uint256(100))}); + paramRules[1] = + ParamRule({offset: 32, condition: ParamCondition((i + 1) % 6), param: bytes32(uint256(100))}); permissions[i] = Permission({ index: i, target: address(callees[i]), @@ -78,22 +72,82 @@ contract SessionKeyValidatorTest is KernelECDSATest { } } - function _generateParam(ParamCondition condition, bool correct) internal pure returns(uint256 param) { - if(condition == ParamCondition.EQUAL) { + function _generateParam(ParamCondition condition, bool correct) internal pure returns (uint256 param) { + if (condition == ParamCondition.EQUAL) { param = correct ? 100 : 101; - } else if(condition == ParamCondition.GREATER_THAN) { - param = correct ? 101 : 100; - } else if(condition == ParamCondition.LESS_THAN) { + } else if (condition == ParamCondition.GREATER_THAN) { + param = correct ? 101 : 100; + } else if (condition == ParamCondition.LESS_THAN) { param = correct ? 99 : 100; - } else if(condition == ParamCondition.NOT_EQUAL) { + } else if (condition == ParamCondition.NOT_EQUAL) { param = correct ? 101 : 100; - } else if(condition == ParamCondition.GREATER_THAN_OR_EQUAL) { + } else if (condition == ParamCondition.GREATER_THAN_OR_EQUAL) { param = correct ? 100 : 99; - } else if(condition == ParamCondition.LESS_THAN_OR_EQUAL) { + } else if (condition == ParamCondition.LESS_THAN_OR_EQUAL) { param = correct ? 100 : 101; } } + function _buildUserOpBatch( + Permission[] memory permissions, + SessionData memory sessionData, + uint256 indexToUse, + uint8 usingPaymasterMode, + bool param1Faulty, + bool param2Faulty + ) internal view returns (UserOperation memory op) { + Call[] memory calls = new Call[](1); + calls[0] = Call({ + to: permissions[indexToUse].target, + value: 0, + data: abi.encodeWithSelector( + permissions[indexToUse].sig, + _generateParam(ParamCondition(indexToUse % 6), !param1Faulty), + _generateParam(ParamCondition((indexToUse + 1) % 6), !param2Faulty) + ) + }); + + op = entryPoint.fillUserOp(address(kernel), abi.encodeWithSelector(IKernel.executeBatch.selector, calls)); + if (usingPaymasterMode != 0) { + // 0 = no paymaster + // 1 = unknown paymaster + // 2 = correct paymaster + op.paymasterAndData = usingPaymasterMode == 1 + ? abi.encodePacked(address(unknownPaymaster)) + : abi.encodePacked(address(paymaster)); + } + bytes memory enableData = abi.encodePacked( + sessionKey, + sessionData.merkleRoot, + sessionData.validAfter, + sessionData.validUntil, + sessionData.paymaster, + sessionData.nonce + ); + bytes32 digest = getTypedDataHash( + IKernel.executeBatch.selector, + ValidAfter.unwrap(sessionData.validAfter), + ValidUntil.unwrap(sessionData.validUntil), + address(sessionKeyValidator), + address(0), + enableData + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, digest); + op.signature = abi.encodePacked( + bytes4(0x00000002), + uint48(ValidAfter.unwrap(sessionData.validAfter)), + uint48(ValidUntil.unwrap(sessionData.validUntil)), + address(sessionKeyValidator), + address(0), + uint256(enableData.length), + enableData, + uint256(65), + r, + s, + v + ); + } + function _buildUserOp( Permission[] memory permissions, SessionData memory sessionData, @@ -110,8 +164,8 @@ contract SessionKeyValidatorTest is KernelECDSATest { 0, abi.encodeWithSelector( permissions[indexToUse].sig, - _generateParam(ParamCondition(indexToUse%6), !param1Faulty), // since EQ - _generateParam(ParamCondition((indexToUse+1)%6), !param2Faulty) // since NOT_EQ + _generateParam(ParamCondition(indexToUse % 6), !param1Faulty), // since EQ + _generateParam(ParamCondition((indexToUse + 1) % 6), !param2Faulty) // since NOT_EQ ), Operation.Call ) @@ -179,19 +233,32 @@ contract SessionKeyValidatorTest is KernelECDSATest { bool faultySig; bool param1Faulty; bool param2Faulty; + bool wrongProof; + } + + struct BatchTestConfig { + uint8 count; } - function test_scenario_non_batch( - TestConfig memory config - ) public { + + function test_scenario_batch(TestConfig memory config, BatchTestConfig memory batchConfig) public { vm.warp(1000); + if (batchConfig.count == 0) { + batchConfig.count = 1; + } + config.runs = 0; + config.interval = 0; + config.validAfter = 0; // TODO: runs not checked with batch vm.assume(config.indexToUse < config.numberOfPermissions && config.numberOfPermissions > 1); - vm.assume(config.validAfter < type(uint32).max && config.interval < type(uint32).max && config.runs < type(uint32).max); + vm.assume( + config.validAfter < type(uint32).max && config.interval < type(uint32).max && config.runs < type(uint32).max + ); config.paymasterMode = config.paymasterMode % 3; config.usingPaymasterMode = config.usingPaymasterMode % 3; - bool shouldFail = (config.usingPaymasterMode < config.paymasterMode) || (1000 < config.validAfter) || config.faultySig || config.param1Faulty || config.param2Faulty; + bool shouldFail = (config.usingPaymasterMode < config.paymasterMode) || (1000 < config.validAfter) + || config.faultySig || config.param1Faulty || config.param2Faulty || config.wrongProof; config.runs = config.runs % 10; config.earlyRun = config.runs == 0 ? 0 : config.earlyRun % config.runs; - if(config.interval == 0 || config.validAfter == 0) { + if (config.interval == 0 || config.validAfter == 0) { config.earlyRun = 0; } if (config.interval > 0) { @@ -200,7 +267,11 @@ contract SessionKeyValidatorTest is KernelECDSATest { vm.assume(config.validAfter < block.timestamp); } // setup permissions - execRule = ExecutionRule({runs: config.runs, validAfter: ValidAfter.wrap(config.validAfter), interval: config.interval}); + execRule = ExecutionRule({ + runs: config.runs, + validAfter: ValidAfter.wrap(config.validAfter), + interval: config.interval + }); Permission[] memory permissions = _setup_permission(config.numberOfPermissions); _buildHashes(permissions); (uint128 lastNonce,) = sessionKeyValidator.nonces(address(kernel)); @@ -209,16 +280,29 @@ contract SessionKeyValidatorTest is KernelECDSATest { validAfter: ValidAfter.wrap(config.validAfter), validUntil: ValidUntil.wrap(0), paymaster: config.paymasterMode == 2 ? address(paymaster) : address(uint160(config.paymasterMode)), - nonce: uint256(lastNonce) + 1//lastNonce + 1 + nonce: uint256(lastNonce) + 1 //lastNonce + 1 }); // now encode data to op - UserOperation memory op = _buildUserOp(permissions, sessionData, config.indexToUse, config.usingPaymasterMode, config.param1Faulty, config.param2Faulty); + UserOperation memory op = _buildUserOpBatch( + permissions, + sessionData, + config.indexToUse, + config.usingPaymasterMode, + config.param1Faulty, + config.param2Faulty + ); + bytes32[][] memory proofs = new bytes32[][](batchConfig.count); + Permission[] memory usingPermission = new Permission[](batchConfig.count); + for (uint256 i = 0; i < batchConfig.count; i++) { + proofs[i] = _getProof(data, config.indexToUse, config.wrongProof); + usingPermission[i] = permissions[config.indexToUse]; + } op.signature = bytes.concat( op.signature, abi.encodePacked( sessionKey, entryPoint.signUserOpHash(vm, config.faultySig ? sessionKeyPriv + 1 : sessionKeyPriv, op), - abi.encode(permissions[config.indexToUse], _getProof(data, config.indexToUse)) + abi.encode(usingPermission, proofs) ) ); @@ -241,7 +325,114 @@ contract SessionKeyValidatorTest is KernelECDSATest { } if (!shouldFail && config.runs > 0) { for (uint256 i = 1; i < config.runs; i++) { - if(config.earlyRun != i) { + if (config.earlyRun != i) { + vm.warp(config.validAfter + config.interval * i); + } else { + vm.warp(config.validAfter + config.interval * i - 1); + } + op.nonce = op.nonce + 1; + op.signature = _getBatchActionSignature(op, permissions, config.indexToUse); + if (config.earlyRun == i) { + vm.expectRevert(); + } + entryPoint.handleOps(ops, beneficiary); + if (config.earlyRun == i) { + vm.warp(config.validAfter + config.interval * i); + entryPoint.handleOps(ops, beneficiary); + } + (ValidAfter updatedValidAfter, uint48 r) = sessionKeyValidator.executionStatus( + keccak256(abi.encodePacked(sessionData.nonce, uint32(config.indexToUse))), address(kernel) + ); + if (config.validAfter > 0 && config.interval > 0) { + assertEq( + uint256(ValidAfter.unwrap(updatedValidAfter)), uint256(config.validAfter + config.interval * i) + ); + } + if (config.runs > 0) { + assertEq(uint256(r), uint256(i + 1)); + } + } + op.nonce = op.nonce + 1; + op.signature = _getBatchActionSignature(op, permissions, config.indexToUse); + vm.expectRevert(); + entryPoint.handleOps(ops, beneficiary); + } + } + + function test_scenario_non_batch(TestConfig memory config) public { + vm.warp(1000); + vm.assume(config.indexToUse < config.numberOfPermissions && config.numberOfPermissions > 1); + vm.assume( + config.validAfter < type(uint32).max && config.interval < type(uint32).max && config.runs < type(uint32).max + ); + config.paymasterMode = config.paymasterMode % 3; + config.usingPaymasterMode = config.usingPaymasterMode % 3; + bool shouldFail = (config.usingPaymasterMode < config.paymasterMode) || (1000 < config.validAfter) + || config.faultySig || config.param1Faulty || config.param2Faulty || config.wrongProof; + config.runs = config.runs % 10; + config.earlyRun = config.runs == 0 ? 0 : config.earlyRun % config.runs; + if (config.interval == 0 || config.validAfter == 0) { + config.earlyRun = 0; + } + if (config.interval > 0) { + vm.assume(config.validAfter > 0 && config.validAfter < block.timestamp); + } else { + vm.assume(config.validAfter < block.timestamp); + } + // setup permissions + execRule = ExecutionRule({ + runs: config.runs, + validAfter: ValidAfter.wrap(config.validAfter), + interval: config.interval + }); + Permission[] memory permissions = _setup_permission(config.numberOfPermissions); + _buildHashes(permissions); + (uint128 lastNonce,) = sessionKeyValidator.nonces(address(kernel)); + SessionData memory sessionData = SessionData({ + merkleRoot: _getRoot(data), + validAfter: ValidAfter.wrap(config.validAfter), + validUntil: ValidUntil.wrap(0), + paymaster: config.paymasterMode == 2 ? address(paymaster) : address(uint160(config.paymasterMode)), + nonce: uint256(lastNonce) + 1 //lastNonce + 1 + }); + // now encode data to op + UserOperation memory op = _buildUserOp( + permissions, + sessionData, + config.indexToUse, + config.usingPaymasterMode, + config.param1Faulty, + config.param2Faulty + ); + op.signature = bytes.concat( + op.signature, + abi.encodePacked( + sessionKey, + entryPoint.signUserOpHash(vm, config.faultySig ? sessionKeyPriv + 1 : sessionKeyPriv, op), + abi.encode(permissions[config.indexToUse], _getProof(data, config.indexToUse, config.wrongProof)) + ) + ); + + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = op; + if (shouldFail) { + vm.expectRevert(); + } + entryPoint.handleOps(ops, beneficiary); + if (config.interval > 0 && config.validAfter > 0 && !shouldFail) { + (ValidAfter updatedValidAfter, uint48 r) = sessionKeyValidator.executionStatus( + keccak256(abi.encodePacked(sessionData.nonce, uint32(config.indexToUse))), address(kernel) + ); + assertEq(uint256(ValidAfter.unwrap(updatedValidAfter)), uint256(config.validAfter)); + if (config.runs > 0) { + assertEq(uint256(r), uint256(1)); + } else { + assertEq(uint256(r), uint256(0)); + } + } + if (!shouldFail && config.runs > 0) { + for (uint256 i = 1; i < config.runs; i++) { + if (config.earlyRun != i) { vm.warp(config.validAfter + config.interval * i); } else { vm.warp(config.validAfter + config.interval * i - 1); @@ -260,9 +451,11 @@ contract SessionKeyValidatorTest is KernelECDSATest { keccak256(abi.encodePacked(sessionData.nonce, uint32(config.indexToUse))), address(kernel) ); if (config.validAfter > 0 && config.interval > 0) { - assertEq(uint256(ValidAfter.unwrap(updatedValidAfter)), uint256(config.validAfter + config.interval * i)); + assertEq( + uint256(ValidAfter.unwrap(updatedValidAfter)), uint256(config.validAfter + config.interval * i) + ); } - if(config.runs > 0) { + if (config.runs > 0) { assertEq(uint256(r), uint256(i + 1)); } } @@ -273,13 +466,34 @@ contract SessionKeyValidatorTest is KernelECDSATest { } } - function _getSingleActionSignature(UserOperation memory _op, Permission[] memory permissions, uint8 indexToUse) internal view returns(bytes memory) { + function _getBatchActionSignature(UserOperation memory _op, Permission[] memory permissions, uint8 indexToUse) + internal + view + returns (bytes memory) + { + Permission[] memory _permissions = new Permission[](1); + _permissions[0] = permissions[indexToUse]; + bytes32[][] memory _proofs = new bytes32[][](1); + _proofs[0] = _getProof(data, indexToUse, false); + return abi.encodePacked( + bytes4(0x00000001), + abi.encodePacked( + sessionKey, entryPoint.signUserOpHash(vm, sessionKeyPriv, _op), abi.encode(_permissions, _proofs) + ) + ); + } + + function _getSingleActionSignature(UserOperation memory _op, Permission[] memory permissions, uint8 indexToUse) + internal + view + returns (bytes memory) + { return abi.encodePacked( bytes4(0x00000001), abi.encodePacked( sessionKey, entryPoint.signUserOpHash(vm, sessionKeyPriv, _op), - abi.encode(permissions[indexToUse], _getProof(data, indexToUse)) + abi.encode(permissions[indexToUse], _getProof(data, indexToUse, false)) ) ); }