From 252f5e3acb537a25ba6a2407a0f553329120b732 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 22:30:37 +0900 Subject: [PATCH 01/10] factory merged with erc1967 minimal factory --- src/factory/KernelFactory.sol | 10 ++++------ test/foundry/Kernel.t.sol | 4 +--- test/foundry/KernelExecution.t.sol | 4 +--- test/foundry/KernelMultiOwned.t.sol | 4 +--- test/foundry/KillSwitch.t.sol | 4 +--- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/factory/KernelFactory.sol b/src/factory/KernelFactory.sol index dd020f52..85365e02 100644 --- a/src/factory/KernelFactory.sol +++ b/src/factory/KernelFactory.sol @@ -7,15 +7,13 @@ import "openzeppelin-contracts/contracts/utils/Create2.sol"; import "src/Kernel.sol"; import "src/validator/ECDSAValidator.sol"; -contract KernelFactory { - AdminLessERC1967Factory public immutable erc1967factory; +contract KernelFactory is AdminLessERC1967Factory{ Kernel public immutable kernelTemplate; IEntryPoint public immutable entryPoint; event AccountCreated(address indexed account, address indexed validator, bytes data, uint256 index); - constructor(AdminLessERC1967Factory _erc1967factory, IEntryPoint _entryPoint) { - erc1967factory = _erc1967factory; + constructor(IEntryPoint _entryPoint) { entryPoint = _entryPoint; kernelTemplate = new Kernel(_entryPoint); } @@ -27,7 +25,7 @@ contract KernelFactory { { bytes memory initData = abi.encodeWithSelector(KernelStorage.initialize.selector, _validator, _data); bytes32 salt = bytes32(uint256(keccak256(abi.encodePacked(_validator, _data, _index))) & type(uint96).max); - proxy = erc1967factory.deployDeterministicAndCall(address(kernelTemplate), salt, initData); + proxy = this.deployDeterministicAndCall(address(kernelTemplate), salt, initData); } function getAccountAddress(IKernelValidator _validator, bytes calldata _data, uint256 _index) @@ -36,6 +34,6 @@ contract KernelFactory { returns (address) { bytes32 salt = bytes32(uint256(keccak256(abi.encodePacked(_validator, _data, _index))) & type(uint96).max); - return erc1967factory.predictDeterministicAddress(salt); + return predictDeterministicAddress(salt); } } diff --git a/test/foundry/Kernel.t.sol b/test/foundry/Kernel.t.sol index dd6e8781..aac78853 100644 --- a/test/foundry/Kernel.t.sol +++ b/test/foundry/Kernel.t.sol @@ -18,7 +18,6 @@ using ERC4337Utils for EntryPoint; contract KernelTest is Test { Kernel kernel; - AdminLessERC1967Factory erc1967factory; KernelFactory factory; ECDSAKernelFactory ecdsaFactory; EntryPoint entryPoint; @@ -29,9 +28,8 @@ contract KernelTest is Test { function setUp() public { (owner, ownerKey) = makeAddrAndKey("owner"); - erc1967factory = new AdminLessERC1967Factory(); entryPoint = new EntryPoint(); - factory = new KernelFactory(erc1967factory, entryPoint); + factory = new KernelFactory(entryPoint); validator = new ECDSAValidator(); ecdsaFactory = new ECDSAKernelFactory(factory, validator); diff --git a/test/foundry/KernelExecution.t.sol b/test/foundry/KernelExecution.t.sol index 15fc862b..80dd0df1 100644 --- a/test/foundry/KernelExecution.t.sol +++ b/test/foundry/KernelExecution.t.sol @@ -21,7 +21,6 @@ using ERC4337Utils for EntryPoint; contract KernelExecutionTest is Test { Kernel kernel; - AdminLessERC1967Factory erc1967factory; KernelFactory factory; ECDSAKernelFactory ecdsaFactory; EntryPoint entryPoint; @@ -33,8 +32,7 @@ contract KernelExecutionTest is Test { function setUp() public { (owner, ownerKey) = makeAddrAndKey("owner"); entryPoint = new EntryPoint(); - erc1967factory = new AdminLessERC1967Factory(); - factory = new KernelFactory(erc1967factory, entryPoint); + factory = new KernelFactory(entryPoint); validator = new ECDSAValidator(); ecdsaFactory = new ECDSAKernelFactory(factory, validator); diff --git a/test/foundry/KernelMultiOwned.t.sol b/test/foundry/KernelMultiOwned.t.sol index 3841047e..df517fb5 100644 --- a/test/foundry/KernelMultiOwned.t.sol +++ b/test/foundry/KernelMultiOwned.t.sol @@ -16,7 +16,6 @@ using ERC4337Utils for EntryPoint; contract KernelTest is Test { Kernel kernel; - AdminLessERC1967Factory erc1967factory; KernelFactory factory; MultiECDSAKernelFactory ecdsaFactory; EntryPoint entryPoint; @@ -27,9 +26,8 @@ contract KernelTest is Test { function setUp() public { (owner, ownerKey) = makeAddrAndKey("owner"); - erc1967factory = new AdminLessERC1967Factory(); entryPoint = new EntryPoint(); - factory = new KernelFactory(erc1967factory, entryPoint); + factory = new KernelFactory(entryPoint); validator = new MultiECDSAValidator(); ecdsaFactory = new MultiECDSAKernelFactory(factory, validator, entryPoint); diff --git a/test/foundry/KillSwitch.t.sol b/test/foundry/KillSwitch.t.sol index 44922b4e..23d771ff 100644 --- a/test/foundry/KillSwitch.t.sol +++ b/test/foundry/KillSwitch.t.sol @@ -16,7 +16,6 @@ using ERC4337Utils for EntryPoint; contract KernelExecutionTest is Test { Kernel kernel; - AdminLessERC1967Factory erc1967factory; KernelFactory factory; ECDSAKernelFactory ecdsaFactory; EntryPoint entryPoint; @@ -30,9 +29,8 @@ contract KernelExecutionTest is Test { function setUp() public { (owner, ownerKey) = makeAddrAndKey("owner"); - erc1967factory = new AdminLessERC1967Factory(); entryPoint = new EntryPoint(); - factory = new KernelFactory(erc1967factory, entryPoint); + factory = new KernelFactory(entryPoint); validator = new ECDSAValidator(); ecdsaFactory = new ECDSAKernelFactory(factory, validator); From 0f8402dff3fecdf151ff9f344a93e55630d325d8 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:31:43 +0900 Subject: [PATCH 02/10] Kernel Storage to abstract the initData setting --- src/abstract/KernelStorage.sol | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/abstract/KernelStorage.sol b/src/abstract/KernelStorage.sol index f03fbc32..6e4c6dac 100644 --- a/src/abstract/KernelStorage.sol +++ b/src/abstract/KernelStorage.sol @@ -60,13 +60,7 @@ contract KernelStorage { // Function to initialize the wallet kernel function initialize(IKernelValidator _defaultValidator, bytes calldata _data) external payable { - WalletKernelStorage storage ws = getKernelStorage(); - if (address(ws.defaultValidator) != address(0)) { - revert AlreadyInitialized(); - } - ws.defaultValidator = _defaultValidator; - emit DefaultValidatorChanged(address(0), address(_defaultValidator)); - _defaultValidator.enable(_data); + _setInitialData(_defaultValidator, _data); } // Function to get the wallet kernel storage @@ -161,4 +155,16 @@ contract KernelStorage { getKernelStorage().disabledMode = _disableFlag; getKernelStorage().lastDisabledTime = uint48(block.timestamp); } + + function _setInitialData(IKernelValidator _defaultValidator, bytes calldata _data) internal virtual { + address validator; + assembly { + validator := shr(80, sload(KERNEL_STORAGE_SLOT_1)) + } + if (address(validator) != address(0)) { + revert AlreadyInitialized(); + } + getKernelStorage().defaultValidator = _defaultValidator; + _defaultValidator.enable(_data); + } } From 89f10b60a6f06e07c236429583dab92c6477a7eb Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:32:06 +0900 Subject: [PATCH 03/10] only one factory to decrease deployment gas --- src/factory/ECDSAKernelFactory.sol | 25 ------- src/factory/KernelFactory.sol | 28 ++++---- src/factory/MultiECDSAKernelFactory.sol | 88 ------------------------- 3 files changed, 15 insertions(+), 126 deletions(-) delete mode 100644 src/factory/ECDSAKernelFactory.sol delete mode 100644 src/factory/MultiECDSAKernelFactory.sol diff --git a/src/factory/ECDSAKernelFactory.sol b/src/factory/ECDSAKernelFactory.sol deleted file mode 100644 index 0ada474c..00000000 --- a/src/factory/ECDSAKernelFactory.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.18; - -import "./KernelFactory.sol"; -import "src/validator/ECDSAValidator.sol"; - -contract ECDSAKernelFactory { - KernelFactory public immutable singletonFactory; - ECDSAValidator public immutable validator; - - constructor(KernelFactory _singletonFactory, ECDSAValidator _validator) { - singletonFactory = _singletonFactory; - validator = _validator; - } - - function createAccount(address _owner, uint256 _index) external returns (address proxy) { - bytes memory data = abi.encodePacked(_owner); - proxy = singletonFactory.createAccount(validator, data, _index); - } - - function getAccountAddress(address _owner, uint256 _index) public view returns (address) { - bytes memory data = abi.encodePacked(_owner); - return singletonFactory.getAccountAddress(validator, data, _index); - } -} diff --git a/src/factory/KernelFactory.sol b/src/factory/KernelFactory.sol index 85365e02..574c2d67 100644 --- a/src/factory/KernelFactory.sol +++ b/src/factory/KernelFactory.sol @@ -6,34 +6,36 @@ import "./AdminLessERC1967Factory.sol"; import "openzeppelin-contracts/contracts/utils/Create2.sol"; import "src/Kernel.sol"; import "src/validator/ECDSAValidator.sol"; +import "solady/auth/Ownable.sol"; -contract KernelFactory is AdminLessERC1967Factory{ - Kernel public immutable kernelTemplate; - IEntryPoint public immutable entryPoint; +contract KernelFactory is AdminLessERC1967Factory, Ownable{ - event AccountCreated(address indexed account, address indexed validator, bytes data, uint256 index); + mapping(address => bool) public isAllowedImplementation; - constructor(IEntryPoint _entryPoint) { - entryPoint = _entryPoint; - kernelTemplate = new Kernel(_entryPoint); + constructor(address _owner) { + _initializeOwner(_owner); } - function createAccount(IKernelValidator _validator, bytes calldata _data, uint256 _index) + function setImplementation(address _implementation, bool _allow) external onlyOwner { + isAllowedImplementation[_implementation] = _allow; + } + + function createAccount(address _implementation, bytes calldata _data, uint256 _index) external payable returns (address proxy) { - bytes memory initData = abi.encodeWithSelector(KernelStorage.initialize.selector, _validator, _data); - bytes32 salt = bytes32(uint256(keccak256(abi.encodePacked(_validator, _data, _index))) & type(uint96).max); - proxy = this.deployDeterministicAndCall(address(kernelTemplate), salt, initData); + require(isAllowedImplementation[_implementation], "KernelFactory: implementation not allowed"); + bytes32 salt = bytes32(uint256(keccak256(abi.encodePacked(_data, _index))) & type(uint96).max); + proxy = this.deployDeterministicAndCall(_implementation, salt, _data); } - function getAccountAddress(IKernelValidator _validator, bytes calldata _data, uint256 _index) + function getAccountAddress(bytes calldata _data, uint256 _index) public view returns (address) { - bytes32 salt = bytes32(uint256(keccak256(abi.encodePacked(_validator, _data, _index))) & type(uint96).max); + bytes32 salt = bytes32(uint256(keccak256(abi.encodePacked(_data, _index))) & type(uint96).max); return predictDeterministicAddress(salt); } } diff --git a/src/factory/MultiECDSAKernelFactory.sol b/src/factory/MultiECDSAKernelFactory.sol deleted file mode 100644 index a7b9d6ce..00000000 --- a/src/factory/MultiECDSAKernelFactory.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.18; - -import "./KernelFactory.sol"; -import "src/validator/MultiECDSAValidator.sol"; -import "src/interfaces/IAddressBook.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract MultiECDSAKernelFactory is IAddressBook, Ownable { - KernelFactory public immutable singletonFactory; - MultiECDSAValidator public immutable validator; - IEntryPoint public immutable entryPoint; - - address[] public owners; - - constructor(KernelFactory _singletonFactory, MultiECDSAValidator _validator, IEntryPoint _entryPoint) { - singletonFactory = _singletonFactory; - validator = _validator; - entryPoint = _entryPoint; - } - - function setOwners(address[] calldata _owners) external onlyOwner { - owners = _owners; - } - - function getOwners() external view override returns (address[] memory) { - return owners; - } - - function createAccount(uint256 _index) external returns (address proxy) { - bytes memory data = abi.encodePacked(address(this)); - proxy = singletonFactory.createAccount(validator, data, _index); - } - - function getAccountAddress(uint256 _index) public view returns (address) { - bytes memory data = abi.encodePacked(address(this)); - return singletonFactory.getAccountAddress(validator, data, _index); - } - - /** - * add a deposit for this factory, used for paying for transaction fees - */ - function deposit() public payable { - entryPoint.depositTo{value: msg.value}(address(this)); - } - - /** - * withdraw value from the deposit - * @param withdrawAddress target to send to - * @param amount to withdraw - */ - function withdrawTo(address payable withdrawAddress, uint256 amount) public onlyOwner { - entryPoint.withdrawTo(withdrawAddress, amount); - } - /** - * add stake for this factory. - * This method can also carry eth value to add to the current stake. - * @param unstakeDelaySec - the unstake delay for this factory. Can only be increased. - */ - - function addStake(uint32 unstakeDelaySec) external payable onlyOwner { - entryPoint.addStake{value: msg.value}(unstakeDelaySec); - } - - /** - * return current factory's deposit on the entryPoint. - */ - function getDeposit() public view returns (uint256) { - return entryPoint.balanceOf(address(this)); - } - - /** - * unlock the stake, in order to withdraw it. - * The factory can't serve requests once unlocked, until it calls addStake again - */ - function unlockStake() external onlyOwner { - entryPoint.unlockStake(); - } - - /** - * withdraw the entire factory's stake. - * stake must be unlocked first (and then wait for the unstakeDelay to be over) - * @param withdrawAddress the address to send withdrawn value. - */ - function withdrawStake(address payable withdrawAddress) external onlyOwner { - entryPoint.withdrawStake(withdrawAddress); - } -} From 10c88de3e505065dcb8ebb0970febb0e87f60d67 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:32:58 +0900 Subject: [PATCH 04/10] test cleanup to properly structure the test --- test/foundry/Kernel.t.sol | 16 +++-- test/foundry/KernelExecution.t.sol | 14 ++-- test/foundry/KernelMultiOwned.t.sol | 107 ---------------------------- 3 files changed, 19 insertions(+), 118 deletions(-) diff --git a/test/foundry/Kernel.t.sol b/test/foundry/Kernel.t.sol index aac78853..217bc816 100644 --- a/test/foundry/Kernel.t.sol +++ b/test/foundry/Kernel.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import "src/factory/AdminLessERC1967Factory.sol"; import "src/factory/KernelFactory.sol"; -import "src/factory/ECDSAKernelFactory.sol"; import "src/Kernel.sol"; import "src/validator/ECDSAValidator.sol"; // test artifacts @@ -18,23 +17,28 @@ using ERC4337Utils for EntryPoint; contract KernelTest is Test { Kernel kernel; + Kernel kernelImpl; KernelFactory factory; - ECDSAKernelFactory ecdsaFactory; EntryPoint entryPoint; ECDSAValidator validator; address owner; uint256 ownerKey; address payable beneficiary; + address factoryOwner; function setUp() public { (owner, ownerKey) = makeAddrAndKey("owner"); + (factoryOwner, ) = makeAddrAndKey("factoryOwner"); entryPoint = new EntryPoint(); - factory = new KernelFactory(entryPoint); + kernelImpl = new Kernel(entryPoint); + factory = new KernelFactory(factoryOwner); + vm.startPrank(factoryOwner); + factory.setImplementation(address(kernelImpl), true); + vm.stopPrank(); validator = new ECDSAValidator(); - ecdsaFactory = new ECDSAKernelFactory(factory, validator); - kernel = Kernel(payable(ecdsaFactory.createAccount(owner, 0))); + kernel = Kernel(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 0)))); vm.deal(address(kernel), 1e30); beneficiary = payable(address(makeAddr("beneficiary"))); } @@ -51,7 +55,7 @@ contract KernelTest is Test { } function test_validate_signature() external { - Kernel kernel2 = Kernel(payable(address(ecdsaFactory.createAccount(owner, 1)))); + Kernel kernel2 = Kernel(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 1)))); bytes32 hash = keccak256(abi.encodePacked("hello world")); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); assertEq(kernel2.isValidSignature(hash, abi.encodePacked(r, s, v)), Kernel.isValidSignature.selector); diff --git a/test/foundry/KernelExecution.t.sol b/test/foundry/KernelExecution.t.sol index 80dd0df1..1878aff8 100644 --- a/test/foundry/KernelExecution.t.sol +++ b/test/foundry/KernelExecution.t.sol @@ -5,7 +5,6 @@ import "src/factory/AdminLessERC1967Factory.sol"; import "src/Kernel.sol"; import "src/validator/ECDSAValidator.sol"; import "src/factory/KernelFactory.sol"; -import "src/factory/ECDSAKernelFactory.sol"; // test artifacts import "src/test/TestValidator.sol"; import "src/test/TestExecutor.sol"; @@ -21,23 +20,28 @@ using ERC4337Utils for EntryPoint; contract KernelExecutionTest is Test { Kernel kernel; + Kernel kernelImpl; KernelFactory factory; - ECDSAKernelFactory ecdsaFactory; EntryPoint entryPoint; ECDSAValidator validator; address owner; uint256 ownerKey; address payable beneficiary; + address factoryOwner; function setUp() public { (owner, ownerKey) = makeAddrAndKey("owner"); + (factoryOwner, ) = makeAddrAndKey("factoryOwner"); entryPoint = new EntryPoint(); - factory = new KernelFactory(entryPoint); + kernelImpl = new Kernel(entryPoint); + factory = new KernelFactory(factoryOwner); + vm.startPrank(factoryOwner); + factory.setImplementation(address(kernelImpl), true); + vm.stopPrank(); validator = new ECDSAValidator(); - ecdsaFactory = new ECDSAKernelFactory(factory, validator); - kernel = Kernel(payable(address(ecdsaFactory.createAccount(owner, 0)))); + kernel = Kernel(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 0)))); vm.deal(address(kernel), 1e30); beneficiary = payable(address(makeAddr("beneficiary"))); } diff --git a/test/foundry/KernelMultiOwned.t.sol b/test/foundry/KernelMultiOwned.t.sol index df517fb5..e69de29b 100644 --- a/test/foundry/KernelMultiOwned.t.sol +++ b/test/foundry/KernelMultiOwned.t.sol @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "src/factory/AdminLessERC1967Factory.sol"; -import "src/factory/KernelFactory.sol"; -import "src/factory/MultiECDSAKernelFactory.sol"; -import "src/Kernel.sol"; -import "src/validator/MultiECDSAValidator.sol"; -// test artifacts -import "src/test/TestValidator.sol"; -// test utils -import "forge-std/Test.sol"; -import {ERC4337Utils} from "./ERC4337Utils.sol"; - -using ERC4337Utils for EntryPoint; - -contract KernelTest is Test { - Kernel kernel; - KernelFactory factory; - MultiECDSAKernelFactory ecdsaFactory; - EntryPoint entryPoint; - MultiECDSAValidator validator; - address owner; - uint256 ownerKey; - address payable beneficiary; - - function setUp() public { - (owner, ownerKey) = makeAddrAndKey("owner"); - entryPoint = new EntryPoint(); - factory = new KernelFactory(entryPoint); - - validator = new MultiECDSAValidator(); - ecdsaFactory = new MultiECDSAKernelFactory(factory, validator, entryPoint); - address[] memory owners = new address[](1); - owners[0] = owner; - ecdsaFactory.setOwners(owners); - - kernel = Kernel(payable(ecdsaFactory.createAccount(0))); - vm.deal(address(kernel), 1e30); - beneficiary = payable(address(makeAddr("beneficiary"))); - } - - function test_initialize_twice() external { - vm.expectRevert(); - kernel.initialize(validator, abi.encodePacked(owner)); - } - - function test_validate_signature() external { - Kernel kernel2 = Kernel(payable(address(ecdsaFactory.createAccount(1)))); - bytes32 hash = keccak256(abi.encodePacked("hello world")); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); - assertEq(kernel2.isValidSignature(hash, abi.encodePacked(r, s, v)), Kernel.isValidSignature.selector); - } - - function test_set_default_validator() external { - TestValidator newValidator = new TestValidator(); - bytes memory empty; - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector(KernelStorage.setDefaultValidator.selector, address(newValidator), empty) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - assertEq(address(KernelStorage(address(kernel)).getDefaultValidator()), address(newValidator)); - } - - function test_disable_mode() external { - bytes memory empty; - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector(KernelStorage.disableMode.selector, bytes4(0x00000001), address(0), empty) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - assertEq(uint256(bytes32(KernelStorage(address(kernel)).getDisabledMode())), 1 << 224); - } - - function test_set_execution() external { - console.log("owner", owner); - TestValidator newValidator = new TestValidator(); - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector( - KernelStorage.setExecution.selector, - bytes4(0xdeadbeef), - address(0xdead), - address(newValidator), - uint48(0), - uint48(0), - bytes("") - ) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - ExecutionDetail memory execution = KernelStorage(address(kernel)).getExecution(bytes4(0xdeadbeef)); - assertEq(execution.executor, address(0xdead)); - assertEq(address(execution.validator), address(newValidator)); - assertEq(uint256(execution.validUntil), uint256(0)); - assertEq(uint256(execution.validAfter), uint256(0)); - } -} From 205c5072bf2bbeac3c41c288898654b9ad952d69 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:33:14 +0900 Subject: [PATCH 05/10] removing killswitch test too --- test/foundry/KillSwitch.t.sol | 182 ---------------------------------- 1 file changed, 182 deletions(-) delete mode 100644 test/foundry/KillSwitch.t.sol diff --git a/test/foundry/KillSwitch.t.sol b/test/foundry/KillSwitch.t.sol deleted file mode 100644 index 23d771ff..00000000 --- a/test/foundry/KillSwitch.t.sol +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "src/factory/AdminLessERC1967Factory.sol"; -import "src/factory/KernelFactory.sol"; -import "src/validator/ECDSAValidator.sol"; -import "src/factory/ECDSAKernelFactory.sol"; -import "src/Kernel.sol"; -import "src/validator/KillSwitchValidator.sol"; -import "src/executor/KillSwitchAction.sol"; -// test utils -import "forge-std/Test.sol"; -import {ERC4337Utils} from "./ERC4337Utils.sol"; - -using ERC4337Utils for EntryPoint; - -contract KernelExecutionTest is Test { - Kernel kernel; - KernelFactory factory; - ECDSAKernelFactory ecdsaFactory; - EntryPoint entryPoint; - ECDSAValidator validator; - - KillSwitchValidator killSwitch; - KillSwitchAction action; - address owner; - uint256 ownerKey; - address payable beneficiary; - - function setUp() public { - (owner, ownerKey) = makeAddrAndKey("owner"); - entryPoint = new EntryPoint(); - factory = new KernelFactory(entryPoint); - - validator = new ECDSAValidator(); - ecdsaFactory = new ECDSAKernelFactory(factory, validator); - - kernel = Kernel(payable(address(ecdsaFactory.createAccount(owner, 0)))); - vm.deal(address(kernel), 1e30); - beneficiary = payable(address(makeAddr("beneficiary"))); - killSwitch = new KillSwitchValidator(); - action = new KillSwitchAction(killSwitch); - } - - 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 - } - - function logGas(UserOperation memory op) internal returns (uint256 used) { - try this.consoleGasUsage(op) { - revert("should revert"); - } catch Error(string memory reason) { - used = abi.decode(bytes(reason), (uint256)); - console.log("validation gas usage :", used); - } - } - - function consoleGasUsage(UserOperation memory op) external { - uint256 gas = gasleft(); - vm.startPrank(address(entryPoint)); - kernel.validateUserOp(op, entryPoint.getUserOpHash(op), 0); - vm.stopPrank(); - revert(string(abi.encodePacked(gas - gasleft()))); - } -} - -// computes the hash of a permit -function getStructHash( - bytes4 sig, - uint48 validUntil, - uint48 validAfter, - address validator, - address executor, - bytes memory enableData -) pure returns (bytes32) { - return keccak256( - abi.encode( - keccak256("ValidatorApproved(bytes4 sig,uint256 validatorData,address executor,bytes enableData)"), - bytes4(sig), - uint256(uint256(uint160(validator)) | (uint256(validAfter) << 160) | (uint256(validUntil) << (48 + 160))), - executor, - keccak256(enableData) - ) - ); -} - -// computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer -function getTypedDataHash( - address sender, - bytes4 sig, - uint48 validUntil, - uint48 validAfter, - address validator, - address executor, - bytes memory enableData -) view returns (bytes32) { - return keccak256( - abi.encodePacked( - "\x19\x01", - _buildDomainSeparator("Kernel", "0.2.1", sender), - getStructHash(sig, validUntil, validAfter, validator, executor, enableData) - ) - ); -} - -function _buildDomainSeparator(string memory name, string memory version, address verifyingContract) - view - returns (bytes32) -{ - bytes32 hashedName = keccak256(bytes(name)); - bytes32 hashedVersion = keccak256(bytes(version)); - bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - return keccak256(abi.encode(typeHash, hashedName, hashedVersion, block.chainid, address(verifyingContract))); -} From f7a21f8ab5bd61ef0bc444f2199cbc4efa250523 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:33:36 +0900 Subject: [PATCH 06/10] KernelLite PoC --- src/KernelLite.sol | 251 ++++++++++++++++++++++++++++++++++ src/lite/KernelLiteECDSA.sol | 49 +++++++ test/foundry/KernelLite.t.sol | 172 +++++++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 src/KernelLite.sol create mode 100644 src/lite/KernelLiteECDSA.sol create mode 100644 test/foundry/KernelLite.t.sol diff --git a/src/KernelLite.sol b/src/KernelLite.sol new file mode 100644 index 00000000..e883d6dc --- /dev/null +++ b/src/KernelLite.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Importing external libraries and contracts +import "solady/utils/EIP712.sol"; +import "solady/utils/ECDSA.sol"; +import "account-abstraction/core/Helpers.sol"; +import "account-abstraction/interfaces/IEntryPoint.sol"; +import {EntryPoint} from "account-abstraction/core/EntryPoint.sol"; +import "./abstract/Compatibility.sol"; +import "./abstract/KernelStorage.sol"; +import "./utils/KernelHelper.sol"; + +enum Operation { + Call, + DelegateCall +} + +bytes32 constant VALIDATOR_APPROVED_STRUCT_HASH = 0x3ce406685c1b3551d706d85a68afdaa49ac4e07b451ad9b8ff8b58c3ee964176; + +/// @title KernelLite +/// @author taek +/// @notice wallet kernel for extensible wallet functionality +abstract contract KernelLite is EIP712, Compatibility, KernelStorage { + string public constant name = "Kernel"; + + string public constant version = "0.2.1"; + + error NotEntryPoint(); + error DisabledMode(); + + /// @dev Sets up the EIP712 and KernelStorage with the provided entry point + constructor(IEntryPoint _entryPoint) KernelStorage(_entryPoint) {} + + function _domainNameAndVersion() internal pure override returns (string memory, string memory) { + return (name, version); + } + + /// @notice Accepts incoming Ether transactions and calls from the EntryPoint contract + /// @dev This function will delegate any call to the appropriate executor based on the function signature. + fallback() external payable { + bytes4 sig = msg.sig; + address executor = getKernelStorage().execution[sig].executor; + if (msg.sender != address(entryPoint) && !_checkCaller()) { + revert NotAuthorizedCaller(); + } + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), executor, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + /// @notice Executes a function call to an external contract + /// @dev The type of operation (call or delegatecall) is specified as an argument. + /// @param to The address of the target contract + /// @param value The amount of Ether to send + /// @param data The call data to be sent + /// @param operation The type of operation (call or delegatecall) + function execute(address to, uint256 value, bytes calldata data, Operation operation) external payable { + if (msg.sender != address(entryPoint) && !_checkCaller()) { + revert NotAuthorizedCaller(); + } + bytes memory callData = data; + if (operation == Operation.DelegateCall) { + assembly { + let success := delegatecall(gas(), to, add(callData, 0x20), mload(callData), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch success + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } else { + assembly { + let success := call(gas(), to, value, add(callData, 0x20), mload(callData), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch success + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + } + + /// @notice Validates a user operation based on its mode + /// @dev This function will validate user operation and be called by EntryPoint + /// @param userOp The user operation to be validated + /// @param userOpHash The hash of the user operation + /// @param missingAccountFunds The funds needed to be reimbursed + /// @return validationData The data used for validation + function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) + external + payable + returns (uint256 validationData) + { + if (msg.sender != address(entryPoint)) { + revert NotEntryPoint(); + } + bytes4 mode = bytes4(userOp.signature[0:4]); // mode == 00..00 use validators + // mode == 0x00000000 use sudo validator + // mode == 0x00000001 use given validator + // mode == 0x00000002 enable validator + UserOperation memory op = userOp; + IKernelValidator validator; + if (mode == 0x00000000) { + // sudo mode (use default validator) + op.signature = userOp.signature[4:]; + + if (missingAccountFunds != 0) { + assembly { + pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) + } + //ignore failure (its EntryPoint's job to verify, not account.) + } + // short circuit here for default validator + return _validateUserOp(userOp, userOpHash, missingAccountFunds); + } + + bytes32 storage_slot_1; + assembly { + storage_slot_1 := sload(KERNEL_STORAGE_SLOT_1) + } + if(mode & (storage_slot_1 << 224) != 0x00000000) { + revert DisabledMode(); + } else if (mode == 0x00000001) { + bytes4 sig = bytes4(userOp.callData[0:4]); + ExecutionDetail storage detail = getKernelStorage().execution[sig]; + validator = detail.validator; + if (address(validator) == address(0)) { + return SIG_VALIDATION_FAILED; + } + op.signature = userOp.signature[4:]; + validationData = (uint256(detail.validAfter) << 160) | (uint256(detail.validUntil) << 208); + } else if (mode == 0x00000002) { + bytes4 sig = bytes4(userOp.callData[0:4]); + // use given validator + // userOp.signature[4:10] = validUntil, + // userOp.signature[10:16] = validAfter, + // userOp.signature[16:36] = validator address, + validator = IKernelValidator(address(bytes20(userOp.signature[16:36]))); + bytes calldata enableData; + bytes calldata remainSig; + (validationData, enableData, remainSig) = _approveValidator(sig, userOp.signature); + validator.enable(enableData); + op.signature = remainSig; + } else { + return SIG_VALIDATION_FAILED; + } + if (missingAccountFunds != 0) { + assembly { + pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) + } + //ignore failure (its EntryPoint's job to verify, not account.) + } + validationData = + _intersectValidationData(validationData, validator.validateUserOp(op, userOpHash, missingAccountFunds)); + return validationData; + } + + function _approveValidator(bytes4 sig, bytes calldata signature) + internal + returns (uint256 validationData, bytes calldata enableData, bytes calldata validationSig) + { + unchecked { + uint256 cursor = 88; + uint256 length = uint256(bytes32(signature[56:88])); // this is enableDataLength + assembly { + enableData.offset := add(signature.offset, cursor) + enableData.length := length + cursor := add(cursor, length) // 88 + enableDataLength + } + length = uint256(bytes32(signature[cursor:cursor + 32])); // this is enableSigLength + assembly { + cursor := add(cursor, 32) + } + bytes32 enableDigest = _hashTypedData( + keccak256( + abi.encode( + VALIDATOR_APPROVED_STRUCT_HASH, + bytes4(sig), + uint256(bytes32(signature[4:36])), + address(bytes20(signature[36:56])), + keccak256(enableData) + ) + ) + ); + validationData = _intersectValidationData( + _validateSignature( + enableDigest, signature[cursor:cursor+length] + ), + uint256(bytes32(signature[4:36])) & 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 + ); + assembly { + cursor := add(cursor, length) + validationSig.offset := add(signature.offset, cursor) + validationSig.length := sub(signature.length, cursor) + } + getKernelStorage().execution[sig] = ExecutionDetail({ + executor: address(bytes20(signature[36:56])), + validUntil: uint48(bytes6(signature[4:10])), + validAfter: uint48(bytes6(signature[10:16])), + validator: IKernelValidator(address(bytes20(signature[16:36]))) + }); + } + } + + /// @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) external view returns (bytes4) { + uint256 validationData = _validateSignature(hash, signature); + ValidationData memory data = _parseValidationData(validationData); + if (data.validAfter > block.timestamp) { + return 0xffffffff; + } + if (data.validUntil < block.timestamp) { + return 0xffffffff; + } + if (data.aggregator != address(0)) { + return 0xffffffff; + } + + return 0x1626ba7e; + } + + function _checkCaller() internal view returns (bool) { + if (_validCaller(msg.sender, msg.data)) { + return true; + } + bytes4 sig = msg.sig; + ExecutionDetail storage detail = getKernelStorage().execution[sig]; + if ( + address(detail.validator) == address(0) || (detail.validUntil != 0 && detail.validUntil < block.timestamp) + || detail.validAfter > block.timestamp + ) { + return false; + } else { + return detail.validator.validCaller(msg.sender, msg.data); + } + } + + function _validateUserOp(UserOperation calldata _op, bytes32 _opHash, uint256 _missingFunds) internal virtual returns(uint256); + + function _validateSignature(bytes32 _hash, bytes calldata _signature) internal view virtual returns(uint256); + + function _validCaller(address _caller, bytes calldata _data) internal view virtual returns (bool); +} diff --git a/src/lite/KernelLiteECDSA.sol b/src/lite/KernelLiteECDSA.sol new file mode 100644 index 00000000..bda918e5 --- /dev/null +++ b/src/lite/KernelLiteECDSA.sol @@ -0,0 +1,49 @@ +pragma solidity ^0.8.0; + +import "src/KernelLite.sol"; + +struct KernelLiteECDSAStorage { + address owner; +} + +contract KernelLiteECDSA is KernelLite { + bytes32 constant private KERNEL_LITE_ECDSA_STORAGE_SLOT = 0xdea7fea882fba743201b2aeb1babf326b8944488db560784858525d123ee7e97; // keccak256(abi.encodePacked("zerodev.kernel.lite.ecdsa")) - 1 + + constructor(IEntryPoint _entryPoint) KernelLite(_entryPoint) { + } + + function getKernelLiteECDSAStorage() internal pure returns (KernelLiteECDSAStorage storage s) { + assembly { + s.slot := KERNEL_LITE_ECDSA_STORAGE_SLOT + } + } + + function _setInitialData(IKernelValidator, bytes calldata _data) internal override { + require(getKernelLiteECDSAStorage().owner == address(0), "KernelLiteECDSA: already initialized"); + address owner = address(bytes20(_data[0:20])); + getKernelLiteECDSAStorage().owner = owner; + } + + function _validateUserOp(UserOperation calldata _op, bytes32 _opHash, uint256) internal override returns(uint256) { + address signed = ECDSA.recover(ECDSA.toEthSignedMessageHash(_opHash), _op.signature[4:]); // note that first 4 bytes are for modes + if (signed != getKernelLiteECDSAStorage().owner) { + return SIG_VALIDATION_FAILED; + } + return 0; + } + + function _validateSignature(bytes32 _hash, bytes calldata _signature) internal view override returns(uint256) { + address signed = ECDSA.recover(ECDSA.toEthSignedMessageHash(_hash), _signature); + if (signed == getKernelLiteECDSAStorage().owner) { + return 0; + } + if( ECDSA.recover(_hash, _signature) == getKernelLiteECDSAStorage().owner) { + return 0; + } + return SIG_VALIDATION_FAILED; + } + + function _validCaller(address _caller, bytes calldata) internal view override returns (bool) { + return _caller == getKernelLiteECDSAStorage().owner; + } +} diff --git a/test/foundry/KernelLite.t.sol b/test/foundry/KernelLite.t.sol new file mode 100644 index 00000000..851bfe1d --- /dev/null +++ b/test/foundry/KernelLite.t.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "src/factory/AdminLessERC1967Factory.sol"; +import "src/factory/KernelFactory.sol"; +import {KernelLiteECDSA} from "src/lite/KernelLiteECDSA.sol"; +import "src/Kernel.sol"; +import "src/validator/ECDSAValidator.sol"; +// test artifacts +import "src/test/TestValidator.sol"; +import "src/test/TestERC721.sol"; +import "src/test/TestKernel.sol"; +// test utils +import "forge-std/Test.sol"; +import {ERC4337Utils} from "./ERC4337Utils.sol"; + +using ERC4337Utils for EntryPoint; + +contract KernelTest is Test { + KernelLiteECDSA kernel; + KernelLiteECDSA kernelImpl; + KernelFactory factory; + EntryPoint entryPoint; + ECDSAValidator validator; + address owner; + uint256 ownerKey; + address payable beneficiary; + address factoryOwner; + + function setUp() public { + (owner, ownerKey) = makeAddrAndKey("owner"); + (factoryOwner, ) = makeAddrAndKey("factoryOwner"); + entryPoint = new EntryPoint(); + kernelImpl = new KernelLiteECDSA(entryPoint); + factory = new KernelFactory(factoryOwner); + vm.startPrank(factoryOwner); + factory.setImplementation(address(kernelImpl), true); + vm.stopPrank(); + + validator = new ECDSAValidator(); + + kernel = KernelLiteECDSA(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 0)))); + vm.deal(address(kernel), 1e30); + beneficiary = payable(address(makeAddr("beneficiary"))); + } + + function test_initialize_twice() external { + vm.expectRevert(); + kernel.initialize(validator, abi.encodePacked(owner)); + } + + function test_external_call_default() external { + vm.startPrank(owner); + (bool success,) = address(kernel).call(abi.encodePacked("Hello world")); + assertEq(success, true); + } + + function test_validate_signature() external { + KernelLiteECDSA kernel2 = KernelLiteECDSA(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 1)))); + bytes32 hash = keccak256(abi.encodePacked("hello world")); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); + assertEq(kernel2.isValidSignature(hash, abi.encodePacked(r, s, v)), Kernel.isValidSignature.selector); + } + + function test_validate_userOp() external { + TestKernel kernel2 = new TestKernel(entryPoint); + kernel2.sudoInitialize(validator, abi.encodePacked(owner)); + + UserOperation memory op = entryPoint.fillUserOp( + address(kernel), + abi.encodeWithSelector(Kernel.execute.selector, address(0), 0, bytes("")) + ); + op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); + bytes32 hash = entryPoint.getUserOpHash(op); + vm.startPrank(address(entryPoint)); + kernel2.validateUserOp(op, hash, 0); + vm.stopPrank(); + } + + function test_set_default_validator() external { + TestValidator newValidator = new TestValidator(); + bytes memory empty; + UserOperation memory op = entryPoint.fillUserOp( + address(kernel), + abi.encodeWithSelector(KernelStorage.setDefaultValidator.selector, address(newValidator), empty) + ); + op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = op; + entryPoint.handleOps(ops, beneficiary); + assertEq(address(KernelStorage(address(kernel)).getDefaultValidator()), address(newValidator)); + } + + function test_disable_mode() external { + vm.warp(1000); + bytes memory empty; + UserOperation memory op = entryPoint.fillUserOp( + address(kernel), + abi.encodeWithSelector(KernelStorage.disableMode.selector, bytes4(0x00000001), address(0), empty) + ); + op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = op; + entryPoint.handleOps(ops, beneficiary); + assertEq(uint256(bytes32(KernelStorage(address(kernel)).getDisabledMode())), 1 << 224); + } + + function test_set_execution() external { + console.log("owner", owner); + TestValidator newValidator = new TestValidator(); + UserOperation memory op = entryPoint.fillUserOp( + address(kernel), + abi.encodeWithSelector( + KernelStorage.setExecution.selector, + bytes4(0xdeadbeef), + address(0xdead), + address(newValidator), + uint48(0), + uint48(0), + bytes("") + ) + ); + op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = op; + entryPoint.handleOps(ops, beneficiary); + ExecutionDetail memory execution = KernelStorage(address(kernel)).getExecution(bytes4(0xdeadbeef)); + assertEq(execution.executor, address(0xdead)); + assertEq(address(execution.validator), address(newValidator)); + assertEq(uint256(execution.validUntil), uint256(0)); + assertEq(uint256(execution.validAfter), uint256(0)); + } + + function test_external_call_execution() external { + console.log("owner", owner); + TestValidator newValidator = new TestValidator(); + UserOperation memory op = entryPoint.fillUserOp( + address(kernel), + abi.encodeWithSelector( + KernelStorage.setExecution.selector, + bytes4(0xdeadbeef), + address(0xdead), + address(newValidator), + uint48(0), + uint48(0), + bytes("") + ) + ); + op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = op; + entryPoint.handleOps(ops, beneficiary); + ExecutionDetail memory execution = KernelStorage(address(kernel)).getExecution(bytes4(0xdeadbeef)); + assertEq(execution.executor, address(0xdead)); + assertEq(address(execution.validator), address(newValidator)); + assertEq(uint256(execution.validUntil), uint256(0)); + assertEq(uint256(execution.validAfter), uint256(0)); + + address randomAddr = makeAddr("random"); + newValidator.sudoSetCaller(address(kernel), randomAddr); + vm.startPrank(randomAddr); + (bool success,) = address(kernel).call(abi.encodePacked(bytes4(0xdeadbeef))); + assertEq(success, true); + vm.stopPrank(); + + address notAllowed = makeAddr("notAllowed"); + vm.startPrank(notAllowed); + (bool success2,) = address(kernel).call(abi.encodePacked(bytes4(0xdeadbeef))); + assertEq(success2, false); + vm.stopPrank(); + } +} From 92cfdd18dc6e878eb0275b89c35c0e81588dd8f7 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:34:23 +0900 Subject: [PATCH 07/10] files for setting a proper gas report --- foundry.toml | 2 +- gas/ecdsa/report-lite.txt | 24 ++++++++++++++++++++++++ gas/ecdsa/report.txt | 33 ++++++++++++++++++++++----------- gas/gas_report.sh | 2 ++ 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 gas/ecdsa/report-lite.txt diff --git a/foundry.toml b/foundry.toml index 003a12a6..fa9e04f2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,7 +7,7 @@ solc_version = '0.8.19' bytecode_hash = "none" cbor_metadata = false optimize = true -via-ir = false +via-ir = true runs = 1000000 gas_reports=["Kernel","KernelFactory"] diff --git a/gas/ecdsa/report-lite.txt b/gas/ecdsa/report-lite.txt new file mode 100644 index 00000000..3b02ceec --- /dev/null +++ b/gas/ecdsa/report-lite.txt @@ -0,0 +1,24 @@ +No files changed, compilation skipped + +Running 8 tests for test/foundry/KernelLite.t.sol:KernelTest +[PASS] test_disable_mode() (gas: 174219) +[PASS] test_external_call_default() (gas: 22953) +[PASS] test_external_call_execution() (gas: 442499) +[PASS] test_initialize_twice() (gas: 20968) +[PASS] test_set_default_validator() (gas: 372577) +[PASS] test_set_execution() (gas: 403664) +[PASS] test_validate_signature() (gas: 139666) +[PASS] test_validate_userOp() (gas: 1735819) +Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.78ms +| src/factory/KernelFactory.sol:KernelFactory contract | | | | | | +|------------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 594023 | 3050 | | | | | +| Function Name | min | avg | median | max | # calls | +| createAccount | 106913 | 107413 | 106913 | 111413 | 9 | +| deployDeterministicAndCall | 105350 | 105627 | 105350 | 107850 | 9 | +| setImplementation | 22862 | 22862 | 22862 | 22862 | 8 | + + + +Ran 1 test suites: 8 tests passed, 0 failed, 0 skipped (8 total tests) diff --git a/gas/ecdsa/report.txt b/gas/ecdsa/report.txt index 0a64adca..aa26498d 100644 --- a/gas/ecdsa/report.txt +++ b/gas/ecdsa/report.txt @@ -1,29 +1,40 @@ No files changed, compilation skipped -Running 7 tests for test/foundry/Kernel.t.sol:KernelTest -[PASS] test_disable_mode() (gas: 162879) -[PASS] test_external_call_default() (gas: 28845) -[PASS] test_external_call_execution() (gas: 453354) -[PASS] test_initialize_twice() (gas: 20840) +Running 8 tests for test/foundry/Kernel.t.sol:KernelTest +[PASS] test_disable_mode() (gas: 162901) +[PASS] test_external_call_default() (gas: 28867) +[PASS] test_external_call_execution() (gas: 453376) +[PASS] test_initialize_twice() (gas: 20885) [PASS] test_set_default_validator() (gas: 361374) -[PASS] test_set_execution() (gas: 411689) -[PASS] test_validate_signature() (gas: 165811) -Test result: ok. 7 passed; 0 failed; 0 skipped; finished in 2.67ms +[PASS] test_set_execution() (gas: 411669) +[PASS] test_validate_signature() (gas: 164809) +[PASS] test_validate_userOp() (gas: 1732266) +Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.87ms | src/Kernel.sol:Kernel contract | | | | | | |--------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1590591 | 8353 | | | | | +| 1589391 | 8347 | | | | | | Function Name | min | avg | median | max | # calls | | disableMode | 3765 | 3765 | 3765 | 3765 | 1 | | getDefaultValidator | 341 | 341 | 341 | 341 | 1 | | getDisabledMode | 577 | 577 | 577 | 577 | 1 | | getExecution | 1249 | 1249 | 1249 | 1249 | 2 | -| initialize | 3083 | 44928 | 49847 | 52347 | 9 | +| initialize | 3046 | 43982 | 48253 | 50753 | 10 | | isValidSignature | 6047 | 6047 | 6047 | 6047 | 1 | | setDefaultValidator | 7870 | 7870 | 7870 | 7870 | 1 | | setExecution | 49877 | 49877 | 49877 | 49877 | 2 | | validateUserOp | 46040 | 46234 | 46256 | 46386 | 4 | +| src/factory/KernelFactory.sol:KernelFactory contract | | | | | | +|------------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 594023 | 3050 | | | | | +| Function Name | min | avg | median | max | # calls | +| createAccount | 132097 | 132874 | 132097 | 139097 | 9 | +| deployDeterministicAndCall | 130534 | 131089 | 130534 | 135534 | 9 | +| setImplementation | 22862 | 22862 | 22862 | 22862 | 8 | -Ran 1 test suites: 7 tests passed, 0 failed, 0 skipped (7 total tests) + + +Ran 1 test suites: 8 tests passed, 0 failed, 0 skipped (8 total tests) diff --git a/gas/gas_report.sh b/gas/gas_report.sh index 26f8ee2b..e776d913 100755 --- a/gas/gas_report.sh +++ b/gas/gas_report.sh @@ -1 +1,3 @@ +forge build forge test --gas-report --match-path test/foundry/Kernel.t.sol > gas/ecdsa/report.txt +forge test --gas-report --match-path test/foundry/KernelLite.t.sol > gas/ecdsa/report-lite.txt From a037f9fbc1ae0839b32125709914f9a9bd03abf6 Mon Sep 17 00:00:00 2001 From: leekt Date: Wed, 2 Aug 2023 23:53:59 +0900 Subject: [PATCH 08/10] using memory directly to reduce gas --- foundry.toml | 2 +- gas/ecdsa/report-lite.txt | 20 ++++++++++++++++++-- gas/ecdsa/report.txt | 10 +++++----- src/Kernel.sol | 7 +++---- src/KernelLite.sol | 7 +++---- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/foundry.toml b/foundry.toml index fa9e04f2..2a88ba29 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,6 +10,6 @@ optimize = true via-ir = true runs = 1000000 -gas_reports=["Kernel","KernelFactory"] +gas_reports=["Kernel","KernelLiteECDSA","KernelFactory"] # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/gas/ecdsa/report-lite.txt b/gas/ecdsa/report-lite.txt index 3b02ceec..61972268 100644 --- a/gas/ecdsa/report-lite.txt +++ b/gas/ecdsa/report-lite.txt @@ -8,8 +8,8 @@ Running 8 tests for test/foundry/KernelLite.t.sol:KernelTest [PASS] test_set_default_validator() (gas: 372577) [PASS] test_set_execution() (gas: 403664) [PASS] test_validate_signature() (gas: 139666) -[PASS] test_validate_userOp() (gas: 1735819) -Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.78ms +[PASS] test_validate_userOp() (gas: 1733019) +Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.77ms | src/factory/KernelFactory.sol:KernelFactory contract | | | | | | |------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -20,5 +20,21 @@ Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.78ms | setImplementation | 22862 | 22862 | 22862 | 22862 | 8 | +| src/lite/KernelLiteECDSA.sol:KernelLiteECDSA contract | | | | | | +|-------------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 1601205 | 8406 | | | | | +| Function Name | min | avg | median | max | # calls | +| disableMode | 22865 | 22865 | 22865 | 22865 | 1 | +| getDefaultValidator | 341 | 341 | 341 | 341 | 1 | +| getDisabledMode | 577 | 577 | 577 | 577 | 1 | +| getExecution | 1249 | 1249 | 1249 | 1249 | 2 | +| initialize | 3117 | 21073 | 23069 | 23069 | 10 | +| isValidSignature | 8587 | 8587 | 8587 | 8587 | 1 | +| setDefaultValidator | 26774 | 26774 | 26774 | 26774 | 1 | +| setExecution | 49853 | 49853 | 49853 | 49853 | 2 | +| validateUserOp | 38837 | 38850 | 38852 | 38861 | 4 | + + Ran 1 test suites: 8 tests passed, 0 failed, 0 skipped (8 total tests) diff --git a/gas/ecdsa/report.txt b/gas/ecdsa/report.txt index aa26498d..c53e8856 100644 --- a/gas/ecdsa/report.txt +++ b/gas/ecdsa/report.txt @@ -6,14 +6,14 @@ Running 8 tests for test/foundry/Kernel.t.sol:KernelTest [PASS] test_external_call_execution() (gas: 453376) [PASS] test_initialize_twice() (gas: 20885) [PASS] test_set_default_validator() (gas: 361374) -[PASS] test_set_execution() (gas: 411669) -[PASS] test_validate_signature() (gas: 164809) -[PASS] test_validate_userOp() (gas: 1732266) -Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.87ms +[PASS] test_set_execution() (gas: 411711) +[PASS] test_validate_signature() (gas: 164810) +[PASS] test_validate_userOp() (gas: 1729466) +Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.77ms | src/Kernel.sol:Kernel contract | | | | | | |--------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1589391 | 8347 | | | | | +| 1586591 | 8333 | | | | | | Function Name | min | avg | median | max | # calls | | disableMode | 3765 | 3765 | 3765 | 3765 | 1 | | getDefaultValidator | 341 | 341 | 341 | 341 | 1 | diff --git a/src/Kernel.sol b/src/Kernel.sol index 4a827f2f..82b44ab2 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -60,14 +60,13 @@ contract Kernel is EIP712, Compatibility, KernelStorage { /// @param value The amount of Ether to send /// @param data The call data to be sent /// @param operation The type of operation (call or delegatecall) - function execute(address to, uint256 value, bytes calldata data, Operation operation) external payable { + function execute(address to, uint256 value, bytes memory data, Operation operation) external payable { if (msg.sender != address(entryPoint) && !_checkCaller()) { revert NotAuthorizedCaller(); } - bytes memory callData = data; if (operation == Operation.DelegateCall) { assembly { - let success := delegatecall(gas(), to, add(callData, 0x20), mload(callData), 0, 0) + let success := delegatecall(gas(), to, add(data, 0x20), mload(data), 0, 0) returndatacopy(0, 0, returndatasize()) switch success case 0 { revert(0, returndatasize()) } @@ -75,7 +74,7 @@ contract Kernel is EIP712, Compatibility, KernelStorage { } } else { assembly { - let success := call(gas(), to, value, add(callData, 0x20), mload(callData), 0, 0) + let success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) returndatacopy(0, 0, returndatasize()) switch success case 0 { revert(0, returndatasize()) } diff --git a/src/KernelLite.sol b/src/KernelLite.sol index e883d6dc..6b1ff31e 100644 --- a/src/KernelLite.sol +++ b/src/KernelLite.sol @@ -60,14 +60,13 @@ abstract contract KernelLite is EIP712, Compatibility, KernelStorage { /// @param value The amount of Ether to send /// @param data The call data to be sent /// @param operation The type of operation (call or delegatecall) - function execute(address to, uint256 value, bytes calldata data, Operation operation) external payable { + function execute(address to, uint256 value, bytes memory data, Operation operation) external payable { if (msg.sender != address(entryPoint) && !_checkCaller()) { revert NotAuthorizedCaller(); } - bytes memory callData = data; if (operation == Operation.DelegateCall) { assembly { - let success := delegatecall(gas(), to, add(callData, 0x20), mload(callData), 0, 0) + let success := delegatecall(gas(), to, add(data, 0x20), mload(data), 0, 0) returndatacopy(0, 0, returndatasize()) switch success case 0 { revert(0, returndatasize()) } @@ -75,7 +74,7 @@ abstract contract KernelLite is EIP712, Compatibility, KernelStorage { } } else { assembly { - let success := call(gas(), to, value, add(callData, 0x20), mload(callData), 0, 0) + let success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) returndatacopy(0, 0, returndatasize()) switch success case 0 { revert(0, returndatasize()) } From 5ccfa66e49a2497b112488cc80cd0e02af98588b Mon Sep 17 00:00:00 2001 From: leekt Date: Thu, 3 Aug 2023 02:41:13 +0900 Subject: [PATCH 09/10] changed order for validator to reduce gas cost --- gas/ecdsa/report-lite.txt | 14 +++++++------- src/KernelLite.sol | 6 ++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/gas/ecdsa/report-lite.txt b/gas/ecdsa/report-lite.txt index 61972268..bd7680bd 100644 --- a/gas/ecdsa/report-lite.txt +++ b/gas/ecdsa/report-lite.txt @@ -1,15 +1,15 @@ No files changed, compilation skipped Running 8 tests for test/foundry/KernelLite.t.sol:KernelTest -[PASS] test_disable_mode() (gas: 174219) +[PASS] test_disable_mode() (gas: 171425) [PASS] test_external_call_default() (gas: 22953) -[PASS] test_external_call_execution() (gas: 442499) +[PASS] test_external_call_execution() (gas: 439687) [PASS] test_initialize_twice() (gas: 20968) -[PASS] test_set_default_validator() (gas: 372577) -[PASS] test_set_execution() (gas: 403664) +[PASS] test_set_default_validator() (gas: 369789) +[PASS] test_set_execution() (gas: 400852) [PASS] test_validate_signature() (gas: 139666) [PASS] test_validate_userOp() (gas: 1733019) -Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.77ms +Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.73ms | src/factory/KernelFactory.sol:KernelFactory contract | | | | | | |------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -23,7 +23,7 @@ Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.77ms | src/lite/KernelLiteECDSA.sol:KernelLiteECDSA contract | | | | | | |-------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1601205 | 8406 | | | | | +| 1598398 | 8392 | | | | | | Function Name | min | avg | median | max | # calls | | disableMode | 22865 | 22865 | 22865 | 22865 | 1 | | getDefaultValidator | 341 | 341 | 341 | 341 | 1 | @@ -33,7 +33,7 @@ Test result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.77ms | isValidSignature | 8587 | 8587 | 8587 | 8587 | 1 | | setDefaultValidator | 26774 | 26774 | 26774 | 26774 | 1 | | setExecution | 49853 | 49853 | 49853 | 49853 | 2 | -| validateUserOp | 38837 | 38850 | 38852 | 38861 | 4 | +| validateUserOp | 36049 | 36049 | 36049 | 36049 | 4 | diff --git a/src/KernelLite.sol b/src/KernelLite.sol index 6b1ff31e..b16fd383 100644 --- a/src/KernelLite.sol +++ b/src/KernelLite.sol @@ -101,12 +101,8 @@ abstract contract KernelLite is EIP712, Compatibility, KernelStorage { // mode == 0x00000000 use sudo validator // mode == 0x00000001 use given validator // mode == 0x00000002 enable validator - UserOperation memory op = userOp; - IKernelValidator validator; if (mode == 0x00000000) { // sudo mode (use default validator) - op.signature = userOp.signature[4:]; - if (missingAccountFunds != 0) { assembly { pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) @@ -116,6 +112,8 @@ abstract contract KernelLite is EIP712, Compatibility, KernelStorage { // short circuit here for default validator return _validateUserOp(userOp, userOpHash, missingAccountFunds); } + UserOperation memory op = userOp; + IKernelValidator validator; bytes32 storage_slot_1; assembly { From 8bb7e15a6a8cc5d7f0dbefe1463bb46134692f4e Mon Sep 17 00:00:00 2001 From: leekt Date: Mon, 7 Aug 2023 15:07:18 +0900 Subject: [PATCH 10/10] optimize only --- lib/account-abstraction | 2 +- src/KernelLite.sol | 248 ---------------------------------- src/lite/KernelLiteECDSA.sol | 49 ------- test/foundry/KernelLite.t.sol | 172 ----------------------- 4 files changed, 1 insertion(+), 470 deletions(-) delete mode 100644 src/KernelLite.sol delete mode 100644 src/lite/KernelLiteECDSA.sol delete mode 100644 test/foundry/KernelLite.t.sol diff --git a/lib/account-abstraction b/lib/account-abstraction index abff2aca..12be13e2 160000 --- a/lib/account-abstraction +++ b/lib/account-abstraction @@ -1 +1 @@ -Subproject commit abff2aca61a8f0934e533d0d352978055fddbd96 +Subproject commit 12be13e2e97b763e1ef294602b3f2072bc301443 diff --git a/src/KernelLite.sol b/src/KernelLite.sol deleted file mode 100644 index b16fd383..00000000 --- a/src/KernelLite.sol +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Importing external libraries and contracts -import "solady/utils/EIP712.sol"; -import "solady/utils/ECDSA.sol"; -import "account-abstraction/core/Helpers.sol"; -import "account-abstraction/interfaces/IEntryPoint.sol"; -import {EntryPoint} from "account-abstraction/core/EntryPoint.sol"; -import "./abstract/Compatibility.sol"; -import "./abstract/KernelStorage.sol"; -import "./utils/KernelHelper.sol"; - -enum Operation { - Call, - DelegateCall -} - -bytes32 constant VALIDATOR_APPROVED_STRUCT_HASH = 0x3ce406685c1b3551d706d85a68afdaa49ac4e07b451ad9b8ff8b58c3ee964176; - -/// @title KernelLite -/// @author taek -/// @notice wallet kernel for extensible wallet functionality -abstract contract KernelLite is EIP712, Compatibility, KernelStorage { - string public constant name = "Kernel"; - - string public constant version = "0.2.1"; - - error NotEntryPoint(); - error DisabledMode(); - - /// @dev Sets up the EIP712 and KernelStorage with the provided entry point - constructor(IEntryPoint _entryPoint) KernelStorage(_entryPoint) {} - - function _domainNameAndVersion() internal pure override returns (string memory, string memory) { - return (name, version); - } - - /// @notice Accepts incoming Ether transactions and calls from the EntryPoint contract - /// @dev This function will delegate any call to the appropriate executor based on the function signature. - fallback() external payable { - bytes4 sig = msg.sig; - address executor = getKernelStorage().execution[sig].executor; - if (msg.sender != address(entryPoint) && !_checkCaller()) { - revert NotAuthorizedCaller(); - } - assembly { - calldatacopy(0, 0, calldatasize()) - let result := delegatecall(gas(), executor, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - } - - /// @notice Executes a function call to an external contract - /// @dev The type of operation (call or delegatecall) is specified as an argument. - /// @param to The address of the target contract - /// @param value The amount of Ether to send - /// @param data The call data to be sent - /// @param operation The type of operation (call or delegatecall) - function execute(address to, uint256 value, bytes memory data, Operation operation) external payable { - if (msg.sender != address(entryPoint) && !_checkCaller()) { - revert NotAuthorizedCaller(); - } - if (operation == Operation.DelegateCall) { - 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()) } - } - } else { - assembly { - let success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch success - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - } - } - - /// @notice Validates a user operation based on its mode - /// @dev This function will validate user operation and be called by EntryPoint - /// @param userOp The user operation to be validated - /// @param userOpHash The hash of the user operation - /// @param missingAccountFunds The funds needed to be reimbursed - /// @return validationData The data used for validation - function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) - external - payable - returns (uint256 validationData) - { - if (msg.sender != address(entryPoint)) { - revert NotEntryPoint(); - } - bytes4 mode = bytes4(userOp.signature[0:4]); // mode == 00..00 use validators - // mode == 0x00000000 use sudo validator - // mode == 0x00000001 use given validator - // mode == 0x00000002 enable validator - if (mode == 0x00000000) { - // sudo mode (use default validator) - if (missingAccountFunds != 0) { - assembly { - pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) - } - //ignore failure (its EntryPoint's job to verify, not account.) - } - // short circuit here for default validator - return _validateUserOp(userOp, userOpHash, missingAccountFunds); - } - UserOperation memory op = userOp; - IKernelValidator validator; - - bytes32 storage_slot_1; - assembly { - storage_slot_1 := sload(KERNEL_STORAGE_SLOT_1) - } - if(mode & (storage_slot_1 << 224) != 0x00000000) { - revert DisabledMode(); - } else if (mode == 0x00000001) { - bytes4 sig = bytes4(userOp.callData[0:4]); - ExecutionDetail storage detail = getKernelStorage().execution[sig]; - validator = detail.validator; - if (address(validator) == address(0)) { - return SIG_VALIDATION_FAILED; - } - op.signature = userOp.signature[4:]; - validationData = (uint256(detail.validAfter) << 160) | (uint256(detail.validUntil) << 208); - } else if (mode == 0x00000002) { - bytes4 sig = bytes4(userOp.callData[0:4]); - // use given validator - // userOp.signature[4:10] = validUntil, - // userOp.signature[10:16] = validAfter, - // userOp.signature[16:36] = validator address, - validator = IKernelValidator(address(bytes20(userOp.signature[16:36]))); - bytes calldata enableData; - bytes calldata remainSig; - (validationData, enableData, remainSig) = _approveValidator(sig, userOp.signature); - validator.enable(enableData); - op.signature = remainSig; - } else { - return SIG_VALIDATION_FAILED; - } - if (missingAccountFunds != 0) { - assembly { - pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0)) - } - //ignore failure (its EntryPoint's job to verify, not account.) - } - validationData = - _intersectValidationData(validationData, validator.validateUserOp(op, userOpHash, missingAccountFunds)); - return validationData; - } - - function _approveValidator(bytes4 sig, bytes calldata signature) - internal - returns (uint256 validationData, bytes calldata enableData, bytes calldata validationSig) - { - unchecked { - uint256 cursor = 88; - uint256 length = uint256(bytes32(signature[56:88])); // this is enableDataLength - assembly { - enableData.offset := add(signature.offset, cursor) - enableData.length := length - cursor := add(cursor, length) // 88 + enableDataLength - } - length = uint256(bytes32(signature[cursor:cursor + 32])); // this is enableSigLength - assembly { - cursor := add(cursor, 32) - } - bytes32 enableDigest = _hashTypedData( - keccak256( - abi.encode( - VALIDATOR_APPROVED_STRUCT_HASH, - bytes4(sig), - uint256(bytes32(signature[4:36])), - address(bytes20(signature[36:56])), - keccak256(enableData) - ) - ) - ); - validationData = _intersectValidationData( - _validateSignature( - enableDigest, signature[cursor:cursor+length] - ), - uint256(bytes32(signature[4:36])) & 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 - ); - assembly { - cursor := add(cursor, length) - validationSig.offset := add(signature.offset, cursor) - validationSig.length := sub(signature.length, cursor) - } - getKernelStorage().execution[sig] = ExecutionDetail({ - executor: address(bytes20(signature[36:56])), - validUntil: uint48(bytes6(signature[4:10])), - validAfter: uint48(bytes6(signature[10:16])), - validator: IKernelValidator(address(bytes20(signature[16:36]))) - }); - } - } - - /// @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) external view returns (bytes4) { - uint256 validationData = _validateSignature(hash, signature); - ValidationData memory data = _parseValidationData(validationData); - if (data.validAfter > block.timestamp) { - return 0xffffffff; - } - if (data.validUntil < block.timestamp) { - return 0xffffffff; - } - if (data.aggregator != address(0)) { - return 0xffffffff; - } - - return 0x1626ba7e; - } - - function _checkCaller() internal view returns (bool) { - if (_validCaller(msg.sender, msg.data)) { - return true; - } - bytes4 sig = msg.sig; - ExecutionDetail storage detail = getKernelStorage().execution[sig]; - if ( - address(detail.validator) == address(0) || (detail.validUntil != 0 && detail.validUntil < block.timestamp) - || detail.validAfter > block.timestamp - ) { - return false; - } else { - return detail.validator.validCaller(msg.sender, msg.data); - } - } - - function _validateUserOp(UserOperation calldata _op, bytes32 _opHash, uint256 _missingFunds) internal virtual returns(uint256); - - function _validateSignature(bytes32 _hash, bytes calldata _signature) internal view virtual returns(uint256); - - function _validCaller(address _caller, bytes calldata _data) internal view virtual returns (bool); -} diff --git a/src/lite/KernelLiteECDSA.sol b/src/lite/KernelLiteECDSA.sol deleted file mode 100644 index bda918e5..00000000 --- a/src/lite/KernelLiteECDSA.sol +++ /dev/null @@ -1,49 +0,0 @@ -pragma solidity ^0.8.0; - -import "src/KernelLite.sol"; - -struct KernelLiteECDSAStorage { - address owner; -} - -contract KernelLiteECDSA is KernelLite { - bytes32 constant private KERNEL_LITE_ECDSA_STORAGE_SLOT = 0xdea7fea882fba743201b2aeb1babf326b8944488db560784858525d123ee7e97; // keccak256(abi.encodePacked("zerodev.kernel.lite.ecdsa")) - 1 - - constructor(IEntryPoint _entryPoint) KernelLite(_entryPoint) { - } - - function getKernelLiteECDSAStorage() internal pure returns (KernelLiteECDSAStorage storage s) { - assembly { - s.slot := KERNEL_LITE_ECDSA_STORAGE_SLOT - } - } - - function _setInitialData(IKernelValidator, bytes calldata _data) internal override { - require(getKernelLiteECDSAStorage().owner == address(0), "KernelLiteECDSA: already initialized"); - address owner = address(bytes20(_data[0:20])); - getKernelLiteECDSAStorage().owner = owner; - } - - function _validateUserOp(UserOperation calldata _op, bytes32 _opHash, uint256) internal override returns(uint256) { - address signed = ECDSA.recover(ECDSA.toEthSignedMessageHash(_opHash), _op.signature[4:]); // note that first 4 bytes are for modes - if (signed != getKernelLiteECDSAStorage().owner) { - return SIG_VALIDATION_FAILED; - } - return 0; - } - - function _validateSignature(bytes32 _hash, bytes calldata _signature) internal view override returns(uint256) { - address signed = ECDSA.recover(ECDSA.toEthSignedMessageHash(_hash), _signature); - if (signed == getKernelLiteECDSAStorage().owner) { - return 0; - } - if( ECDSA.recover(_hash, _signature) == getKernelLiteECDSAStorage().owner) { - return 0; - } - return SIG_VALIDATION_FAILED; - } - - function _validCaller(address _caller, bytes calldata) internal view override returns (bool) { - return _caller == getKernelLiteECDSAStorage().owner; - } -} diff --git a/test/foundry/KernelLite.t.sol b/test/foundry/KernelLite.t.sol deleted file mode 100644 index 851bfe1d..00000000 --- a/test/foundry/KernelLite.t.sol +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "src/factory/AdminLessERC1967Factory.sol"; -import "src/factory/KernelFactory.sol"; -import {KernelLiteECDSA} from "src/lite/KernelLiteECDSA.sol"; -import "src/Kernel.sol"; -import "src/validator/ECDSAValidator.sol"; -// test artifacts -import "src/test/TestValidator.sol"; -import "src/test/TestERC721.sol"; -import "src/test/TestKernel.sol"; -// test utils -import "forge-std/Test.sol"; -import {ERC4337Utils} from "./ERC4337Utils.sol"; - -using ERC4337Utils for EntryPoint; - -contract KernelTest is Test { - KernelLiteECDSA kernel; - KernelLiteECDSA kernelImpl; - KernelFactory factory; - EntryPoint entryPoint; - ECDSAValidator validator; - address owner; - uint256 ownerKey; - address payable beneficiary; - address factoryOwner; - - function setUp() public { - (owner, ownerKey) = makeAddrAndKey("owner"); - (factoryOwner, ) = makeAddrAndKey("factoryOwner"); - entryPoint = new EntryPoint(); - kernelImpl = new KernelLiteECDSA(entryPoint); - factory = new KernelFactory(factoryOwner); - vm.startPrank(factoryOwner); - factory.setImplementation(address(kernelImpl), true); - vm.stopPrank(); - - validator = new ECDSAValidator(); - - kernel = KernelLiteECDSA(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 0)))); - vm.deal(address(kernel), 1e30); - beneficiary = payable(address(makeAddr("beneficiary"))); - } - - function test_initialize_twice() external { - vm.expectRevert(); - kernel.initialize(validator, abi.encodePacked(owner)); - } - - function test_external_call_default() external { - vm.startPrank(owner); - (bool success,) = address(kernel).call(abi.encodePacked("Hello world")); - assertEq(success, true); - } - - function test_validate_signature() external { - KernelLiteECDSA kernel2 = KernelLiteECDSA(payable(address(factory.createAccount(address(kernelImpl), abi.encodeWithSelector(KernelStorage.initialize.selector, validator, abi.encodePacked(owner)), 1)))); - bytes32 hash = keccak256(abi.encodePacked("hello world")); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); - assertEq(kernel2.isValidSignature(hash, abi.encodePacked(r, s, v)), Kernel.isValidSignature.selector); - } - - function test_validate_userOp() external { - TestKernel kernel2 = new TestKernel(entryPoint); - kernel2.sudoInitialize(validator, abi.encodePacked(owner)); - - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector(Kernel.execute.selector, address(0), 0, bytes("")) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - bytes32 hash = entryPoint.getUserOpHash(op); - vm.startPrank(address(entryPoint)); - kernel2.validateUserOp(op, hash, 0); - vm.stopPrank(); - } - - function test_set_default_validator() external { - TestValidator newValidator = new TestValidator(); - bytes memory empty; - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector(KernelStorage.setDefaultValidator.selector, address(newValidator), empty) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - assertEq(address(KernelStorage(address(kernel)).getDefaultValidator()), address(newValidator)); - } - - function test_disable_mode() external { - vm.warp(1000); - bytes memory empty; - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector(KernelStorage.disableMode.selector, bytes4(0x00000001), address(0), empty) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - assertEq(uint256(bytes32(KernelStorage(address(kernel)).getDisabledMode())), 1 << 224); - } - - function test_set_execution() external { - console.log("owner", owner); - TestValidator newValidator = new TestValidator(); - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector( - KernelStorage.setExecution.selector, - bytes4(0xdeadbeef), - address(0xdead), - address(newValidator), - uint48(0), - uint48(0), - bytes("") - ) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - ExecutionDetail memory execution = KernelStorage(address(kernel)).getExecution(bytes4(0xdeadbeef)); - assertEq(execution.executor, address(0xdead)); - assertEq(address(execution.validator), address(newValidator)); - assertEq(uint256(execution.validUntil), uint256(0)); - assertEq(uint256(execution.validAfter), uint256(0)); - } - - function test_external_call_execution() external { - console.log("owner", owner); - TestValidator newValidator = new TestValidator(); - UserOperation memory op = entryPoint.fillUserOp( - address(kernel), - abi.encodeWithSelector( - KernelStorage.setExecution.selector, - bytes4(0xdeadbeef), - address(0xdead), - address(newValidator), - uint48(0), - uint48(0), - bytes("") - ) - ); - op.signature = abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKey, op)); - UserOperation[] memory ops = new UserOperation[](1); - ops[0] = op; - entryPoint.handleOps(ops, beneficiary); - ExecutionDetail memory execution = KernelStorage(address(kernel)).getExecution(bytes4(0xdeadbeef)); - assertEq(execution.executor, address(0xdead)); - assertEq(address(execution.validator), address(newValidator)); - assertEq(uint256(execution.validUntil), uint256(0)); - assertEq(uint256(execution.validAfter), uint256(0)); - - address randomAddr = makeAddr("random"); - newValidator.sudoSetCaller(address(kernel), randomAddr); - vm.startPrank(randomAddr); - (bool success,) = address(kernel).call(abi.encodePacked(bytes4(0xdeadbeef))); - assertEq(success, true); - vm.stopPrank(); - - address notAllowed = makeAddr("notAllowed"); - vm.startPrank(notAllowed); - (bool success2,) = address(kernel).call(abi.encodePacked(bytes4(0xdeadbeef))); - assertEq(success2, false); - vm.stopPrank(); - } -}