-
Notifications
You must be signed in to change notification settings - Fork 75
Feat/multi sig validator #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.18; | ||
|
|
||
| import "./KernelFactory.sol"; | ||
| import "src/validator/MultiSigValidator.sol"; | ||
| import "src/interfaces/IMultiSigAddressBook.sol"; | ||
| import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
|
||
| contract MultiSigKernelFactory is IMultiSigAddressBook, Ownable { | ||
| KernelFactory public immutable singletonFactory; | ||
| MultiSigValidator public immutable validator; | ||
| IEntryPoint public immutable entryPoint; | ||
|
|
||
| address[] public owners; | ||
| uint256 public threshold; | ||
|
|
||
| constructor(KernelFactory _singletonFactory, MultiSigValidator _validator, IEntryPoint _entryPoint) { | ||
| singletonFactory = _singletonFactory; | ||
| validator = _validator; | ||
| entryPoint = _entryPoint; | ||
| } | ||
|
|
||
| function setOwners(address[] calldata _owners, uint256 _threshold) external onlyOwner { | ||
| require(_owners.length >= _threshold, "MultiSigKernelFactory: threshold must be less than or equal to the number of owners"); | ||
| owners = _owners; | ||
| threshold = _threshold; | ||
| } | ||
|
|
||
| function getOwners() external view override returns(address[] memory) { | ||
| return owners; | ||
| } | ||
|
|
||
| function getThreshold() external view override returns(uint256) { | ||
| return threshold; | ||
| } | ||
|
|
||
| function createAccount(uint256 _index) external returns (EIP1967Proxy 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.18; | ||
|
|
||
| interface IMultiSigAddressBook { | ||
| function getOwners() external view returns(address[] memory); | ||
| function getThreshold() external view returns(uint256); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
| pragma solidity >=0.7.0 <0.9.0; | ||
|
|
||
| /** | ||
| * @title SignatureDecoder - Decodes signatures encoded as bytes | ||
| * @author Richard Meissner - @rmeissner | ||
| */ | ||
| abstract contract SignatureDecoder { | ||
| /** | ||
| * @notice Splits signature bytes into `uint8 v, bytes32 r, bytes32 s`. | ||
| * @dev Make sure to perform a bounds check for @param pos, to avoid out of bounds access on @param signatures | ||
| * The signature format is a compact form of {bytes32 r}{bytes32 s}{uint8 v} | ||
| * Compact means uint8 is not padded to 32 bytes. | ||
| * @param pos Which signature to read. | ||
| * A prior bounds check of this parameter should be performed, to avoid out of bounds access. | ||
| * @param signatures Concatenated {r, s, v} signatures. | ||
| * @return v Recovery ID or Safe signature type. | ||
| * @return r Output value r of the signature. | ||
| * @return s Output value s of the signature. | ||
| */ | ||
| function signatureSplit(bytes memory signatures, uint256 pos) internal pure returns (uint8 v, bytes32 r, bytes32 s) { | ||
| // solhint-disable-next-line no-inline-assembly | ||
| assembly { | ||
| let signaturePos := mul(0x41, pos) | ||
| r := mload(add(signatures, add(signaturePos, 0x20))) | ||
| s := mload(add(signatures, add(signaturePos, 0x40))) | ||
| /** | ||
| * Here we are loading the last 32 bytes, including 31 bytes | ||
| * of 's'. There is no 'mload8' to do this. | ||
| * 'byte' is not working due to the Solidity parser, so lets | ||
| * use the second best option, 'and' | ||
| */ | ||
| v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.0; | ||
|
|
||
| import "./IValidator.sol"; | ||
| import "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol"; | ||
| import "src/utils/KernelHelper.sol"; | ||
| import "src/utils/SignatureDecoder.sol"; | ||
| import "src/interfaces/IMultiSigAddressBook.sol"; | ||
| import "forge-std/Test.sol"; | ||
|
|
||
| struct MultiSigValidatorStorage { | ||
| mapping(address => bool) isOwner; | ||
| uint256 threshold; | ||
| uint256 ownerCount; | ||
| } | ||
|
|
||
| contract MultiSigValidator is IKernelValidator, SignatureDecoder, Test { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this inheriting the Test? |
||
| event OwnerAdded(address indexed kernel, address indexed owner); | ||
| event OwnerRemoved(address indexed kernel, address indexed owner); | ||
| event ThresholdChanged(address indexed kernel, uint256 indexed threshold); | ||
|
|
||
| mapping(address => MultiSigValidatorStorage) | ||
| public multiSigValidatorStorage; | ||
|
|
||
| function disable(bytes calldata _data) external override { | ||
| address[] memory owners = abi.decode(_data, (address[])); | ||
| for (uint256 i = 0; i < owners.length; i++) { | ||
| multiSigValidatorStorage[msg.sender].isOwner[owners[i]] = false; | ||
| emit OwnerRemoved(msg.sender, owners[i]); | ||
| } | ||
| } | ||
|
|
||
| function enable(bytes calldata _data) external override { | ||
| address addressBook = address(bytes20(_data)); | ||
| address[] memory owners = IMultiSigAddressBook(addressBook).getOwners(); | ||
| uint256 threshold = IMultiSigAddressBook(addressBook).getThreshold(); | ||
| for (uint256 i = 0; i < owners.length; i++) { | ||
| if (!multiSigValidatorStorage[msg.sender].isOwner[owners[i]]) { | ||
| multiSigValidatorStorage[msg.sender].isOwner[owners[i]] = true; | ||
| emit OwnerAdded(msg.sender, owners[i]); | ||
| } | ||
| } | ||
| multiSigValidatorStorage[msg.sender].threshold = threshold; | ||
| emit ThresholdChanged(msg.sender, threshold); | ||
| } | ||
|
|
||
| function validateUserOp( | ||
| UserOperation calldata _userOp, | ||
| bytes32 _userOpHash, | ||
| uint256 | ||
| ) external view override returns (uint256 validationData) { | ||
| if (!_signaturesAreValid(_userOpHash, _userOp.signature)) { | ||
| return SIG_VALIDATION_FAILED; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| function validateSignature( | ||
| bytes32 hash, | ||
| bytes calldata signature | ||
| ) public view override returns (uint256) { | ||
| if (!_signaturesAreValid(hash, signature)) { | ||
| return SIG_VALIDATION_FAILED; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| function _signaturesAreValid( | ||
| bytes32 hash, | ||
| bytes calldata signatures | ||
| ) internal view returns (bool) { | ||
| MultiSigValidatorStorage storage validatorStorage = multiSigValidatorStorage[msg.sender]; | ||
| uint256 threshold = validatorStorage.threshold; | ||
| address lastOwner = address(0); | ||
| address currentOwner; | ||
| uint8 v; | ||
| bytes32 r; | ||
| bytes32 s; | ||
| uint256 i; | ||
|
|
||
| if (signatures.length < threshold * 65) { | ||
| return false; | ||
| } | ||
| for (i = 0; i < threshold; i++) { | ||
| (v, r, s) = signatureSplit(signatures, i); | ||
| if (v > 30) { | ||
| // If v > 30 then default v (27,28) has been adjusted for eth_sign flow | ||
| // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover | ||
| currentOwner = ecrecover( | ||
| ECDSA.toEthSignedMessageHash(hash), | ||
| v - 4, | ||
| r, | ||
| s | ||
| ); | ||
| } else { | ||
| // Default is the ecrecover flow with the provided data hash | ||
| // Use ecrecover with the messageHash for EOA signatures | ||
| currentOwner = ecrecover(hash, v, r, s); | ||
| } | ||
| // To prevent signer reuse | ||
| // signatures are ordered by address | ||
| if (currentOwner <= lastOwner || !validatorStorage.isOwner[currentOwner]) { | ||
| return false; | ||
| } | ||
| lastOwner = currentOwner; | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Test.sol