Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/validator/KillSwitchValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "src/Kernel.sol";
import {WalletKernelStorage, ExecutionDetail} from "src/abstract/KernelStorage.sol";
import "src/interfaces/IValidator.sol";
import "src/common/Types.sol";
import {KillSwitchAction} from "src/executor/KillSwitchAction.sol";

struct KillSwitchValidatorStorage {
address guardian;
Expand Down Expand Up @@ -66,6 +67,12 @@ contract KillSwitchValidator is IKernelValidator {
// if signature verification has not been failed, return with the result
ValidationData delayedData = packValidationData(pausedUntil, ValidUntil.wrap(0));
return _intersectValidationData(validationData, delayedData);
} else if (bytes4(_userOp.callData[0:4]) == KillSwitchAction.toggleKillSwitch.selector) {
bytes32 hash = ECDSA.toEthSignedMessageHash(_userOpHash);
address recovered = ECDSA.recover(hash, _userOp.signature);
if (validatorStorage.guardian == recovered) {
return packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0));
}
}
}
if (_userOp.signature.length == 71) {
Expand Down
168 changes: 168 additions & 0 deletions test/foundry/validator/KillSwitchValidator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "src/factory/KernelFactory.sol";
import "src/validator/ECDSAValidator.sol";
import "src/Kernel.sol";
import "src/validator/KillSwitchValidator.sol";
import "src/executor/KillSwitchAction.sol";
// test utils
import "forge-std/Test.sol";
import "test/foundry/utils/ERC4337Utils.sol";

using ERC4337Utils for EntryPoint;

contract KillSwitchValidatorTest is KernelTestBase {
KillSwitchValidator killSwitch;
KillSwitchAction action;
address guardian;
uint256 guardianKey;

function setUp() public {
_initialize();
defaultValidator = new ECDSAValidator();
_setAddress();
(guardian, guardianKey) = makeAddrAndKey("guardian");
killSwitch = new KillSwitchValidator();
action = new KillSwitchAction(killSwitch);
}

function test_force_unblock() external {
UserOperation memory op = entryPoint.fillUserOp(
address(kernel), abi.encodeWithSelector(Kernel.execute.selector, owner, 0, "", Operation.Call)
);

op.signature = bytes.concat(bytes4(0), entryPoint.signUserOpHash(vm, ownerKey, op));
UserOperation[] memory ops = new UserOperation[](1);
ops[0] = op;
entryPoint.handleOps(ops, beneficiary);

op = entryPoint.fillUserOp(address(kernel), abi.encodeWithSelector(KillSwitchAction.toggleKillSwitch.selector));
address guardianKeyAddr;
uint256 guardianKeyPriv;
(guardianKeyAddr, guardianKeyPriv) = makeAddrAndKey("guardianKey");
bytes memory enableData = abi.encodePacked(guardianKeyAddr);
{
bytes32 digest = getTypedDataHash(
address(kernel),
KillSwitchAction.toggleKillSwitch.selector,
0,
0,
address(killSwitch),
address(action),
enableData
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, digest);

op.signature = abi.encodePacked(
bytes4(0x00000002),
uint48(0),
uint48(0),
address(killSwitch),
address(action),
uint256(enableData.length),
enableData,
uint256(65),
r,
s,
v
);
}

uint256 pausedUntil = block.timestamp + 1000;

bytes32 hash = entryPoint.getUserOpHash(op);
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
guardianKeyPriv,
ECDSA.toEthSignedMessageHash(keccak256(bytes.concat(bytes6(uint48(pausedUntil)), hash)))
);
bytes memory sig = abi.encodePacked(r, s, v);

op.signature = bytes.concat(op.signature, bytes6(uint48(pausedUntil)), sig);
}

ops[0] = op;
logGas(op);
entryPoint.handleOps(ops, beneficiary);
assertEq(kernel.getDisabledMode(), bytes4(0xffffffff));
assertEq(address(kernel.getDefaultValidator()), address(killSwitch));
op = entryPoint.fillUserOp(address(kernel), abi.encodeWithSelector(KillSwitchAction.toggleKillSwitch.selector));

op.signature = bytes.concat(bytes4(0), entryPoint.signUserOpHash(vm, guardianKeyPriv, op));
ops[0] = op;
entryPoint.handleOps(ops, beneficiary);
assertEq(kernel.getDisabledMode(), bytes4(0));
}

function test_mode_2() external {
UserOperation memory op = entryPoint.fillUserOp(
address(kernel), abi.encodeWithSelector(Kernel.execute.selector, owner, 0, "", Operation.Call)
);

op.signature = bytes.concat(bytes4(0), entryPoint.signUserOpHash(vm, ownerKey, op));
UserOperation[] memory ops = new UserOperation[](1);
ops[0] = op;
entryPoint.handleOps(ops, beneficiary);

op = entryPoint.fillUserOp(address(kernel), abi.encodeWithSelector(KillSwitchAction.toggleKillSwitch.selector));
address guardianKeyAddr;
uint256 guardianKeyPriv;
(guardianKeyAddr, guardianKeyPriv) = makeAddrAndKey("guardianKey");
bytes memory enableData = abi.encodePacked(guardianKeyAddr);
{
bytes32 digest = getTypedDataHash(
address(kernel),
KillSwitchAction.toggleKillSwitch.selector,
0,
0,
address(killSwitch),
address(action),
enableData
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, digest);

op.signature = abi.encodePacked(
bytes4(0x00000002),
uint48(0),
uint48(0),
address(killSwitch),
address(action),
uint256(enableData.length),
enableData,
uint256(65),
r,
s,
v
);
}

uint256 pausedUntil = block.timestamp + 1000;

bytes32 hash = entryPoint.getUserOpHash(op);
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
guardianKeyPriv,
ECDSA.toEthSignedMessageHash(keccak256(bytes.concat(bytes6(uint48(pausedUntil)), hash)))
);
bytes memory sig = abi.encodePacked(r, s, v);

op.signature = bytes.concat(op.signature, bytes6(uint48(pausedUntil)), sig);
}

ops[0] = op;
logGas(op);
entryPoint.handleOps(ops, beneficiary);
assertEq(address(kernel.getDefaultValidator()), address(killSwitch));
op = entryPoint.fillUserOp(
address(kernel), abi.encodeWithSelector(Kernel.execute.selector, owner, 0, "", Operation.Call)
);

op.signature = bytes.concat(bytes4(0), entryPoint.signUserOpHash(vm, ownerKey, op));
ops[0] = op;
vm.expectRevert();
entryPoint.handleOps(ops, beneficiary); // should revert because kill switch is active
vm.warp(pausedUntil + 1);
entryPoint.handleOps(ops, beneficiary); // should not revert because pausedUntil has been passed
}
}