diff --git a/contracts/SoulWallet.sol b/contracts/SoulWallet.sol index 1b5f36ff..1115cf92 100644 --- a/contracts/SoulWallet.sol +++ b/contracts/SoulWallet.sol @@ -15,7 +15,10 @@ import "./base/FallbackManager.sol"; import "./base/UpgradeManager.sol"; import "./base/ValidatorManager.sol"; -// Draft +/// @title SoulWallet +/// @author SoulWallet team +/// @notice logic contract of SoulWallet +/// @dev Draft contract - may be subject to changes contract SoulWallet is Initializable, ISoulWallet, @@ -31,6 +34,9 @@ contract SoulWallet is ERC1271Handler, ValidatorManager { + /// @notice Creates a new SoulWallet instance + /// @param _EntryPoint Address of the entry point + /// @param _validator Address of the validator constructor(IEntryPoint _EntryPoint, IValidator _validator) EntryPointManager(_EntryPoint) ValidatorManager(_validator) @@ -38,6 +44,11 @@ contract SoulWallet is _disableInitializers(); } + /// @notice Initializes the SoulWallet with given parameters + /// @param owners List of owner addresses (passkey public key hash or eoa address) + /// @param defalutCallbackHandler Default callback handler address + /// @param modules List of module data + /// @param plugins List of plugin data function initialize( bytes32[] calldata owners, address defalutCallbackHandler, @@ -60,10 +71,16 @@ contract SoulWallet is } } + /// @notice Gets the address of the entry point + /// @return IEntryPoint Address of the entry point function entryPoint() public view override(BaseAccount) returns (IEntryPoint) { return EntryPointManager._entryPoint(); } + /// @notice Validates the user's signature + /// @param userOp User operation details + /// @param userOpHash Hash of the user operation + /// @return validationData Data related to validation process function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash) internal virtual @@ -91,10 +108,15 @@ contract SoulWallet is validationData = validationData | ((sigValid && guardHookResult) ? 0 : SIG_VALIDATION_FAILED); } + /// @notice Upgrades the contract to a new implementation + /// @param newImplementation Address of the new implementation + /// @dev Can only be called from an external module for security reasons function upgradeTo(address newImplementation) external onlyModule { UpgradeManager._upgradeTo(newImplementation); } + /// @notice Handles the upgrade from an old implementation + /// @param oldImplementation Address of the old implementation function upgradeFrom(address oldImplementation) external pure override { (oldImplementation); revert Errors.NOT_IMPLEMENTED(); //Initial version no need data migration diff --git a/contracts/SoulWalletFactory.sol b/contracts/SoulWalletFactory.sol index 38b7529e..ae05643a 100644 --- a/contracts/SoulWalletFactory.sol +++ b/contracts/SoulWalletFactory.sol @@ -12,36 +12,48 @@ import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /** - * @author soulwallet team. - * @title A factory contract to create soul wallet. - * @dev it is called by the entrypoint which call the "initCode" factory to create and return the sender wallet address. - * @notice . + * @title SoulWalletFactory + * @author soulwallet team + * @notice A factory contract to create soul wallets + * @dev This contract is called by the entrypoint which uses the "initCode" to create and return the sender's wallet address */ - contract SoulWalletFactory is Ownable { uint256 private immutable _WALLETIMPL; IEntryPoint public immutable entryPoint; string public constant VERSION = "0.0.1"; - event SoulWalletCreation(address proxy); + event SoulWalletCreation(address indexed proxy); + /** + * @dev Initializes the factory with the wallet implementation and entry point addresses + * @param _walletImpl Address of the SoulWallet implementation + * @param _entryPoint Address of the EntryPoint contract + * @param _owner Address of the contract owner + */ constructor(address _walletImpl, address _entryPoint, address _owner) Ownable(_owner) { - require(_walletImpl != address(0)); + require(_walletImpl != address(0), "Invalid wallet implementation address"); _WALLETIMPL = uint256(uint160(_walletImpl)); - require(_entryPoint != address(0)); + require(_entryPoint != address(0), "Invalid entry point address"); entryPoint = IEntryPoint(_entryPoint); } + /** + * @notice Returns the wallet implementation address + * @return Address of the wallet implementation + */ function walletImpl() external view returns (address) { return address(uint160(_WALLETIMPL)); } function _calcSalt(bytes memory _initializer, bytes32 _salt) private pure returns (bytes32 salt) { - salt = keccak256(abi.encodePacked(keccak256(_initializer), _salt)); + return keccak256(abi.encodePacked(keccak256(_initializer), _salt)); } /** - * @notice deploy the soul wallet contract using proxy and returns the address of the proxy. should be called by entrypoint with useropeartoin.initcode > 0 + * @dev Deploys the SoulWallet using a proxy and returns the proxy's address + * @param _initializer Initialization data + * @param _salt Salt for the create2 deployment + * @return proxy Address of the deployed proxy */ function createWallet(bytes memory _initializer, bytes32 _salt) external returns (address proxy) { bytes memory deploymentData = abi.encodePacked(type(SoulWalletProxy).creationCode, _WALLETIMPL); @@ -60,25 +72,23 @@ contract SoulWalletFactory is Ownable { } /** - * @notice returns the proxy creationCode external method. - * @dev used by soulwalletlib to calcudate the soul wallet address. - * @return bytes . + * @notice Returns the proxy's creation code + * @dev Used by soulwalletlib to calculate the SoulWallet address + * @return Byte array representing the proxy's creation code */ function proxyCode() external pure returns (bytes memory) { return _proxyCode(); } - /** - * @notice returns the proxy creationCode private method. - * @dev . - * @return bytes . - */ function _proxyCode() private pure returns (bytes memory) { return type(SoulWalletProxy).creationCode; } /** - * @notice return the counterfactual address of soul wallet as it would be return by createWallet() + * @notice Calculates the counterfactual address of the SoulWallet as it would be returned by `createWallet` + * @param _initializer Initialization data + * @param _salt Salt for the create2 deployment + * @return proxy Counterfactual address of the SoulWallet */ function getWalletAddress(bytes memory _initializer, bytes32 _salt) external view returns (address proxy) { bytes memory deploymentData = abi.encodePacked(type(SoulWalletProxy).creationCode, _WALLETIMPL); @@ -86,22 +96,41 @@ contract SoulWalletFactory is Ownable { proxy = Create2.computeAddress(salt, keccak256(deploymentData)); } + /** + * @notice Deposits ETH to the entry point on behalf of the contract + */ function deposit() public payable { entryPoint.depositTo{value: msg.value}(address(this)); } + /** + * @notice Allows the owner to withdraw ETH from entrypoint contract + * @param withdrawAddress Address to receive the withdrawn ETH + * @param amount Amount of ETH to withdraw + */ function withdrawTo(address payable withdrawAddress, uint256 amount) public onlyOwner { entryPoint.withdrawTo(withdrawAddress, amount); } + /** + * @notice Allows the owner to add stake to the entry point + * @param unstakeDelaySec Duration (in seconds) after which the stake can be unlocked + */ function addStake(uint32 unstakeDelaySec) external payable onlyOwner { entryPoint.addStake{value: msg.value}(unstakeDelaySec); } + /** + * @notice Allows the owner to unlock their stake from the entry point + */ function unlockStake() external onlyOwner { entryPoint.unlockStake(); } + /** + * @notice Allows the owner to withdraw their stake from the entry point + * @param withdrawAddress Address to receive the withdrawn stake + */ function withdrawStake(address payable withdrawAddress) external onlyOwner { entryPoint.withdrawStake(withdrawAddress); } diff --git a/contracts/SoulWalletProxy.sol b/contracts/SoulWalletProxy.sol index 5fedc756..09e281ca 100644 --- a/contracts/SoulWalletProxy.sol +++ b/contracts/SoulWalletProxy.sol @@ -1,17 +1,21 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title SoulWalletProxy + * @notice A proxy contract that forwards calls to an implementation contract + * @dev This proxy uses the EIP-1967 standard for storage slots + */ contract SoulWalletProxy { /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1 + * @notice Storage slot with the address of the current implementation + * @dev This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1 */ bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** - * @dev Initializes the contract setting the implementation - * - * @param logic Address of the initial implementation. + * @notice Initializes the proxy with the address of the initial implementation contract + * @param logic Address of the initial implementation */ constructor(address logic) { assembly ("memory-safe") { @@ -20,7 +24,8 @@ contract SoulWalletProxy { } /** - * @dev Fallback function + * @notice Fallback function which forwards all calls to the implementation contract + * @dev Uses delegatecall to ensure the context remains within the proxy */ fallback() external payable { assembly { diff --git a/contracts/authority/Authority.sol b/contracts/authority/Authority.sol index 3250a2be..ddf70408 100644 --- a/contracts/authority/Authority.sol +++ b/contracts/authority/Authority.sol @@ -8,7 +8,16 @@ import "../interfaces/IModuleManager.sol"; import "../libraries/Errors.sol"; import "./ModuleAuth.sol"; +/** + * @title Authority + * @notice An abstract contract that provides authorization mechanisms + * @dev Inherits various authorization patterns including EntryPoint, Owner, and Module-based authentication + */ abstract contract Authority is EntryPointAuth, OwnerAuth, ModuleAuth { + /** + * @notice Ensures the calling contract is either the Authority contract itself or an authorized module + * @dev Uses the inherited `_isAuthorizedModule()` from ModuleAuth for module-based authentication + */ modifier onlySelfOrModule() { if (msg.sender != address(this) && !_isAuthorizedModule()) { revert Errors.CALLER_MUST_BE_SELF_OR_MODULE(); diff --git a/contracts/authority/EntryPointAuth.sol b/contracts/authority/EntryPointAuth.sol index aef0c120..d35b2b9b 100644 --- a/contracts/authority/EntryPointAuth.sol +++ b/contracts/authority/EntryPointAuth.sol @@ -4,7 +4,17 @@ pragma solidity ^0.8.20; import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import "../libraries/Errors.sol"; +/** + * @title EntryPointAuth + * @notice Abstract contract to provide EntryPoint based authentication + * @dev Requires the inheriting contracts to implement the `_entryPoint` method + */ abstract contract EntryPointAuth { + /** + * @notice Expected to return the associated entry point for the contract + * @dev Must be implemented by inheriting contracts + * @return The EntryPoint associated with the contract + */ function _entryPoint() internal view virtual returns (IEntryPoint); /* @@ -16,7 +26,8 @@ abstract contract EntryPointAuth { ┌────────────┐ ┌────────┐ │ entryPoint │ ──► │ here │ └────────────┘ └────────┘ - + * @notice Modifier to ensure the caller is the expected entry point + * @dev If not called from the expected entry point, it will revert */ modifier onlyEntryPoint() { if (msg.sender != address(_entryPoint())) { diff --git a/contracts/authority/ModuleAuth.sol b/contracts/authority/ModuleAuth.sol index b65bbf1d..c4509fcb 100644 --- a/contracts/authority/ModuleAuth.sol +++ b/contracts/authority/ModuleAuth.sol @@ -3,9 +3,23 @@ pragma solidity ^0.8.20; import "../libraries/Errors.sol"; +/** + * @title ModuleAuth + * @notice Abstract contract to provide Module-based authentication + * @dev Requires the inheriting contracts to implement the `_isAuthorizedModule` method + */ abstract contract ModuleAuth { + /** + * @notice Expected to return whether the current context is authorized as a module + * @dev Must be implemented by inheriting contracts + * @return True if the context is an authorized module, otherwise false + */ function _isAuthorizedModule() internal view virtual returns (bool); + /** + * @notice Modifier to ensure the caller is an authorized module + * @dev If not called from an authorized module, it will revert + */ modifier onlyModule() { if (!_isAuthorizedModule()) { revert Errors.CALLER_MUST_BE_MODULE(); diff --git a/contracts/authority/OwnerAuth.sol b/contracts/authority/OwnerAuth.sol index 7042f579..b762691b 100644 --- a/contracts/authority/OwnerAuth.sol +++ b/contracts/authority/OwnerAuth.sol @@ -1,6 +1,17 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title OwnerAuth + * @notice Abstract contract to provide Owner-based authentication + * @dev Requires the inheriting contracts to implement the `_isOwner` method + */ abstract contract OwnerAuth { + /** + * @notice Expected to return whether the provided owner identifier matches the owner context + * @dev Must be implemented by inheriting contracts + * @param owner The owner identifier to be checked + * @return True if the provided owner identifier matches the current owner context, otherwise false + */ function _isOwner(bytes32 owner) internal view virtual returns (bool); } diff --git a/contracts/base/EntryPointManager.sol b/contracts/base/EntryPointManager.sol index 3d37d7cd..24047e42 100644 --- a/contracts/base/EntryPointManager.sol +++ b/contracts/base/EntryPointManager.sol @@ -6,8 +6,8 @@ import "../authority/EntryPointAuth.sol"; abstract contract EntryPointManager is EntryPointAuth { IEntryPoint private immutable _ENTRY_POINT; - constructor(IEntryPoint anEntryPoint) { - _ENTRY_POINT = anEntryPoint; + constructor(IEntryPoint entryPoint) { + _ENTRY_POINT = entryPoint; } function _entryPoint() internal view override returns (IEntryPoint) { diff --git a/contracts/base/ExecutionManager.sol b/contracts/base/ExecutionManager.sol index ec4f548e..2e2180f8 100644 --- a/contracts/base/ExecutionManager.sol +++ b/contracts/base/ExecutionManager.sol @@ -5,16 +5,26 @@ import "../authority/Authority.sol"; import "./PluginManager.sol"; import "../interfaces/IExecutionManager.sol"; +/** + * @title ExecutionManager + * @notice Manages the execution of transactions and batches of transactions + * @dev Inherits functionality from IExecutionManager, Authority, and PluginManager + */ abstract contract ExecutionManager is IExecutionManager, Authority, PluginManager { /** - * execute a transaction + * @notice Execute a transaction + * @param dest The destination address for the transaction + * @param value The amount of ether to be sent with the transaction + * @param func The calldata for the transaction */ function execute(address dest, uint256 value, bytes calldata func) external override onlyEntryPoint { _call(dest, value, func); } /** - * execute a sequence of transactions + * @notice Execute a sequence of transactions without any associated ether + * @param dest List of destination addresses for each transaction + * @param func List of calldata for each transaction */ function executeBatch(address[] calldata dest, bytes[] calldata func) external override onlyEntryPoint { for (uint256 i = 0; i < dest.length;) { @@ -26,7 +36,10 @@ abstract contract ExecutionManager is IExecutionManager, Authority, PluginManage } /** - * execute a sequence of transactions + * @notice Execute a sequence of transactions, each potentially having associated ether + * @param dest List of destination addresses for each transaction + * @param value List of ether amounts for each transaction + * @param func List of calldata for each transaction */ function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external @@ -41,6 +54,12 @@ abstract contract ExecutionManager is IExecutionManager, Authority, PluginManage } } + /** + * @dev Internal function to handle the call logic + * @param target Address of the target contract + * @param value Ether to be sent with the transaction + * @param data Calldata for the transaction + */ function _call(address target, uint256 value, bytes memory data) private executeHook(target, value, data) { assembly ("memory-safe") { let result := call(gas(), target, value, add(data, 0x20), mload(data), 0, 0) diff --git a/contracts/base/FallbackManager.sol b/contracts/base/FallbackManager.sol index 08a609bd..55f211ad 100644 --- a/contracts/base/FallbackManager.sol +++ b/contracts/base/FallbackManager.sol @@ -5,15 +5,29 @@ import "../interfaces/IFallbackManager.sol"; import "../authority/Authority.sol"; import "../libraries/AccountStorage.sol"; +/** + * @title FallbackManager + * @notice Manages the fallback behavior for the contract + * @dev Inherits functionalities from Authority and IFallbackManager + */ abstract contract FallbackManager is Authority, IFallbackManager { + /// @notice A payable function that allows the contract to receive ether receive() external payable {} + /** + * @dev Sets the address of the fallback handler contract + * @param fallbackContract The address of the new fallback handler contract + */ function _setFallbackHandler(address fallbackContract) internal { AccountStorage.layout().defaultFallbackContract = fallbackContract; } + /** + * @notice Fallback function that forwards all requests to the fallback handler contract + * @dev The request is forwarded using a STATICCALL + * It ensures that the state of the contract doesn't change even if the fallback function has state-changing operations + */ fallback() external payable { - // all requests are forwarded to the fallback contract use STATICCALL address fallbackContract = AccountStorage.layout().defaultFallbackContract; assembly { /* not memory-safe */ @@ -26,6 +40,10 @@ abstract contract FallbackManager is Authority, IFallbackManager { } } + /** + * @notice Sets the address of the fallback handler and emits the FallbackChanged event + * @param fallbackContract The address of the new fallback handler + */ function setFallbackHandler(address fallbackContract) external override onlySelfOrModule { _setFallbackHandler(fallbackContract); emit FallbackChanged(fallbackContract); diff --git a/contracts/base/ModuleManager.sol b/contracts/base/ModuleManager.sol index 3313fbe5..4cd73e7d 100644 --- a/contracts/base/ModuleManager.sol +++ b/contracts/base/ModuleManager.sol @@ -8,14 +8,21 @@ import "../interfaces/IPluginManager.sol"; import "../libraries/AddressLinkedList.sol"; import "../libraries/SelectorLinkedList.sol"; +/** + * @title ModuleManager + * @notice Manages the modules that are added to, or removed from, the wallet + * @dev Inherits functionalities from IModuleManager and Authority + */ abstract contract ModuleManager is IModuleManager, Authority { using AddressLinkedList for mapping(address => address); using SelectorLinkedList for mapping(bytes4 => bytes4); + /// @dev Returns the mapping of modules function _modulesMapping() private view returns (mapping(address => address) storage modules) { modules = AccountStorage.layout().modules; } + /// @dev Returns the mapping of module selectors function _moduleSelectorsMapping() private view @@ -23,6 +30,7 @@ abstract contract ModuleManager is IModuleManager, Authority { { moduleSelectors = AccountStorage.layout().moduleSelectors; } + /// @dev Checks if the sender is an authorized module function _isAuthorizedModule() internal view override returns (bool) { address module = msg.sender; @@ -32,14 +40,24 @@ abstract contract ModuleManager is IModuleManager, Authority { mapping(address => mapping(bytes4 => bytes4)) storage moduleSelectors = _moduleSelectorsMapping(); return moduleSelectors[module].isExist(msg.sig); } + /** + * @notice Check if a module is authorized + * @param module Address of the module + * @return A boolean indicating the authorization status + */ function isAuthorizedModule(address module) external view override returns (bool) { return _modulesMapping().isExist(module); } + /** + * @notice Add a new module + * @param moduleAndData Byte data containing the module address and initialization data + */ function addModule(bytes calldata moduleAndData) external override onlyModule { _addModule(moduleAndData); } + /// @dev Internal function to add a module function _addModule(bytes calldata moduleAndData) internal { if (moduleAndData.length < 20) { @@ -62,6 +80,10 @@ abstract contract ModuleManager is IModuleManager, Authority { aModule.walletInit(initData); emit ModuleAdded(moduleAddress); } + /** + * @notice Remove a module + * @param module Address of the module to be removed + */ function removeModule(address module) external override onlyModule { mapping(address => address) storage modules = _modulesMapping(); @@ -76,6 +98,11 @@ abstract contract ModuleManager is IModuleManager, Authority { emit ModuleRemovedWithError(module); } } + /** + * @notice List all the modules and their associated selectors + * @return modules An array of module addresses + * @return selectors A two-dimensional array of selectors + */ function listModule() external view override returns (address[] memory modules, bytes4[][] memory selectors) { mapping(address => address) storage _modules = _modulesMapping(); @@ -114,6 +141,12 @@ abstract contract ModuleManager is IModuleManager, Authority { } } } + /** + * @notice Execute a transaction from a module + * @param to Address to which the transaction should be executed + * @param value Amount of ETH (in wei) to be sent + * @param data Transaction data + */ function executeFromModule(address to, uint256 value, bytes memory data) external override onlyModule { if (to == address(this)) revert Errors.MODULE_EXECUTE_FROM_MODULE_RECURSIVE(); diff --git a/contracts/base/OwnerManager.sol b/contracts/base/OwnerManager.sol index c0ed4208..baced5db 100644 --- a/contracts/base/OwnerManager.sol +++ b/contracts/base/OwnerManager.sol @@ -7,35 +7,70 @@ import "../interfaces/IOwnerManager.sol"; import "../libraries/Bytes32LinkedList.sol"; import "../libraries/Errors.sol"; +/** + * @title OwnerManager + * @notice Manages the owners of the wallet, allowing for addition, removal, and listing of owners + * The owner should be of bytes32 type. Currently, an owner is an eoa key or the public key of the passkey + * @dev Inherits functionalities from IOwnerManager and Authority + */ abstract contract OwnerManager is IOwnerManager, Authority { using Bytes32LinkedList for mapping(bytes32 => bytes32); + /** + * @notice Helper function to get the owner mapping from account storage + * @return owners Mapping of current owners + */ function _ownerMapping() private view returns (mapping(bytes32 => bytes32) storage owners) { owners = AccountStorage.layout().owners; } + /** + * @notice Checks if the provided owner is a current owner + * @param owner Address in bytes32 format to check + * @return true if provided owner is a current owner, false otherwise + */ function _isOwner(bytes32 owner) internal view override returns (bool) { return _ownerMapping().isExist(owner); } + /** + * @notice External function to check if the provided owner is a current owner + * @param owner Address in bytes32 format to check + * @return true if provided owner is a current owner, false otherwise + */ function isOwner(bytes32 owner) external view override returns (bool) { return _isOwner(owner); } + /** + * @notice Clears all owners + */ function _clearOwner() private { _ownerMapping().clear(); emit OwnerCleared(); } + /** + * @notice Resets the owner to a new owner + * @param newOwner The new owner address in bytes32 format + */ function resetOwner(bytes32 newOwner) external override onlySelfOrModule { _clearOwner(); _addOwner(newOwner); } + /** + * @notice Resets the owners to a new set of owners + * @param newOwners An array of new owner addresses in bytes32 format + */ function resetOwners(bytes32[] calldata newOwners) external override onlySelfOrModule { _clearOwner(); _addOwners(newOwners); } + /** + * @notice Adds multiple owners + * @param owners An array of owner addresses in bytes32 format to add + */ function _addOwners(bytes32[] calldata owners) internal { for (uint256 i = 0; i < owners.length;) { @@ -45,19 +80,35 @@ abstract contract OwnerManager is IOwnerManager, Authority { } } } + /** + * @notice Adds a single owner + * @param owner The owner address in bytes32 format to add + */ function addOwner(bytes32 owner) external override onlySelfOrModule { _addOwner(owner); } + /** + * @notice Adds multiple owners + * @param owners An array of owner addresses in bytes32 format to add + */ function addOwners(bytes32[] calldata owners) external override onlySelfOrModule { _addOwners(owners); } + /** + * @notice Adds a single owner + * @param owner The owner address in bytes32 format to add + */ function _addOwner(bytes32 owner) internal { _ownerMapping().add(owner); emit OwnerAdded(owner); } + /** + * @notice Removes a single owner + * @param owner The owner address in bytes32 format to remove + */ function removeOwner(bytes32 owner) external override onlySelfOrModule { _ownerMapping().remove(owner); @@ -66,6 +117,10 @@ abstract contract OwnerManager is IOwnerManager, Authority { } emit OwnerRemoved(owner); } + /** + * @notice Lists all current owners + * @return owners An array of current owner addresses in bytes32 format + */ function listOwner() external view override returns (bytes32[] memory owners) { uint256 size = _ownerMapping().size(); diff --git a/contracts/base/PluginManager.sol b/contracts/base/PluginManager.sol index 40f68b1c..fed9b293 100644 --- a/contracts/base/PluginManager.sol +++ b/contracts/base/PluginManager.sol @@ -9,12 +9,20 @@ import "../libraries/AddressLinkedList.sol"; import "../interfaces/IPluggable.sol"; import "../interfaces/IPluginStorage.sol"; +/** + * @title PluginManager + * @dev This contract manages plugins and provides associated utility functions + */ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { uint8 private constant _GUARD_HOOK = 1 << 0; uint8 private constant _PRE_HOOK = 1 << 1; uint8 private constant _POST_HOOK = 1 << 2; using AddressLinkedList for mapping(address => address); + /** + * @dev Adds a new plugin + * @param pluginAndData The plugin address and associated data + */ function addPlugin(bytes calldata pluginAndData) external override onlyModule { _addPlugin(pluginAndData); @@ -52,6 +60,10 @@ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { } emit PluginAdded(pluginAddress); } + /** + * @dev Removes a plugin + * @param plugin Address of the plugin to be removed + */ function removePlugin(address plugin) external override onlyModule { AccountStorage.Layout storage l = AccountStorage.layout(); @@ -66,10 +78,20 @@ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { l.preHookPlugins.tryRemove(plugin); l.postHookPlugins.tryRemove(plugin); } + /** + * @dev Checks if a plugin is authorized + * @param plugin Address of the plugin + * @return Returns true if the plugin is authorized, false otherwise + */ function isAuthorizedPlugin(address plugin) external view override returns (bool) { return AccountStorage.layout().plugins.isExist(plugin); } + /** + * @dev Lists plugins based on the hook type + * @param hookType Type of the hook + * @return plugins An array of plugin addresses + */ function listPlugin(uint8 hookType) external view override returns (address[] memory plugins) { if (hookType == 0) { @@ -120,6 +142,13 @@ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { _cursorFrom = cursor; } } + /** + * @dev Hooks a user operation with associated data + * @param userOp User operation details + * @param userOpHash Hash of the user operation + * @param guardHookData Data for the guard hook + * @return Returns true if the hook was successful, false otherwise + */ function guardHook(UserOperation calldata userOp, bytes32 userOpHash, bytes calldata guardHookData) internal @@ -178,6 +207,12 @@ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { } return true; } + /** + * @dev Executes hooks around a transaction + * @param target Address of the transaction target + * @param value Amount of ether to send with the transaction + * @param data Data of the transaction + */ modifier executeHook(address target, uint256 value, bytes memory data) { AccountStorage.Layout storage l = AccountStorage.layout(); @@ -216,6 +251,9 @@ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { success := call(gas(), target, 0, add(data, 0x20), mload(data), 0, 0) } } + /** + * @dev Ensures that the function is only called by an authorized plugin + */ modifier onlyPlugin() { if (AccountStorage.layout().plugins[msg.sender] == address(0)) { @@ -223,10 +261,21 @@ abstract contract PluginManager is IPluginManager, Authority, IPluginStorage { } _; } + /** + * @dev Stores data for a plugin + * @param key Key to store the data against + * @param value Data to be stored + */ function pluginDataStore(bytes32 key, bytes calldata value) external override onlyPlugin { AccountStorage.layout().pluginDataBytes[msg.sender][key] = value; } + /** + * @dev Loads data of a plugin + * @param plugin Address of the plugin + * @param key Key for which data needs to be loaded + * @return Returns the loaded data + */ function pluginDataLoad(address plugin, bytes32 key) external view override returns (bytes memory) { return AccountStorage.layout().pluginDataBytes[plugin][key]; diff --git a/contracts/base/UpgradeManager.sol b/contracts/base/UpgradeManager.sol index 02e7babb..11817e8c 100644 --- a/contracts/base/UpgradeManager.sol +++ b/contracts/base/UpgradeManager.sol @@ -3,13 +3,21 @@ pragma solidity ^0.8.20; import "../interfaces/IUpgradable.sol"; import "../libraries/Errors.sol"; +/** + * @title UpgradeManager + * @dev This contract allows for the logic of a proxy to be upgraded + */ abstract contract UpgradeManager is IUpgradable { /** - * @dev Storage slot with the address of the current implementation. + * @dev Storage slot with the address of the current implementation * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1 */ bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + /** + * @dev Upgrades the logic to a new implementation + * @param newImplementation Address of the new implementation + */ function _upgradeTo(address newImplementation) internal { bool isContract; diff --git a/contracts/base/Validator.sol b/contracts/base/Validator.sol index 08096090..191ff4c0 100644 --- a/contracts/base/Validator.sol +++ b/contracts/base/Validator.sol @@ -2,7 +2,15 @@ pragma solidity ^0.8.20; import "../interfaces/IValidator.sol"; +/** + * @title Validator + * @dev This abstract contract provides a method to retrieve an IValidator interface + */ abstract contract Validator { + /** + * @dev Gets the IValidator interface + * @return An instance of the IValidator interface + */ function validator() public view virtual returns (IValidator); } diff --git a/contracts/base/ValidatorManager.sol b/contracts/base/ValidatorManager.sol index 6d8cb603..61865d02 100644 --- a/contracts/base/ValidatorManager.sol +++ b/contracts/base/ValidatorManager.sol @@ -4,12 +4,25 @@ pragma solidity ^0.8.20; import "../interfaces/IValidator.sol"; import "./Validator.sol"; +/** + * @title ValidatorManager + * @dev This abstract contract extends the Validator contract and manages a single instance of IValidator + */ abstract contract ValidatorManager is Validator { + /// @dev The IValidator interface instance IValidator private immutable _VALIDATOR; + /** + * @dev Constructs the ValidatorManager contracs + * @param aValidator The IValidator interface instance + */ constructor(IValidator aValidator) { _VALIDATOR = aValidator; } + /** + * @dev Gets the IValidator interface instance + * @return The IValidator interface instance + */ function validator() public view override returns (IValidator) { return _VALIDATOR; diff --git a/contracts/handler/ERC1271Handler.sol b/contracts/handler/ERC1271Handler.sol index 6043872f..90cd3a7b 100644 --- a/contracts/handler/ERC1271Handler.sol +++ b/contracts/handler/ERC1271Handler.sol @@ -7,15 +7,31 @@ import "../interfaces/IERC1271Handler.sol"; import "../authority/Authority.sol"; import "../libraries/AccountStorage.sol"; +/** + * @title ERC1271Handler + * @dev This contract provides functionality to handle ERC1271 signature validations + */ abstract contract ERC1271Handler is Authority, IERC1271Handler, SignatureValidator { + // Magic value indicating a valid signature for ERC-1271 contracts // bytes4(keccak256("isValidSignature(bytes32,bytes)") bytes4 internal constant MAGICVALUE = 0x1626ba7e; + // Constants indicating different invalid states bytes4 internal constant INVALID_ID = 0xffffffff; bytes4 internal constant INVALID_TIME_RANGE = 0xfffffffe; + /** + * @dev Provides access to the mapping of approved hashes from the AccountStorage + * @return The mapping of approved hashes + */ function _approvedHashes() private view returns (mapping(bytes32 => uint256) storage) { return AccountStorage.layout().approvedHashes; } + /** + * @dev Checks if a given signature is valid for the provided hash + * @param rawHash The raw hash to check the signature against + * @param signature The provided signature + * @return magicValue A bytes4 magic value indicating the result of the signature check + */ function isValidSignature(bytes32 rawHash, bytes calldata signature) external @@ -49,6 +65,10 @@ abstract contract ERC1271Handler is Authority, IERC1271Handler, SignatureValidat return INVALID_ID; } } + /** + * @dev Approves a given hash + * @param hash The hash to be approved + */ function approveHash(bytes32 hash) external override onlySelfOrModule { mapping(bytes32 => uint256) storage approvedHashes = _approvedHashes(); @@ -58,6 +78,10 @@ abstract contract ERC1271Handler is Authority, IERC1271Handler, SignatureValidat approvedHashes[hash] = 1; emit ApproveHash(hash); } + /** + * @dev Rejects a given hash + * @param hash The hash to be rejected + */ function rejectHash(bytes32 hash) external override onlySelfOrModule { mapping(bytes32 => uint256) storage approvedHashes = _approvedHashes(); diff --git a/contracts/helper/SignatureValidator.sol b/contracts/helper/SignatureValidator.sol index bbb86552..c8037e2a 100644 --- a/contracts/helper/SignatureValidator.sol +++ b/contracts/helper/SignatureValidator.sol @@ -8,13 +8,31 @@ import "../libraries/Errors.sol"; import "../libraries/TypeConversion.sol"; import "../libraries/SignatureDecoder.sol"; +/** + * @title SignatureValidator + * @dev This contract provides functionality for validating cryptographic signatures + */ abstract contract SignatureValidator is OwnerAuth, Validator { using ECDSA for bytes32; using TypeConversion for address; + /** + * @dev Encodes the raw hash using a validator to prevent replay attacks + * If the same owner signs the message for different smart contract accounts, + * this function uses EIP-712-like encoding to encode the raw hash + * @param rawHash The raw hash to encode + * @return encodeRawHash The encoded hash + */ function _encodeRawHash(bytes32 rawHash) internal view returns (bytes32 encodeRawHash) { return validator().encodeRawHash(rawHash); } + /** + * @dev Validates an EIP1271 signature + * @param rawHash The raw hash against which the signature is to be checked + * @param rawSignature The signature to validate + * @return validationData The data used for validation + * @return sigValid A boolean indicating if the signature is valid or not + */ function _isValidate1271Signature(bytes32 rawHash, bytes calldata rawSignature) internal @@ -39,6 +57,14 @@ abstract contract SignatureValidator is OwnerAuth, Validator { sigValid = _isOwner(recovered); } } + /** + * @dev Validates a user operation signature + * @param userOpHash The hash of the user operation + * @param userOpSignature The signature of the user operation + * @return validationData same as defined in EIP4337 + * @return sigValid A boolean indicating if the signature is valid or not + * @return guardHookInputData Input data for the guard hook + */ function _isValidUserOp(bytes32 userOpHash, bytes calldata userOpSignature) internal diff --git a/contracts/interfaces/IERC1271Handler.sol b/contracts/interfaces/IERC1271Handler.sol index cf6c8d2f..5a19ec0f 100644 --- a/contracts/interfaces/IERC1271Handler.sol +++ b/contracts/interfaces/IERC1271Handler.sol @@ -3,10 +3,31 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/interfaces/IERC1271.sol"; +/** + * @title IERC1271Handler + * @dev This interface extends the IERC1271 interface by adding functionality to approve and reject hashes + * The main intention is to manage the approval status of specific signed hashes + */ interface IERC1271Handler is IERC1271 { + /** + * @dev Emitted when a hash has been approved. + * @param hash The approved hash. + */ event ApproveHash(bytes32 indexed hash); + /** + * @dev Emitted when a hash has been rejected + * @param hash The rejected hash + */ event RejectHash(bytes32 indexed hash); + /** + * @notice Approves the given hash + * @param hash The hash to approve + */ function approveHash(bytes32 hash) external; + /** + * @notice Rejects the given hash + * @param hash The hash to reject + */ function rejectHash(bytes32 hash) external; } diff --git a/contracts/interfaces/IExecutionManager.sol b/contracts/interfaces/IExecutionManager.sol index 9a6b5822..76695eaf 100644 --- a/contracts/interfaces/IExecutionManager.sol +++ b/contracts/interfaces/IExecutionManager.sol @@ -1,19 +1,36 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title IExecutionManager + * @dev Interface for executing transactions or batch of transactions + * The execution can be a single transaction or multiple transactions in sequence + */ interface IExecutionManager { /** - * execute a transaction (called directly from owner, or by entryPoint) + * @notice Executes a single transaction + * @dev This can be invoked directly by the owner or by an entry point + * + * @param dest The destination address for the transaction + * @param value The amount of Ether (in wei) to transfer along with the transaction. Can be 0 for non-ETH transfers + * @param func The function call data to be executed */ function execute(address dest, uint256 value, bytes calldata func) external; /** - * execute a sequence of transactions + * @notice Executes a sequence of transactions with the same Ether value for each + * @dev All transactions in the batch will carry 0 Ether value + * @param dest An array of destination addresses for each transaction in the batch + * @param func An array of function call data for each transaction in the batch */ function executeBatch(address[] calldata dest, bytes[] calldata func) external; /** - * execute a sequence of transactions + * @notice Executes a sequence of transactions with specified Ether values for each + * @dev The values for Ether transfer are specified for each transaction + * @param dest An array of destination addresses for each transaction in the batch + * @param value An array of amounts of Ether (in wei) to transfer for each transaction in the batch + * @param func An array of function call data for each transaction in the batch */ function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external; } diff --git a/contracts/interfaces/IFallbackManager.sol b/contracts/interfaces/IFallbackManager.sol index 782aa535..88ac8cb6 100644 --- a/contracts/interfaces/IFallbackManager.sol +++ b/contracts/interfaces/IFallbackManager.sol @@ -1,8 +1,23 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title IFallbackManager + * @dev Interface for setting and managing the fallback contract. + * The fallback contract is called when no other function matches the provided function signature. + */ interface IFallbackManager { + /** + * @notice Emitted when the fallback contract is changed + * @param fallbackContract The address of the newly set fallback contract + */ event FallbackChanged(address indexed fallbackContract); + /** + * @notice Set a new fallback contract + * @dev This function allows setting a new address as the fallback contract. The fallback contract will receive + * all calls made to this contract that do not match any other function + * @param fallbackContract The address of the fallback contract to be set + */ function setFallbackHandler(address fallbackContract) external; } diff --git a/contracts/interfaces/IModule.sol b/contracts/interfaces/IModule.sol index 61a021a6..530329eb 100644 --- a/contracts/interfaces/IModule.sol +++ b/contracts/interfaces/IModule.sol @@ -3,6 +3,22 @@ pragma solidity ^0.8.20; import "./IPluggable.sol"; +/** + * @title Module Interface + * @dev This interface defines the funcations that a module needed access in the smart contract wallet + * Modules are key components that can be plugged into the main contract to enhance its functionalities + * For security reasons, a module can only call functions in the smart contract that it has explicitly + * listed via the `requiredFunctions` method + */ interface IModule is IPluggable { + /** + * @notice Provides a list of function selectors that the module is allowed to call + * within the smart contract. When a module is added to the smart contract, it's restricted + * to only call these functions. This ensures that modules have explicit and limited permissions, + * enhancing the security of the smart contract (e.g., a "Daily Limit" module shouldn't be able to + * change the owner) + * + * @return An array of function selectors that this module is permitted to call + */ function requiredFunctions() external pure returns (bytes4[] memory); } diff --git a/contracts/interfaces/IModuleManager.sol b/contracts/interfaces/IModuleManager.sol index 773f23e0..f8a95eab 100644 --- a/contracts/interfaces/IModuleManager.sol +++ b/contracts/interfaces/IModuleManager.sol @@ -3,18 +3,60 @@ pragma solidity ^0.8.20; import "./IModule.sol"; +/** + * @title Module Manager Interface + * @dev This interface defines the management functionalities for handling modules + * within the system. Modules are components that can be added to or removed from the + * smart contract to extend its functionalities. The manager ensures that only authorized + * modules can execute certain functionalities + */ interface IModuleManager { + /** + * @notice Emitted when a new module is successfully added + * @param module The address of the newly added module + */ event ModuleAdded(address indexed module); + /** + * @notice Emitted when a module is successfully removed + * @param module The address of the removed module + */ event ModuleRemoved(address indexed module); + /** + * @notice Emitted when there's an error while removing a module + * @param module The address of the module that was attempted to be removed + */ event ModuleRemovedWithError(address indexed module); + /** + * @notice Adds a new module to the system + * @param moduleAndData The module to be added and its associated initialization data + */ function addModule(bytes calldata moduleAndData) external; + /** + * @notice Removes a module from the system + * @param module The address of the module to be removed + */ + function removeModule(address module) external; - function removeModule(address) external; - + /** + * @notice Checks if a module is authorized within the system + * @param module The address of the module to check + * @return True if the module is authorized, false otherwise + */ function isAuthorizedModule(address module) external returns (bool); - + /** + * @notice Provides a list of all added modules and their respective authorized function selectors + * @return modules An array of the addresses of all added modules + * @return selectors A 2D array where each inner array represents the function selectors + * that the corresponding module in the 'modules' array is allowed to call + */ function listModule() external view returns (address[] memory modules, bytes4[][] memory selectors); - + /** + * @notice Allows a module to execute a function within the system. This ensures that the + * module can only call functions it is permitted to, based on its declared `requiredFunctions` + * @param dest The address of the destination contract where the function will be executed + * @param value The amount of ether (in wei) to be sent with the function call + * @param func The function data to be executed + */ function executeFromModule(address dest, uint256 value, bytes calldata func) external; } diff --git a/contracts/interfaces/IOwnerManager.sol b/contracts/interfaces/IOwnerManager.sol index 4f7735df..9271f9fc 100644 --- a/contracts/interfaces/IOwnerManager.sol +++ b/contracts/interfaces/IOwnerManager.sol @@ -1,16 +1,71 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title Owner Manager Interface + * @dev This interface defines the management functionalities for handling owners within the system. + * Owners are identified by a unique bytes32 ID. This design allows for a flexible representation + * of ownership – whether it be an Ethereum address, a hash of an off-chain public key, or any other + * unique identifier. + */ interface IOwnerManager { + /** + * @notice Emitted when a new owner is successfully added + * @param owner The bytes32 ID of the newly added owner + */ event OwnerAdded(bytes32 indexed owner); + + /** + * @notice Emitted when an owner is successfully removed + * @param owner The bytes32 ID of the removed owner + */ event OwnerRemoved(bytes32 indexed owner); + + /** + * @notice Emitted when all owners are cleared from the system + */ event OwnerCleared(); + /** + * @notice Checks if a given bytes32 ID corresponds to an owner within the system + * @param owner The bytes32 ID to check + * @return True if the ID corresponds to an owner, false otherwise + */ function isOwner(bytes32 owner) external view returns (bool); + + /** + * @notice Adds a new owner to the system + * @param owner The bytes32 ID of the owner to be added + */ function addOwner(bytes32 owner) external; + + /** + * @notice Removes an existing owner from the system + * @param owner The bytes32 ID of the owner to be removed + */ function removeOwner(bytes32 owner) external; + + /** + * @notice Resets the entire owner set, replacing it with a single new owner + * @param newOwner The bytes32 ID of the new owner + */ function resetOwner(bytes32 newOwner) external; + + /** + * @notice Adds multiple new owners to the system + * @param owners An array of bytes32 IDs representing the owners to be added + */ function addOwners(bytes32[] calldata owners) external; + + /** + * @notice Resets the entire owner set, replacing it with a new set of owners + * @param newOwners An array of bytes32 IDs representing the new set of owners + */ function resetOwners(bytes32[] calldata newOwners) external; + + /** + * @notice Provides a list of all added owners + * @return owners An array of bytes32 IDs representing the owners + */ function listOwner() external view returns (bytes32[] memory owners); } diff --git a/contracts/interfaces/IPluggable.sol b/contracts/interfaces/IPluggable.sol index 398db744..47d252af 100644 --- a/contracts/interfaces/IPluggable.sol +++ b/contracts/interfaces/IPluggable.sol @@ -3,7 +3,19 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +/** + * @title Pluggable Interface + * @dev This interface provides functionalities for initializing and deinitializing wallet-related plugins or modules + */ interface IPluggable is IERC165 { + /** + * @notice Initializes a specific module or plugin for the wallet with the provided data + * @param data Initialization data required for the module or plugin + */ function walletInit(bytes calldata data) external; + + /** + * @notice Deinitializes a specific module or plugin from the wallet + */ function walletDeInit() external; } diff --git a/contracts/interfaces/IPlugin.sol b/contracts/interfaces/IPlugin.sol index c7870809..8a14d49a 100644 --- a/contracts/interfaces/IPlugin.sol +++ b/contracts/interfaces/IPlugin.sol @@ -4,23 +4,42 @@ pragma solidity ^0.8.20; import "@account-abstraction/contracts/interfaces/UserOperation.sol"; import "./IPluggable.sol"; +/** + * @title Plugin Interface + * @dev This interface provides functionalities for hooks and interactions of plugins within a wallet or contract + */ interface IPlugin is IPluggable { /** - * @dev - * hookType structure: - * GuardHook: 1<<0 - * PreHook: 1<<1 - * PostHook: 1<<2 + * @notice Specifies the types of hooks a plugin supports + * @return hookType An 8-bit value where: + * - GuardHook is represented by 1<<0 + * - PreHook is represented by 1<<1 + * - PostHook is represented by 1<<2 */ function supportsHook() external pure returns (uint8 hookType); /** - * @dev For flexibility, guardData does not participate in the userOp signature verification. - * Plugins must revert when they do not need guardData but guardData.length > 0(for security reasons) + * @notice A hook that guards the user operation + * @dev For security, plugins should revert when they do not need guardData but guardData.length > 0 + * @param userOp The user operation being performed + * @param userOpHash The hash of the user operation + * @param guardData Additional data for the guard */ function guardHook(UserOperation calldata userOp, bytes32 userOpHash, bytes calldata guardData) external; + /** + * @notice A hook that's executed before the actual operation + * @param target The target address of the operation + * @param value The amount of ether (in wei) involved in the operation + * @param data The calldata for the operation + */ function preHook(address target, uint256 value, bytes calldata data) external; + /** + * @notice A hook that's executed after the actual operation + * @param target The target address of the operation + * @param value The amount of ether (in wei) involved in the operation + * @param data The calldata for the operation + */ function postHook(address target, uint256 value, bytes calldata data) external; } diff --git a/contracts/interfaces/IPluginManager.sol b/contracts/interfaces/IPluginManager.sol index 84e0908f..a67c0cc2 100644 --- a/contracts/interfaces/IPluginManager.sol +++ b/contracts/interfaces/IPluginManager.sol @@ -3,16 +3,38 @@ pragma solidity ^0.8.20; import "./IPlugin.sol"; +/** + * @title Plugin Manager Interface + * @dev This interface provides functionalities for adding, removing, and querying plugins + */ interface IPluginManager { event PluginAdded(address indexed plugin); event PluginRemoved(address indexed plugin); event PluginRemovedWithError(address indexed plugin); + /** + * @notice Add a new plugin along with its initialization data + * @param pluginAndData The plugin address concatenated with its initialization data + */ function addPlugin(bytes calldata pluginAndData) external; + /** + * @notice Remove a plugin from the system + * @param plugin The address of the plugin to be removed + */ function removePlugin(address plugin) external; + /** + * @notice Checks if a plugin is authorized + * @param plugin The address of the plugin to check + * @return True if the plugin is authorized, otherwise false + */ function isAuthorizedPlugin(address plugin) external returns (bool); + /** + * @notice List all plugins of a specific hook type + * @param hookType The type of the hook for which to list plugins + * @return plugins An array of plugin addresses corresponding to the hookType + */ function listPlugin(uint8 hookType) external view returns (address[] memory plugins); } diff --git a/contracts/interfaces/IPluginStorage.sol b/contracts/interfaces/IPluginStorage.sol index 8de6eec2..da94f01b 100644 --- a/contracts/interfaces/IPluginStorage.sol +++ b/contracts/interfaces/IPluginStorage.sol @@ -1,7 +1,23 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title Plugin Storage Interface + * @dev This interface defines the functionalities to store and load data for plugins + */ interface IPluginStorage { + /** + * @notice Store data for a plugin + * @param key The key under which the value should be stored + * @param value The value to be stored + */ function pluginDataStore(bytes32 key, bytes calldata value) external; + + /** + * @notice Load data for a specific plugin using a key + * @param plugin The address of the plugin for which data should be loaded + * @param key The key under which the data is stored + * @return The data stored under the given key for the specified plugin + */ function pluginDataLoad(address plugin, bytes32 key) external view returns (bytes memory); } diff --git a/contracts/interfaces/ISoulWallet.sol b/contracts/interfaces/ISoulWallet.sol index 37f19321..28696a65 100644 --- a/contracts/interfaces/ISoulWallet.sol +++ b/contracts/interfaces/ISoulWallet.sol @@ -9,6 +9,12 @@ import "./IFallbackManager.sol"; import "@account-abstraction/contracts/interfaces/IAccount.sol"; import "./IUpgradable.sol"; +/** + * @title SoulWallet Interface + * @dev This interface aggregates multiple sub-interfaces to represent the functionalities of the SoulWallet + * It encompasses account management, execution management, module management, owner management, plugin management, + * fallback management, and upgradeability + */ interface ISoulWallet is IAccount, IExecutionManager, diff --git a/contracts/interfaces/IUpgradable.sol b/contracts/interfaces/IUpgradable.sol index 5e4fd798..9f82d92e 100644 --- a/contracts/interfaces/IUpgradable.sol +++ b/contracts/interfaces/IUpgradable.sol @@ -1,9 +1,23 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title Upgradable Interface + * @dev This interface provides functionalities to upgrade the implementation of a contract + * It emits an event when the implementation is changed, either to a new version or from an old version + */ interface IUpgradable { event Upgraded(address indexed oldImplementation, address indexed newImplementation); + /** + * @dev Upgrade the current implementation to the provided new implementation address + * @param newImplementation The address of the new contract implementation + */ function upgradeTo(address newImplementation) external; + + /** + * @dev Upgrade from the current implementation, given the old implementation address + * @param oldImplementation The address of the old contract implementation that is being replaced + */ function upgradeFrom(address oldImplementation) external; } diff --git a/contracts/interfaces/IValidator.sol b/contracts/interfaces/IValidator.sol index 433381ae..5bb35675 100644 --- a/contracts/interfaces/IValidator.sol +++ b/contracts/interfaces/IValidator.sol @@ -1,15 +1,41 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title Validator Interface + * @dev This interface defines the functionalities for signature validation and hash encoding + */ interface IValidator { + /** + * @dev Recover the signer of a given raw hash using the provided raw signature + * @param rawHash The raw hash that was signed + * @param rawSignature The signature data + * @return validationData same as defined in EIP4337 + * @return recovered The recovered signer's signing key from the signature + * @return success A boolean indicating the success of the recovery + */ function recoverSignature(bytes32 rawHash, bytes calldata rawSignature) external view returns (uint256 validationData, bytes32 recovered, bool success); + + /** + * @dev Recover the signer of a given raw hash using the provided raw signature according to EIP-1271 standards + * @param rawHash The raw hash that was signed + * @param rawSignature The signature data + * @return validationData same as defined in EIP4337 + * @return recovered The recovered signer's signing key from the signature + * @return success A boolean indicating the success of the recovery + */ function recover1271Signature(bytes32 rawHash, bytes calldata rawSignature) external view returns (uint256 validationData, bytes32 recovered, bool success); + /** + * @dev Encode a raw hash to prevent replay attacks + * @param rawHash The raw hash to encode + * @return The encoded hash + */ function encodeRawHash(bytes32 rawHash) external view returns (bytes32); } diff --git a/contracts/interfaces/IValidatorManager.sol b/contracts/interfaces/IValidatorManager.sol index 464cb60f..48cd1da6 100644 --- a/contracts/interfaces/IValidatorManager.sol +++ b/contracts/interfaces/IValidatorManager.sol @@ -3,6 +3,14 @@ pragma solidity ^0.8.20; import "./IValidator.sol"; +/** + * @title Validator Manager Interface + * @dev This interface provides a method to retrieve the active validator instance + */ interface IValidatorManager { + /** + * @dev Returns the current active validator instance + * @return The active validator instance + */ function validator() external view returns (IValidator); } diff --git a/contracts/keystore/L1/BaseKeyStore.sol b/contracts/keystore/L1/BaseKeyStore.sol index 4ff4e5b7..acd81467 100644 --- a/contracts/keystore/L1/BaseKeyStore.sol +++ b/contracts/keystore/L1/BaseKeyStore.sol @@ -6,15 +6,15 @@ import "../../libraries/KeyStoreSlotLib.sol"; abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { /** - * @dev Verify the signature of the `signKey` - * @param slot KeyStore slot - * @param slotNonce used to prevent replay attack - * @param signKey Current sign key - * @param action Action type, See ./interfaces/IKeyStore.sol: enum Action - * @param data {new key(Action.SET_KEY) | new guardian hash(Action.SET_GUARDIAN) | new guardian safe period(Action.SET_GUARDIAN_SAFE_PERIOD) | empty(Action.CANCEL_SET_GUARDIAN | Action.CANCEL_SET_GUARDIAN_SAFE_PERIOD )} - * @param keySignature `signature of current sign key` - * - * Note Implementer must revert if the signature is invalid + * @notice Verify the signature of the `signKey` + * @dev Implementers must revert if the signature is invalid + * @param slot The KeyStore slot + * @param slotNonce Used to prevent replay attacks + * @param signKey The current sign key + * @param action The action type + * @param data Data associated with the action + * @param rawOwners Raw owner data + * @param keySignature Signature of the current sign key */ function verifySignature( bytes32 slot, @@ -27,12 +27,12 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { ) internal virtual; /** - * @dev Verify the signature of the `guardian` - * @param slot KeyStore slot - * @param slotNonce used to prevent replay attack + * @notice Verify the signature of the `guardian` + * @param slot The KeyStore slot + * @param slotNonce Used to prevent replay attacks * @param rawGuardian The raw data of the `guardianHash` - * @param newKey New key - * @param guardianSignature `signature of current guardian` + * @param newKey The new key + * @param guardianSignature Signature of the guardians */ function verifyGuardianSignature( bytes32 slot, @@ -67,6 +67,11 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { verifyGuardianSignature(slot, _getNonce(slot), rawGuardian, newKey, guardianSignature); _increaseNonce(slot); } + /** + * @notice View the nonce associated with a slot + * @param slot The KeyStore slot + * @return _nonce The nonce + */ function nonce(bytes32 slot) external view override returns (uint256 _nonce) { return _getNonce(slot); @@ -79,6 +84,13 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { { return KeyStoreSlotLib.getSlot(initialKeyHash, initialGuardianHash, guardianSafePeriod); } + /** + * @notice Calculate and retrieve the slot based on provided initial values + * @param initialKeyHash The initial key hash + * @param initialGuardianHash The initial guardian hash + * @param guardianSafePeriod The guardian safe period + * @return slot The KeyStore slot + */ function getSlot(bytes32 initialKeyHash, bytes32 initialGuardianHash, uint256 guardianSafePeriod) external @@ -89,6 +101,11 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { return _getSlot(initialKeyHash, initialGuardianHash, guardianSafePeriod); } + /** + * @notice View the key associated with a slot + * @param slot The KeyStore slot + * @return key The key + */ function getKey(bytes32 slot) external view override returns (bytes32 key) { return _getKey(slot); } @@ -162,11 +179,11 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Change the key (only slot initialized) - * @param slot KeyStore slot - * @param newRawOwners New owners - * @param currentRawOwners current owners - * @param keySignature `signature of current key` + * @notice Set the key for a slot that is already initialized + * @param slot The KeyStore slot + * @param newRawOwners The new owners + * @param currentRawOwners The current owners + * @param keySignature Signature of the owners */ function setKeyByOwner( bytes32 slot, @@ -179,14 +196,13 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Change the key (only slot not initialized) - * @param initialKeyHash Initial key hash - * @param initialGuardianHash Initial guardian hash - * @param initialGuardianSafePeriod Initial guardian safe period - * @param newRawOwners New owners - * @param currentRawOwners current owners - * @param keySignature one of the current owners signature - * + * @notice Set the key for a slot that is not yet initialized + * @param initialKeyHash The initial key hash + * @param initialGuardianHash The initial guardian hash + * @param initialGuardianSafePeriod The initial guardian safe period + * @param newRawOwners The new owners + * @param currentRawOwners The current owners + * @param keySignature One of the current owners' signature */ function setKeyByOwner( bytes32 initialKeyHash, @@ -201,13 +217,13 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Social recovery, change the key (only slot not initialized) - * @param initialKeyHash Initial key hash - * @param initialGuardianHash Initial guardian hash - * @param initialGuardianSafePeriod Initial guardian safe period - * @param newRawOwners New Owners - * @param rawGuardian `raw guardian data` - * @param guardianSignature `signature of initialGuardian` + * @notice Set the key for a slot using social recovery + * @param initialKeyHash The initial key hash + * @param initialGuardianHash The initial guardian hash + * @param initialGuardianSafePeriod The initial guardian safe period + * @param newRawOwners The new owners + * @param rawGuardian Raw guardian data + * @param guardianSignature Signature of the initial guardians */ function setKeyByGuardian( bytes32 initialKeyHash, @@ -226,11 +242,12 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Social recovery, change the key (only slot initialized) - * @param slot KeyStore slot - * @param newRawOwners New Owners - * @param rawGuardian `raw guardian data` - * @param guardianSignature `signature of current guardian` + * @notice Social recovery to change the key using the guardian's signature + * @dev Only works for initialized slots + * @param slot The KeyStore slot identifier + * @param newRawOwners The new raw owner data + * @param rawGuardian The raw data of the guardian + * @param guardianSignature The signature of the guardians */ function setKeyByGuardian( bytes32 slot, @@ -246,8 +263,9 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Get all data stored in the slot. See ./interfaces/IKeyStore.sol: struct keyStoreInfo - * @param slot KeyStore slot + * @notice Fetches all the data stored in a KeyStore slot + * @param slot The KeyStore slot identifier + * @return _keyStoreInfo The keyStoreInfo struct with all the data from the slot */ function getKeyStoreInfo(bytes32 slot) external view override returns (keyStoreInfo memory _keyStoreInfo) { return _getKeyStoreInfo(slot); @@ -262,25 +280,30 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Get guardian hash from raw guardian data - * @param rawGuardian `raw guardian data` + * @notice Computes the hash of a raw guardian data + * @param rawGuardian The raw data of the guardian + * @return guardianHash The computed hash of the provided guardian data */ function getGuardianHash(bytes memory rawGuardian) external pure override returns (bytes32 guardianHash) { return _getGuardianHash(rawGuardian); } /** - * @dev calculate key hash + * @notice Computes the key hash from raw owner data + * @param rawOwners The raw owner data + * @return key The computed key hash */ function getOwnersKeyHash(bytes memory rawOwners) external pure override returns (bytes32 key) { return _getOwnersHash(rawOwners); } /** - * @dev Change guardian hash (only slot initialized) - * @param slot KeyStore slot - * @param newGuardianHash New guardian hash - * @param keySignature `signature of current key` + * @notice Change the guardian hash for a given KeyStore slot + * @dev Only works for initialized slots + * @param slot The KeyStore slot identifier + * @param newGuardianHash The new guardian hash + * @param rawOwners The raw owner data + * @param keySignature The signature of the owner key */ function setGuardian(bytes32 slot, bytes32 newGuardianHash, bytes calldata rawOwners, bytes calldata keySignature) external @@ -302,12 +325,14 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Change guardian hash (only slot not initialized) - * @param initialKeyHash Initial key hash - * @param initialGuardianHash Initial guardian hash - * @param initialGuardianSafePeriod Initial guardian safe period - * @param newGuardianHash New guardian hash - * @param keySignature one of the current owners signature + * @notice Change the guardian hash for a KeyStore slot using initialization data + * @dev for slots that are not initialized + * @param initialKeyHash The initial key hash + * @param initialGuardianHash The initial guardian hash + * @param initialGuardianSafePeriod The initial guardian safe period duration + * @param newGuardianHash The new guardian hash + * @param rawOwners The raw owner data + * @param keySignature A signature from one of the current owners */ function setGuardian( bytes32 initialKeyHash, @@ -331,9 +356,10 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Cancel the pending guardianHash change - * @param slot KeyStore slot - * @param keySignature `signature of current key` + * @notice Cancels a pending change of the guardian hash for a given KeyStore slot + * @param slot The KeyStore slot identifier + * @param rawOwners The raw owner data + * @param keySignature The signature of the current key */ function cancelSetGuardian(bytes32 slot, bytes calldata rawOwners, bytes calldata keySignature) external override { _autoSetupGuardian(slot); @@ -350,10 +376,12 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Change guardian safe period (only slot initialized) - * @param slot KeyStore slot - * @param newGuardianSafePeriod New guardian safe period - * @param keySignature `signature of current key` + * @notice Changes the guardian safe period for a given KeyStore slot + * @dev using for initialized slots + * @param slot The KeyStore slot identifier + * @param newGuardianSafePeriod The new duration of the guardian safe period + * @param rawOwners The raw owner data + * @param keySignature The signature of the current key */ function setGuardianSafePeriod( bytes32 slot, @@ -378,12 +406,14 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Change guardian safe period (only slot not initialized) - * @param initialKeyHash Initial key hash - * @param initialGuardianHash Initial guardian hash - * @param initialGuardianSafePeriod Initial guardian safe period - * @param newGuardianSafePeriod New guardian safe period - * @param keySignature one of the current owners signature + * @notice Changes the guardian safe period for a KeyStore slot using initialization data + * @dev for slots that are not initialized + * @param initialKeyHash The initial key hash + * @param initialGuardianHash The initial guardian hash + * @param initialGuardianSafePeriod The initial guardian safe period duration + * @param newGuardianSafePeriod The new duration of the guardian safe period + * @param rawOwners The raw owner data + * @param keySignature A signature from one of the current owners */ function setGuardianSafePeriod( bytes32 initialKeyHash, @@ -411,9 +441,10 @@ abstract contract BaseKeyStore is IKeyStore, KeyStoreAdapter { } /** - * @dev Cancel the pending guardian safe period change - * @param slot KeyStore slot - * @param keySignature `signature of current key` + * @notice Cancels a pending change of the guardian safe period for a given KeyStore slot + * @param slot The KeyStore slot identifier + * @param rawOwners The raw owner data + * @param keySignature The signature of the current key */ function cancelSetGuardianSafePeriod(bytes32 slot, bytes calldata rawOwners, bytes calldata keySignature) external diff --git a/contracts/keystore/L1/BaseMerkelTree.sol b/contracts/keystore/L1/BaseMerkelTree.sol index d206b635..639ab41c 100644 --- a/contracts/keystore/L1/BaseMerkelTree.sol +++ b/contracts/keystore/L1/BaseMerkelTree.sol @@ -1,11 +1,25 @@ pragma solidity ^0.8.20; +/** + * @title Interface for KeyStore Merkle Proof + */ interface IKeyStoreMerkelProof { + /** + * @notice Gets the merkle root of the tree + * @return The merkle root + */ function getMerkleRoot() external view returns (bytes32); + /** + * @notice Gets the depth of the tree + * @return The tree depth + */ function getTreeDepth() external pure returns (uint256); } -// build keystore merkel tree in evm, code build based on eth2 deposit contract +/** + * @title Implementation of a Merkle Tree structure + * @dev This contract allows for the construction of a merkle tree based on the Ethereum 2.0 deposit contract + */ abstract contract BaseMerkleTree is IKeyStoreMerkelProof { uint256 constant CONTRACT_TREE_DEPTH = 32; uint256 constant MAX_COUNT = 2 ** CONTRACT_TREE_DEPTH - 1; @@ -17,11 +31,18 @@ abstract contract BaseMerkleTree is IKeyStoreMerkelProof { event newLeaf(bytes32 slot, bytes32 signingKeyHash, uint256 blockNo, bytes32 leafNode, uint256 index); + /** + * @dev Constructor initializes the zero hashes + */ constructor() { for (uint256 height = 0; height < CONTRACT_TREE_DEPTH - 1; height++) { zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); } } + /** + * @notice Fetch the current root of the merkle tree + * @return The merkle root + */ function getMerkleRoot() external view returns (bytes32) { bytes32 node; @@ -37,13 +58,26 @@ abstract contract BaseMerkleTree is IKeyStoreMerkelProof { return node; } + /** + * @notice Fetches the current count of leaves in the tree + * @return The count of leaves + */ function getTreeCount() external view returns (uint256) { return leaf_count; } + /** + * @notice Fetches the depth of the tree + * @return The depth of the tree + */ function getTreeDepth() external pure returns (uint256) { return CONTRACT_TREE_DEPTH; } + /** + * @dev Inserts a new leaf into the merkle tree + * @param slot The slot for the leaf + * @param signingKeyHash The hash of the signing key + */ function _insertLeaf(bytes32 slot, bytes32 signingKeyHash) internal { require(leaf_count < MAX_COUNT, "merkle tree full"); diff --git a/contracts/keystore/L1/KeyStoreAdapter.sol b/contracts/keystore/L1/KeyStoreAdapter.sol index e13e8fd8..1590ab08 100644 --- a/contracts/keystore/L1/KeyStoreAdapter.sol +++ b/contracts/keystore/L1/KeyStoreAdapter.sol @@ -5,92 +5,198 @@ import "./interfaces/IKeyStoreStorage.sol"; import "./interfaces/IKeyStore.sol"; import "../../libraries/Errors.sol"; +/** + * @title KeyStoreAdapter + * @dev A contract that provides a set of utility functions to interact with a keystore + */ abstract contract KeyStoreAdapter is IKeyStore { uint256 internal constant _GUARDIAN_PERIOD_MIN = 2 days; uint256 internal constant _GUARDIAN_PERIOD_MAX = 30 days; + /** + * @dev Returns the current instance of the KeyStoreStorage + * This function should be implemented by derived contracts + */ function keyStoreStorage() public view virtual returns (IKeyStoreStorage); + /** + * @dev Fetches the current nonce for a given slot + * @param slot The slot from which to fetch the nonce + * @return _nonce The nonce of the given slot + */ function _getNonce(bytes32 slot) internal view returns (uint256 _nonce) { return keyStoreStorage().getUint256(slot, "nonce"); } + /** + * @dev Increases the nonce of a given slot by one + * @param slot The slot for which the nonce should be increased + */ function _increaseNonce(bytes32 slot) internal { uint256 _newNonce = _getNonce(slot) + 1; keyStoreStorage().setUint256(slot, "nonce", _newNonce); } + /** + * @dev Verifies that a given key is valid + * @param key The key to be verified + */ function _keyGuard(bytes32 key) internal view virtual { if (key == bytes32(0)) { revert Errors.INVALID_KEY(); } } + /** + * @dev Stores the signing key hash in a given slot + * @param slot The slot in which the signing key hash should be stored + * @param key the signing key hash to be stored + */ function _saveKey(bytes32 slot, bytes32 key) internal { _keyGuard(key); keyStoreStorage().setSlotValue(slot, key); emit KeyChanged(slot, key); } + /** + * @dev Fetches the signing key hash stored in a given slot + * @param slot The slot from which to fetch the signing key hash + * @return key The signing key hash stored in the given slot + */ function _getKey(bytes32 slot) internal view returns (bytes32 key) { return keyStoreStorage().getSlotValue(slot); } + /** + * @dev Fetches the guardian hash of a given slot + * @param slot The slot from which to fetch the guardian hash + * @return key The guardian hash of the given slot + */ function _getGuardianHash(bytes32 slot) internal view returns (bytes32 key) { return keyStoreStorage().getBytes32(slot, "guardianHash"); } + /** + * @dev Sets the guardian hash for a given slot + * @param slot The slot for which the guardian hash should be set + * @param _guardianHash The guardian hash to set + */ function _setGuardianHash(bytes32 slot, bytes32 _guardianHash) internal { return keyStoreStorage().setBytes32(slot, "guardianHash", _guardianHash); } + /** + * @dev Fetches the pending guardian hash of a given slot + * @param slot The slot from which to fetch the pending guardian hash + * @return key The pending guardian hash of the given slot + */ function _getPendingGuardianHash(bytes32 slot) internal view returns (bytes32 key) { return keyStoreStorage().getBytes32(slot, "pendingGuardianHash"); } + /** + * @dev Sets the pending guardian hash for a given slot + * @param slot The slot for which the pending guardian hash should be set + * @param _guardianHash The pending guardian hash to set + */ function _setPendingGuardianHash(bytes32 slot, bytes32 _guardianHash) internal { return keyStoreStorage().setBytes32(slot, "pendingGuardianHash", _guardianHash); } + /** + * @dev Fetches the guardian activation time for a given slot + * @param slot The slot from which to fetch the guardian activation time + * @return The guardian activation time of the given slot + */ function _getGuardianActivateAt(bytes32 slot) internal view returns (uint256) { return keyStoreStorage().getUint256(slot, "guardianActivateAt"); } + /** + * @dev Sets the guardian activation time for a given slot + * @param slot The slot for which the guardian activation time should be set + * @param _guardianActiveAt The guardian activation time to set + */ function _setGuardianActivateAt(bytes32 slot, uint256 _guardianActiveAt) internal { return keyStoreStorage().setUint256(slot, "guardianActivateAt", _guardianActiveAt); } + /** + * @dev Fetches the guardian safe period for a given slot + * @param slot The slot from which to fetch the guardian safe period + * @return The guardian safe period of the given slot + */ function _getGuardianSafePeriod(bytes32 slot) internal view returns (uint256) { return keyStoreStorage().getUint256(slot, "guardianSafePeriod"); } + /** + * @dev Sets the guardian safe period for a given slot + * @param slot The slot for which the guardian safe period should be set + * @param _guardianSafePeriod The guardian safe period to set + */ function _setGuardianSafePeriod(bytes32 slot, uint256 _guardianSafePeriod) internal { keyStoreStorage().setUint256(slot, "guardianSafePeriod", _guardianSafePeriod); } + /** + * @dev Fetches the pending guardian safe period for a given slot + * @param slot The slot from which to fetch the pending guardian safe period + * @return The pending guardian safe period of the given slot + */ function _getPendingGuardianSafePeriod(bytes32 slot) internal view returns (uint256) { return keyStoreStorage().getUint256(slot, "pendingGuardianSafePeriod"); } + /** + * @dev Sets the pending guardian safe period for a given slot + * @param slot The slot for which the pending guardian safe period should be set. + * @param _pendingGuardianSafePeriod The pending guardian safe period to set. + */ function _setPendingGuardianSafePeriod(bytes32 slot, uint256 _pendingGuardianSafePeriod) internal { keyStoreStorage().setUint256(slot, "pendingGuardianSafePeriod", _pendingGuardianSafePeriod); } + /** + * @dev Fetches the guardian safe period activation time for a given slot + * @param slot The slot from which to fetch the guardian safe period activation time + * @return The guardian safe period activation time of the given slot + */ function _getGuardianSafePeriodActivateAt(bytes32 slot) internal view returns (uint256) { return keyStoreStorage().getUint256(slot, "guardianSafePeriodActivateAt"); } + /** + * @dev Sets the guardian safe period activation time for a given slot + * @param slot The slot for which the guardian safe period activation time should be set + * @param _guardianSafePeriodActivateAt The guardian safe period activation time to set + */ function _setGuardianSafePeriodActivateAt(bytes32 slot, uint256 _guardianSafePeriodActivateAt) internal { keyStoreStorage().setUint256(slot, "guardianSafePeriodActivateAt", _guardianSafePeriodActivateAt); } + /** + * @dev Stores raw owner bytes for a given slot + * @param slot The slot in which the raw owner bytes should be stored + * @param data The raw owner bytes to store + */ function _storeRawOwnerBytes(bytes32 slot, bytes memory data) internal { keyStoreStorage().setBytes(slot, "rawOwners", data); } + /** + * @dev Fetches the raw owner's bytes for a given slot + * @param slot The slot from which to fetch the raw owner's bytes + * @return rawOwners The raw owner's bytes of the given slot + */ function _getRawOwners(bytes32 slot) internal view returns (bytes memory rawOwners) { return keyStoreStorage().getBytes(slot, "rawOwners"); } + /** + * @dev Fetches guardian info for a given slot + * @param slot The slot from which to fetch the guardian info + * @return _guardianInfo The guardian info of the given slot + */ function _getGuardianInfo(bytes32 slot) internal view returns (guardianInfo memory _guardianInfo) { _guardianInfo.guardianHash = _getGuardianHash(slot); @@ -100,6 +206,11 @@ abstract contract KeyStoreAdapter is IKeyStore { _guardianInfo.pendingGuardianSafePeriod = _getPendingGuardianSafePeriod(slot); _guardianInfo.guardianSafePeriodActivateAt = _getGuardianSafePeriodActivateAt(slot); } + /** + * @dev Fetches keystore info for a given slot + * @param slot The slot from which to fetch the keystore info + * @return _keyStoreInfo The keystore info of the given slot + */ function _getKeyStoreInfo(bytes32 slot) internal view returns (keyStoreInfo memory _keyStoreInfo) { _keyStoreInfo.key = _getKey(slot); @@ -111,6 +222,11 @@ abstract contract KeyStoreAdapter is IKeyStore { _keyStoreInfo.pendingGuardianSafePeriod = _getPendingGuardianSafePeriod(slot); _keyStoreInfo.guardianSafePeriodActivateAt = _getGuardianSafePeriodActivateAt(slot); } + /** + * @dev Sets the guardian info for a given slot + * @param slot The slot for which the guardian info should be set + * @param _info The guardian info to set + */ function _setGuardianInfo(bytes32 slot, guardianInfo memory _info) internal { _setGuardianHash(slot, _info.guardianHash); @@ -120,6 +236,11 @@ abstract contract KeyStoreAdapter is IKeyStore { _setPendingGuardianSafePeriod(slot, _info.pendingGuardianSafePeriod); _setGuardianSafePeriodActivateAt(slot, _info.guardianSafePeriodActivateAt); } + /** + * @dev Sets the keystore info for a given slot + * @param slot The slot for which the keystore info should be set + * @param _info The keystore info to set + */ function _setkeyStoreInfo(bytes32 slot, keyStoreInfo memory _info) internal { _saveKey(slot, _info.key); @@ -131,6 +252,11 @@ abstract contract KeyStoreAdapter is IKeyStore { _setGuardianSafePeriodActivateAt(slot, _info.guardianSafePeriodActivateAt); } + /** + * @dev Sets the keystore logic address for a given slot + * @param slot The slot for which the logic address should be set + * @param newLogicAddress The new logic address + */ function _setKeyStoreLogic(bytes32 slot, address newLogicAddress) internal { keyStoreStorage().setKeystoreLogic(slot, newLogicAddress); } diff --git a/contracts/keystore/L1/KeyStoreStorage.sol b/contracts/keystore/L1/KeyStoreStorage.sol index 82908061..c894c328 100644 --- a/contracts/keystore/L1/KeyStoreStorage.sol +++ b/contracts/keystore/L1/KeyStoreStorage.sol @@ -6,9 +6,13 @@ import "./interfaces/IMerkelTree.sol"; import "./BaseMerkelTree.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; -// using eternal storage pattern +/** + * @title KeyStoreStorage + * @dev Implements the eternal storage pattern. Provides generic storage capabilities + * and integrates with a Merkle Tree structure. + */ contract KeyStoreStorage is IKeyStoreStorage, IMerkleTree, Ownable, BaseMerkleTree { - // storage mapping + // Mapping structure for various data types // slot ->key-> value mapping(bytes32 => mapping(bytes32 => string)) private stringStorage; mapping(bytes32 => mapping(bytes32 => bytes)) private bytesStorage; @@ -18,15 +22,19 @@ contract KeyStoreStorage is IKeyStoreStorage, IMerkleTree, Ownable, BaseMerkleTr mapping(bytes32 => mapping(bytes32 => bool)) private booleanStorage; mapping(bytes32 => mapping(bytes32 => bytes32)) private bytes32Storage; - // slot can set which kesytore logic write to its storage + // Maps a slot to its corresponding keystore logic implementation mapping(bytes32 => address) public slotToKeystoreLogic; // slot => keystore logic implementation - + // Default keystore logic implementation address address defaultKeystoreLogic; event KeystoreLogicSet(bytes32 indexed slot, address indexed logicAddress); event LeafInserted(bytes32 indexed slot, bytes32 signingKeyHash); constructor(address _owner) Ownable(_owner) {} + /** + * @dev Modifier to ensure that the function caller is an authorized keystore + * @param slot The slot being accessed + */ modifier onlyAuthrizedKeystore(bytes32 slot) { if (slotToKeystoreLogic[slot] == address(0)) { @@ -36,54 +44,124 @@ contract KeyStoreStorage is IKeyStoreStorage, IMerkleTree, Ownable, BaseMerkleTr } _; } + /** + * @dev Returns the address stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return Address stored at the slot and key. + */ function getAddress(bytes32 _slot, bytes32 _key) external view override returns (address) { return addressStorage[_slot][_key]; } + /** + * @dev Returns the uint256 value stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return uint256 value stored at the slot and key. + */ function getUint256(bytes32 _slot, bytes32 _key) external view override returns (uint256) { return uint256Storage[_slot][_key]; } + /** + * @dev Returns the string value stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return string value stored at the slot and key. + */ function getString(bytes32 _slot, bytes32 _key) external view override returns (string memory) { return stringStorage[_slot][_key]; } + /** + * @dev Returns the bytes value stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return bytes value stored at the slot and key. + */ function getBytes(bytes32 _slot, bytes32 _key) external view override returns (bytes memory) { return bytesStorage[_slot][_key]; } + /** + * @dev Returns the boolean value stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return boolean value stored at the slot and key. + */ function getBool(bytes32 _slot, bytes32 _key) external view override returns (bool) { return booleanStorage[_slot][_key]; } + /** + * @dev Returns the int256 value stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return int256 value stored at the slot and key. + */ function getInt(bytes32 _slot, bytes32 _key) external view override returns (int256) { return intStorage[_slot][_key]; } + /** + * @dev Returns the bytes32 value stored at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @return bytes32 value stored at the slot and key. + */ function getBytes32(bytes32 _slot, bytes32 _key) external view override returns (bytes32) { return bytes32Storage[_slot][_key]; } + /** + * @dev Returns the sigin key hash of a specific storage slot. + * @param _slot The slot of the storage. + * @return key The represent signing key hash associated with the slot. + */ function getSlotValue(bytes32 _slot) external view override returns (bytes32 key) { assembly { key := sload(_slot) } } + /** + * @dev set the signing key hash of the storage slot. + * @param _slot The slot to set. + * @param _value The signing key hash to set. + */ function setSlotValue(bytes32 _slot, bytes32 _value) external override onlyAuthrizedKeystore(_slot) { assembly { sstore(_slot, _value) } } + /** + * @dev Set an address in the storage. + * @param _slot The slot to set. + * @param _key The key within the slot. + * @param _value The address value to set. + */ function setAddress(bytes32 _slot, bytes32 _key, address _value) external override onlyAuthrizedKeystore(_slot) { addressStorage[_slot][_key] = _value; } + /** + * @dev Set a uint256 value in the storage. + * @param _slot The slot to set. + * @param _key The key within the slot. + * @param _value The uint256 value to set. + */ function setUint256(bytes32 _slot, bytes32 _key, uint256 _value) external override onlyAuthrizedKeystore(_slot) { uint256Storage[_slot][_key] = _value; } + /** + * @dev Set a string value in the storage. + * @param _slot The slot to set. + * @param _key The key within the slot. + * @param _value The string value to set. + */ function setString(bytes32 _slot, bytes32 _key, string calldata _value) external @@ -92,6 +170,12 @@ contract KeyStoreStorage is IKeyStoreStorage, IMerkleTree, Ownable, BaseMerkleTr { stringStorage[_slot][_key] = _value; } + /** + * @dev Sets the bytes value in storage at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @param _value The bytes value to set. + */ function setBytes(bytes32 _slot, bytes32 _key, bytes calldata _value) external @@ -100,30 +184,61 @@ contract KeyStoreStorage is IKeyStoreStorage, IMerkleTree, Ownable, BaseMerkleTr { bytesStorage[_slot][_key] = _value; } + /** + * @dev Sets the boolean value in storage at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @param _value The boolean value to set. + */ function setBool(bytes32 _slot, bytes32 _key, bool _value) external override onlyAuthrizedKeystore(_slot) { booleanStorage[_slot][_key] = _value; } + /** + * @dev Sets the int256 value in storage at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @param _value The int256 value to set. + */ function setInt(bytes32 _slot, bytes32 _key, int256 _value) external override onlyAuthrizedKeystore(_slot) { intStorage[_slot][_key] = _value; } + /** + * @dev Sets the bytes32 value in storage at a specific slot and key. + * @param _slot The slot of the storage. + * @param _key The key within the slot. + * @param _value The bytes32 value to set. + */ function setBytes32(bytes32 _slot, bytes32 _key, bytes32 _value) external override onlyAuthrizedKeystore(_slot) { bytes32Storage[_slot][_key] = _value; } + /** + * @dev Insert a new leaf into the merkle tree and emit an event. + * @param _slot The slot related to the leaf. + * @param _signingKey The signing key related to the leaf. + */ function insertLeaf(bytes32 _slot, bytes32 _signingKey) external override onlyAuthrizedKeystore(_slot) { _insertLeaf(_slot, _signingKey); emit LeafInserted(_slot, _signingKey); } + /** + * @dev Assign a new keystore logic implementation to a slot. + * @param _slot The slot to assign the logic implementation to. + * @param _logicAddress Address of the logic implementation. + */ function setKeystoreLogic(bytes32 _slot, address _logicAddress) external onlyAuthrizedKeystore(_slot) { slotToKeystoreLogic[_slot] = _logicAddress; emit KeystoreLogicSet(_slot, _logicAddress); } - // admin function, set default keystore address + /** + * @dev Set the default keystore logic implementation address. + * @param _defaultKeystoreLogic Address of the default logic implementation. + */ function setDefaultKeystoreAddress(address _defaultKeystoreLogic) external onlyOwner { require(defaultKeystoreLogic == address(0), "defaultKeystoreLogic already initialized"); defaultKeystoreLogic = _defaultKeystoreLogic; diff --git a/contracts/keystore/interfaces/IKeyStoreProof.sol b/contracts/keystore/interfaces/IKeyStoreProof.sol index 8e5db4ae..ecf580dd 100644 --- a/contracts/keystore/interfaces/IKeyStoreProof.sol +++ b/contracts/keystore/interfaces/IKeyStoreProof.sol @@ -1,7 +1,22 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title Key Store Proof Interface + * @dev This interface provides methods to retrieve the keystore signing key hash and raw owners based on a slot. + */ interface IKeyStoreProof { + /** + * @dev Returns the signing key hash associated with a given L1 slot. + * @param l1Slot The L1 slot + * @return signingKeyHash The hash of the signing key associated with the L1 slot + */ function keystoreBySlot(bytes32 l1Slot) external view returns (bytes32 signingKeyHash); + + /** + * @dev Returns the raw owners associated with a given L1 slot. + * @param l1Slot The L1 slot + * @return owners The raw owner data associated with the L1 slot + */ function rawOwnersBySlot(bytes32 l1Slot) external view returns (bytes memory owners); } diff --git a/contracts/libraries/AccountStorage.sol b/contracts/libraries/AccountStorage.sol index 0d82a5f1..c003a0ce 100644 --- a/contracts/libraries/AccountStorage.sol +++ b/contracts/libraries/AccountStorage.sol @@ -3,6 +3,10 @@ pragma solidity ^0.8.20; import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +/** + * @title AccountStorage + * @notice A library that defines the storage layout for the SoulWallet account or contract. + */ library AccountStorage { bytes32 private constant _ACCOUNT_SLOT = keccak256("soulwallet.contracts.AccountStorage"); @@ -38,8 +42,10 @@ library AccountStorage { } // └───────────────────┘ - //#TODO - + /** + * @notice Returns the layout of the storage for the account or contract. + * @return l The layout of the storage. + */ function layout() internal pure returns (Layout storage l) { bytes32 slot = _ACCOUNT_SLOT; assembly ("memory-safe") { diff --git a/contracts/libraries/AddressLinkedList.sol b/contracts/libraries/AddressLinkedList.sol index 5706c3ae..fbffdc28 100644 --- a/contracts/libraries/AddressLinkedList.sol +++ b/contracts/libraries/AddressLinkedList.sol @@ -3,9 +3,16 @@ pragma solidity ^0.8.20; import "../libraries/Errors.sol"; +/** + * @title Address Linked List + * @notice This library provides utility functions to manage a linked list of addresses + */ library AddressLinkedList { address internal constant SENTINEL_ADDRESS = address(1); uint160 internal constant SENTINEL_UINT = 1; + /** + * @dev Modifier that checks if an address is valid. + */ modifier onlyAddress(address addr) { if (uint160(addr) <= SENTINEL_UINT) { @@ -13,6 +20,11 @@ library AddressLinkedList { } _; } + /** + * @notice Adds an address to the linked list. + * @param self The linked list mapping. + * @param addr The address to be added. + */ function add(mapping(address => address) storage self, address addr) internal onlyAddress(addr) { if (self[addr] != address(0)) { @@ -27,6 +39,12 @@ library AddressLinkedList { self[addr] = _prev; } } + /** + * @notice Replaces an old address with a new one in the linked list. + * @param self The linked list mapping. + * @param oldAddr The old address to be replaced. + * @param newAddr The new address. + */ function replace(mapping(address => address) storage self, address oldAddr, address newAddr) internal { if (!isExist(self, oldAddr)) { @@ -49,12 +67,23 @@ library AddressLinkedList { cursor = _addr; } } + /** + * @notice Removes an address from the linked list. + * @param self The linked list mapping. + * @param addr The address to be removed. + */ function remove(mapping(address => address) storage self, address addr) internal { if (!tryRemove(self, addr)) { revert Errors.ADDRESS_NOT_EXISTS(); } } + /** + * @notice Tries to remove an address from the linked list. + * @param self The linked list mapping. + * @param addr The address to be removed. + * @return Returns true if removal is successful, false otherwise. + */ function tryRemove(mapping(address => address) storage self, address addr) internal returns (bool) { if (isExist(self, addr)) { @@ -72,6 +101,10 @@ library AddressLinkedList { } return false; } + /** + * @notice Clears all addresses from the linked list. + * @param self The linked list mapping. + */ function clear(mapping(address => address) storage self) internal { for (address addr = self[SENTINEL_ADDRESS]; uint160(addr) > SENTINEL_UINT; addr = self[addr]) { @@ -79,6 +112,12 @@ library AddressLinkedList { } self[SENTINEL_ADDRESS] = address(0); } + /** + * @notice Checks if an address exists in the linked list. + * @param self The linked list mapping. + * @param addr The address to check. + * @return Returns true if the address exists, false otherwise. + */ function isExist(mapping(address => address) storage self, address addr) internal @@ -88,6 +127,11 @@ library AddressLinkedList { { return self[addr] != address(0); } + /** + * @notice Returns the size of the linked list. + * @param self The linked list mapping. + * @return Returns the size of the linked list. + */ function size(mapping(address => address) storage self) internal view returns (uint256) { uint256 result = 0; @@ -100,13 +144,22 @@ library AddressLinkedList { } return result; } + /** + * @notice Checks if the linked list is empty. + * @param self The linked list mapping. + * @return Returns true if the linked list is empty, false otherwise. + */ function isEmpty(mapping(address => address) storage self) internal view returns (bool) { return self[SENTINEL_ADDRESS] == address(0); } /** - * @dev This function is just an example, please copy this code directly when you need it, you should not call this function + * @notice Returns a list of addresses from the linked list. + * @param self The linked list mapping. + * @param from The starting address. + * @param limit The number of addresses to return. + * @return Returns an array of addresses. */ function list(mapping(address => address) storage self, address from, uint256 limit) internal diff --git a/contracts/libraries/Base64Url.sol b/contracts/libraries/Base64Url.sol index 4c89776d..3d14578c 100644 --- a/contracts/libraries/Base64Url.sol +++ b/contracts/libraries/Base64Url.sol @@ -16,8 +16,7 @@ library Base64Url { /** * @dev Base64Url Encoding/Decoding Table */ - string internal constant _TABLE = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; /** * @dev Converts a `bytes` to its Bytes64Url `string` representation. @@ -41,9 +40,7 @@ library Base64Url { assembly { encodedLen := mul(4, div(dataLen, 3)) //4 * (dataLen / 3); let padding := mod(dataLen, 3) - if gt(padding, 0) { - encodedLen := add(add(encodedLen, padding), 1) - } + if gt(padding, 0) { encodedLen := add(add(encodedLen, padding), 1) } } // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter @@ -62,9 +59,7 @@ library Base64Url { for { let dataPtr := data let endPtr := add(data, dataLen) - } lt(dataPtr, endPtr) { - - } { + } lt(dataPtr, endPtr) {} { // Advance 3 bytes dataPtr := add(dataPtr, 3) let input := mload(dataPtr) @@ -77,22 +72,13 @@ library Base64Url { // and finally write it in the result pointer but with a left shift // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits - mstore8( - resultPtr, - mload(add(tablePtr, and(shr(18, input), 0x3F))) - ) + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) resultPtr := add(resultPtr, 1) // Advance - mstore8( - resultPtr, - mload(add(tablePtr, and(shr(12, input), 0x3F))) - ) + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) resultPtr := add(resultPtr, 1) // Advance - mstore8( - resultPtr, - mload(add(tablePtr, and(shr(6, input), 0x3F))) - ) + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) resultPtr := add(resultPtr, 1) // Advance mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) diff --git a/contracts/libraries/KeyStoreSlotLib.sol b/contracts/libraries/KeyStoreSlotLib.sol index b34125fc..d033d3f9 100644 --- a/contracts/libraries/KeyStoreSlotLib.sol +++ b/contracts/libraries/KeyStoreSlotLib.sol @@ -1,7 +1,18 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title KeyStoreSlotLib + * @notice A library to compute a keystore slot based on input parameters + */ library KeyStoreSlotLib { + /** + * @notice Calculates a slot using the initial key hash, initial guardian hash, and guardian safe period + * @param initialKeyHash The initial key hash used for calculating the slot + * @param initialGuardianHash The initial guardian hash used for calculating the slot + * @param guardianSafePeriod The guardian safe period used for calculating the slot + * @return slot The resulting keystore slot derived from the input parameters + */ function getSlot(bytes32 initialKeyHash, bytes32 initialGuardianHash, uint256 guardianSafePeriod) internal pure diff --git a/contracts/libraries/TypeConversion.sol b/contracts/libraries/TypeConversion.sol index 9fe9663b..789018f6 100644 --- a/contracts/libraries/TypeConversion.sol +++ b/contracts/libraries/TypeConversion.sol @@ -1,10 +1,24 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title TypeConversion + * @notice A library to facilitate address to bytes32 conversions + */ library TypeConversion { + /** + * @notice Converts an address to bytes32 + * @param addr The address to be converted + * @return Resulting bytes32 representation of the input address + */ function toBytes32(address addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(addr))); } + /** + * @notice Converts an array of addresses to an array of bytes32 + * @param addresses Array of addresses to be converted + * @return Array of bytes32 representations of the input addresses + */ function addressesToBytes32Array(address[] memory addresses) internal pure returns (bytes32[] memory) { bytes32[] memory result = new bytes32[](addresses.length); diff --git a/contracts/modules/BaseModule.sol b/contracts/modules/BaseModule.sol index 1cb83614..33d6e821 100644 --- a/contracts/modules/BaseModule.sol +++ b/contracts/modules/BaseModule.sol @@ -5,19 +5,42 @@ import "../interfaces/IModule.sol"; import "../interfaces/ISoulWallet.sol"; import "../interfaces/IModuleManager.sol"; +/** + * @title BaseModule + * @notice An abstract base contract that provides a foundation for other modules. + * It ensures the initialization, de-initialization, and proper authorization of modules. + */ abstract contract BaseModule is IModule { event ModuleInit(address indexed wallet); event ModuleDeInit(address indexed wallet); + /** + * @notice Checks if the module is initialized for a particular wallet. + * @param wallet Address of the wallet. + * @return True if the module is initialized, false otherwise. + */ function inited(address wallet) internal view virtual returns (bool); - + /** + * @notice Initialization logic for the module. + * @param data Initialization data for the module. + */ function _init(bytes calldata data) internal virtual; - + /** + * @notice De-initialization logic for the module. + */ function _deInit() internal virtual; + /** + * @notice Helper function to get the sender of the transaction. + * @return Address of the transaction sender. + */ function sender() internal view returns (address) { return msg.sender; } + /** + * @notice Initializes the module for a wallet. + * @param data Initialization data for the module. + */ function walletInit(bytes calldata data) external { address _sender = sender(); @@ -29,6 +52,9 @@ abstract contract BaseModule is IModule { emit ModuleInit(_sender); } } + /** + * @notice De-initializes the module for a wallet. + */ function walletDeInit() external { address _sender = sender(); @@ -40,6 +66,11 @@ abstract contract BaseModule is IModule { emit ModuleDeInit(_sender); } } + /** + * @notice Verifies if the module supports a specific interface. + * @param interfaceId ID of the interface to be checked. + * @return True if the module supports the given interface, false otherwise. + */ function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { return interfaceId == type(IModule).interfaceId; diff --git a/contracts/modules/keystore/ArbitrumKeyStoreModule/ArbKnownStateRootWithHistory.sol b/contracts/modules/keystore/ArbitrumKeyStoreModule/ArbKnownStateRootWithHistory.sol index cd06c2f0..8a1ebdb4 100644 --- a/contracts/modules/keystore/ArbitrumKeyStoreModule/ArbKnownStateRootWithHistory.sol +++ b/contracts/modules/keystore/ArbitrumKeyStoreModule/ArbKnownStateRootWithHistory.sol @@ -5,17 +5,41 @@ import "../KnownStateRootWithHistoryBase.sol"; import "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; import "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +/* + * @title ArbKnownStateRootWithHistory + * @notice This contract is an extension of `KnownStateRootWithHistoryBase` and keeps a record of block hashes + * for specific block numbers ensuring they are set by a trusted entity on L1 on Arbitrum network. + */ contract ArbKnownStateRootWithHistory is KnownStateRootWithHistoryBase, Ownable { + /* @notice Address of the target contract on Layer 1 (L1). */ address public l1Target; + /* + * @dev Initializes the `l1Target` address and the `owner` of the contract. + * @param _l1Target Address of the target contract on L1. + * @param _owner Owner's address. + */ constructor(address _l1Target, address _owner) Ownable(_owner) { l1Target = _l1Target; } + /* + * @notice Updates the `l1Target` address. + * @param _l1Target New address of the target contract on L1. + */ function updateL1Target(address _l1Target) public onlyOwner { l1Target = _l1Target; } + /* + * @notice Sets the hash for a specific block number. + * @dev This function requires: + * - Message is coming from the L1 target contract's L2 alias. + * - Block number is not 0. + * - Block hash is not the zero hash. + * @param l1BlockNumber Block number on L1. + * @param l1BlockHash Block hash corresponding to the block number. + */ function setBlockHash(uint256 l1BlockNumber, bytes32 l1BlockHash) external { // To check that message came from L1, we check that the sender is the L1 contract's L2 alias. diff --git a/contracts/modules/keystore/ArbitrumKeyStoreModule/L1BlockInfoPassing.sol b/contracts/modules/keystore/ArbitrumKeyStoreModule/L1BlockInfoPassing.sol index ab04746b..fd3e1c80 100644 --- a/contracts/modules/keystore/ArbitrumKeyStoreModule/L1BlockInfoPassing.sol +++ b/contracts/modules/keystore/ArbitrumKeyStoreModule/L1BlockInfoPassing.sol @@ -5,21 +5,42 @@ import "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; import "@arbitrum/nitro-contracts/src/bridge/Outbox.sol"; import {ArbKnownStateRootWithHistory} from "./ArbKnownStateRootWithHistory.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +/** + * @title L1BlockInfoPassing + * @notice This contract facilitates the passing of block information from Layer 1 (L1) to Layer 2 (L2) on the Arbitrum network + */ contract L1BlockInfoPassing is Ownable { address public l2Target; IInbox public immutable inbox; event BlockHashPassingTickedCreated(uint256 ticketId, uint256 blockNumber, bytes32 blockHash); + /** + * @dev Initializes the `l2Target` address, `inbox` address and the `owner` of the contract + * @param _l2Target The address of the target contract on Layer 2 (L2) + * @param _inbox The address of the inbox contract + * @param _owner The owner's address + */ constructor(address _l2Target, address _inbox, address _owner) Ownable(_owner) { l2Target = _l2Target; inbox = IInbox(_inbox); } + /** + * @notice Updates the `l2Target` address + * @param _l2Target New address of the target contract on L2 + */ function updateL2Target(address _l2Target) public onlyOwner { l2Target = _l2Target; } + /** + * @notice Sends the block hash of the previous block to L2 using Arbitrum's retryable ticket mechanism + * @param _maxSubmissionCost The maximum amount of Eth to be paid for the L2-side message execution + * @param _maxGas The maximum amount of gas to be used on L2 for executing the L2-side message + * @param _gasPriceBid The price (in wei) to be paid per unit of gas + * @return ticketID The ID of the retryable ticket created by this function + */ function passBlockHashInL2(uint256 _maxSubmissionCost, uint256 _maxGas, uint256 _gasPriceBid) public diff --git a/contracts/modules/keystore/IKeyStoreModule.sol b/contracts/modules/keystore/IKeyStoreModule.sol index 5f1d8357..81ed86e3 100644 --- a/contracts/modules/keystore/IKeyStoreModule.sol +++ b/contracts/modules/keystore/IKeyStoreModule.sol @@ -1,11 +1,31 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title IKeyStoreModule + * @notice Interface for the KeyStoreModule, responsible for managing and syncing keystores + */ interface IKeyStoreModule { + /** + * @notice Emitted when the keystore for a specific wallet has been synchronized + * @param _wallet The address of the wallet for which the keystore has been synced + * @param _newOwners The new owners of the keystore represented as a bytes32 value + */ event KeyStoreSyncd(address indexed _wallet, bytes32 indexed _newOwners); + /** + * @notice Emitted when a keystore is initialized + * @param _wallet The address of the wallet for which the keystore has been initialized + * @param _initialKey The initial key set for the keystore represented as a bytes32 value + * @param initialGuardianHash The initial hash value for the guardians + * @param guardianSafePeriod The safe period for guardians + */ event KeyStoreInited( address indexed _wallet, bytes32 _initialKey, bytes32 initialGuardianHash, uint64 guardianSafePeriod ); + /** + * @dev Synchronizes the keystore for a specific wallet + * @param wallet The address of the wallet to be synchronized + */ function syncL1Keystore(address wallet) external; } diff --git a/contracts/modules/keystore/IKnownStateRootWithHistory.sol b/contracts/modules/keystore/IKnownStateRootWithHistory.sol index c4a55dac..c06bab3f 100644 --- a/contracts/modules/keystore/IKnownStateRootWithHistory.sol +++ b/contracts/modules/keystore/IKnownStateRootWithHistory.sol @@ -1,14 +1,34 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title BlockInfo + * @dev Struct containing information related to a particular block. + */ struct BlockInfo { - bytes32 storageRootHash; - bytes32 blockHash; - uint256 blockNumber; - uint256 blockTimestamp; + bytes32 storageRootHash; /* Storage root hash of the block */ + bytes32 blockHash; /* Hash of the block */ + uint256 blockNumber; /* Number of the block */ + uint256 blockTimestamp; /* Timestamp of the block */ } +/** + * @title IKnownStateRootWithHistory + * @notice Interface for checking and retrieving information about known state roots and associated block info + */ interface IKnownStateRootWithHistory { + /** + * @notice Checks if a state root is known + * @param _stateRoot The state root to check + * @return bool indicating whether the state root is known + */ function isKnownStateRoot(bytes32 _stateRoot) external returns (bool); + + /** + * @notice Retrieves information about a state root if it's known + * @param _stateRoot The state root for which to retrieve information + * @return result A bool indicating if the state root is known + * @return info A BlockInfo struct containing associated block information + */ function stateRootInfo(bytes32 _stateRoot) external view returns (bool result, BlockInfo memory info); } diff --git a/contracts/modules/keystore/KeyStoreModule.sol b/contracts/modules/keystore/KeyStoreModule.sol index 5a45a2e4..c5bf2c12 100644 --- a/contracts/modules/keystore/KeyStoreModule.sol +++ b/contracts/modules/keystore/KeyStoreModule.sol @@ -7,6 +7,10 @@ import "./MerklePatriciaVerifier.sol"; import "../../libraries/KeyStoreSlotLib.sol"; import "../../keystore/interfaces/IKeyStoreProof.sol"; +/** + * @title KeyStoreModule + * @notice module for syncing a L1 keystore + */ contract KeyStoreModule is IKeyStoreModule, BaseModule { bytes4 private constant _FUNC_RESET_OWNER = bytes4(keccak256("resetOwner(bytes32)")); bytes4 private constant _FUNC_RESET_OWNERS = bytes4(keccak256("resetOwners(bytes32[])")); @@ -18,16 +22,26 @@ contract KeyStoreModule is IKeyStoreModule, BaseModule { mapping(address => bool) walletInited; uint128 private __seed = 0; + /** + * @notice Internal function to increment and return seed. + * @return New incremented seed value. + */ function _newSeed() private returns (uint128) { __seed++; return __seed; } + /** + * @param _keyStoreProof Address of the KeyStoreProof contract. + */ constructor(address _keyStoreProof) { keyStoreProof = IKeyStoreProof(_keyStoreProof); } - // validate the l1 keystore signing key using merkel patricia proof + /** + * @notice Synchronize L1 keystore with the wallet. + * @param wallet Address of the wallet. + */ function syncL1Keystore(address wallet) external override { bytes32 slotInfo = l1Slot[wallet]; @@ -45,6 +59,10 @@ contract KeyStoreModule is IKeyStoreModule, BaseModule { lastKeyStoreSyncSignKey[wallet] = keystoreSignKey; emit KeyStoreSyncd(wallet, keystoreSignKey); } + /** + * @notice Retrieve the list of required functions for the keystore module. + * @return An array of function selectors. + */ function requiredFunctions() external pure override returns (bytes4[] memory) { bytes4[] memory functions = new bytes4[](2); @@ -52,11 +70,20 @@ contract KeyStoreModule is IKeyStoreModule, BaseModule { functions[1] = _FUNC_RESET_OWNERS; return functions; } + /** + * @notice Check if a wallet is initialized. + * @param wallet Address of the wallet. + * @return True if the wallet is initialized, false otherwise. + */ function inited(address wallet) internal view virtual override returns (bool) { return walletInited[wallet]; } - // when wallet add keystore module, it will call this function to set the l1keystore slot mapping + /** + * @dev when wallet add keystore module, it will call this function to set the l1keystore slot mapping + * @notice Internal function to initialize keystore for a wallet. + * @param _data Initialization data containing initial key hash, guardian hash, and guardian safe period. + */ function _init(bytes calldata _data) internal virtual override { address _sender = sender(); @@ -80,6 +107,9 @@ contract KeyStoreModule is IKeyStoreModule, BaseModule { walletInited[_sender] = true; emit KeyStoreInited(_sender, initialKeyHash, initialGuardianHash, guardianSafePeriod); } + /** + * @notice Internal function to deinitialize keystore for a wallet. + */ function _deInit() internal virtual override { address _sender = sender(); diff --git a/contracts/modules/keystore/KeystoreProof.sol b/contracts/modules/keystore/KeystoreProof.sol index 1f5185cb..23fc71e6 100644 --- a/contracts/modules/keystore/KeystoreProof.sol +++ b/contracts/modules/keystore/KeystoreProof.sol @@ -5,6 +5,10 @@ import "./IKnownStateRootWithHistory.sol"; import "./MerklePatriciaVerifier.sol"; import "../../keystore/interfaces/IKeyStoreProof.sol"; +/** + * @title KeystoreProof + * @notice Contract for maintaining and proving a L1 keystore and its storage roots + */ contract KeystoreProof is IKeyStoreProof { mapping(bytes32 => bytes32) public l1SlotToSigningKey; mapping(bytes32 => bytes) public l1SlotToRawOwners; @@ -14,15 +18,24 @@ contract KeystoreProof is IKeyStoreProof { address public immutable STATE_ROOT_HISTORY_ADDESS; address public immutable L1_KEYSTORE_ADDRESS; // the latest block number in l1 that proved - uint256 public lastestProofL1BlockNumber; + uint256 public latestProofL1BlockNumber; event KeyStoreStorageProved(bytes32 stateRoot, bytes32 storageRoot); event L1KeyStoreProved(bytes32 l1Slot, bytes32 signingKeyHash); + /** + * @param _l1KeystoreAddress Address of L1 Keystore + * @param _stateRootHistoryAddress Address of state root history contract + */ constructor(address _l1KeystoreAddress, address _stateRootHistoryAddress) { L1_KEYSTORE_ADDRESS = _l1KeystoreAddress; STATE_ROOT_HISTORY_ADDESS = _stateRootHistoryAddress; } + /** + * @notice Proves the keystore storage root + * @param stateRoot State root to be proved + * @param accountProof Proof for the account associated with the state root + */ function proofKeystoreStorageRoot(bytes32 stateRoot, bytes memory accountProof) external { (bool searchResult, BlockInfo memory currentBlockInfo) = @@ -35,11 +48,19 @@ contract KeystoreProof is IKeyStoreProof { Rlp.Item[] memory keyStoreDetails = Rlp.toList(Rlp.toItem(keyStoreAccountDetailsBytes)); bytes32 keyStoreStorageRootHash = Rlp.toBytes32(keyStoreDetails[2]); stateRootToKeystoreStorageRoot[stateRoot] = keyStoreStorageRootHash; - if (currentBlockInfo.blockNumber > lastestProofL1BlockNumber) { - lastestProofL1BlockNumber = currentBlockInfo.blockNumber; + if (currentBlockInfo.blockNumber > latestProofL1BlockNumber) { + latestProofL1BlockNumber = currentBlockInfo.blockNumber; } emit KeyStoreStorageProved(stateRoot, keyStoreStorageRootHash); } + /** + * @notice Proves the L1 keystore + * @param l1Slot Slot of L1 keystore + * @param stateRoot State root to be proved + * @param newSigningKey New signing key to be set + * @param rawOwners Raw owners to be associated with the signing key + * @param keyProof Proof for the key + */ function proofL1Keystore( bytes32 l1Slot, @@ -70,10 +91,20 @@ contract KeystoreProof is IKeyStoreProof { l1SlotToRawOwners[l1Slot] = rawOwners; emit L1KeyStoreProved(l1Slot, newSigningKey); } + /** + * @notice Retrieves the signing key hash associated with a given L1 slot + * @param l1Slot Slot of L1 keystore + * @return signingKeyHash The signing key hash associated with the L1 slot + */ function keystoreBySlot(bytes32 l1Slot) external view returns (bytes32 signingKeyHash) { return (l1SlotToSigningKey[l1Slot]); } + /** + * @notice Retrieves the raw owners associated with a given L1 slot + * @param l1Slot Slot of L1 keystore + * @return owners The raw owners associated with the L1 slot + */ function rawOwnersBySlot(bytes32 l1Slot) external view override returns (bytes memory owners) { return l1SlotToRawOwners[l1Slot]; diff --git a/contracts/modules/keystore/KnownStateRootWithHistoryBase.sol b/contracts/modules/keystore/KnownStateRootWithHistoryBase.sol index e3b89e0d..afe9a74f 100644 --- a/contracts/modules/keystore/KnownStateRootWithHistoryBase.sol +++ b/contracts/modules/keystore/KnownStateRootWithHistoryBase.sol @@ -4,15 +4,27 @@ pragma solidity ^0.8.20; import "./IKnownStateRootWithHistory.sol"; import "./BlockVerifier.sol"; +/** + * @title KnownStateRootWithHistoryBase + * @notice Abstract contract for maintaining a history of known state roots and associated block info + */ abstract contract KnownStateRootWithHistoryBase is IKnownStateRootWithHistory { + /* Size of the state root history */ uint256 public constant ROOT_HISTORY_SIZE = 100; + /* Current index in the circular buffer of state roots */ uint256 public currentRootIndex = 0; - + /* Mapping of block numbers to associated block information */ mapping(uint256 => BlockInfo) public stateRoots; + /* Mapping of block numbers to block hashes */ mapping(uint256 => bytes32) public blockHashs; event L1BlockSyncd(uint256 indexed blockNumber, bytes32 blockHash); event NewStateRoot(bytes32 indexed stateRoot, uint256 indexed blockNumber, address user); + /** + * @notice Checks if a given state root is known + * @param _stateRoot The state root to check + * @return True if the state root is known, false otherwise + */ function isKnownStateRoot(bytes32 _stateRoot) public view override returns (bool) { if (_stateRoot == 0) { @@ -33,6 +45,11 @@ abstract contract KnownStateRootWithHistoryBase is IKnownStateRootWithHistory { } while (i != _currentRootIndex); return false; } + /** + * @notice Inserts a new state root and associated block info + * @param _blockNumber The number of the block + * @param _blockInfo Serialized block info data + */ function insertNewStateRoot(uint256 _blockNumber, bytes memory _blockInfo) external { bytes32 _blockHash = blockHashs[_blockNumber]; @@ -54,6 +71,12 @@ abstract contract KnownStateRootWithHistoryBase is IKnownStateRootWithHistory { stateRoots[newRootIndex].blockTimestamp = blockTimestamp; emit NewStateRoot(stateRoot, blockNumber, msg.sender); } + /** + * @notice Retrieves information about a given state root + * @param _stateRoot The state root to query + * @return result True if the state root is known, false otherwise + * @return info BlockInfo structure associated with the given state root + */ function stateRootInfo(bytes32 _stateRoot) external view override returns (bool result, BlockInfo memory info) { if (_stateRoot == 0) { @@ -74,6 +97,10 @@ abstract contract KnownStateRootWithHistoryBase is IKnownStateRootWithHistory { } while (i != _currentRootIndex); return (false, info); } + /** + * @notice Retrieves information about the latest known state root + * @return info BlockInfo structure associated with the latest state root + */ function lastestStateRootInfo() external view returns (BlockInfo memory info) { return stateRoots[currentRootIndex]; diff --git a/contracts/modules/keystore/OptimismKeyStoreProofModule/IL1Block.sol b/contracts/modules/keystore/OptimismKeyStoreProofModule/IL1Block.sol index 9c05d21a..925f7b82 100644 --- a/contracts/modules/keystore/OptimismKeyStoreProofModule/IL1Block.sol +++ b/contracts/modules/keystore/OptimismKeyStoreProofModule/IL1Block.sol @@ -1,7 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title IL1Block + * @notice Interface for representing Layer 1 block properties, primarily block hash and block number + */ interface IL1Block { + /** + * @dev Fetches the hash of the L1 block + * @return The block hash as bytes32 + */ function hash() external returns (bytes32); + /** + * @dev Fetches the number of the L1 block + * @return The block number as uint256 + */ function number() external returns (uint256); } diff --git a/contracts/modules/keystore/OptimismKeyStoreProofModule/OpKnownStateRootWithHistory.sol b/contracts/modules/keystore/OptimismKeyStoreProofModule/OpKnownStateRootWithHistory.sol index 1b09cd69..719cff76 100644 --- a/contracts/modules/keystore/OptimismKeyStoreProofModule/OpKnownStateRootWithHistory.sol +++ b/contracts/modules/keystore/OptimismKeyStoreProofModule/OpKnownStateRootWithHistory.sol @@ -3,13 +3,27 @@ pragma solidity ^0.8.20; import "../KnownStateRootWithHistoryBase.sol"; import "./IL1Block.sol"; +/** + * @title OpKnownStateRootWithHistory + * @notice This contract is designed to work with the Optimism network to keep track of L1 block hashes in an L2 environment + * The block hash information is retrieved from a contract deployed on L2 which provides L1 block attributes + */ contract OpKnownStateRootWithHistory is KnownStateRootWithHistoryBase { - // https://community.optimism.io/docs/developers/build/differences/#accessing-l1-information + /* https://community.optimism.io/docs/developers/build/differences/#accessing-l1-information + Reference to the L1Block interface that will provide the L1 block information. */ IL1Block public immutable L1_BLOCK; + /** + * @dev Constructor to set the L1Block precompiler contract address + * @param _l1block Address of the L1Block contract + */ constructor(address _l1block) { L1_BLOCK = IL1Block(_l1block); } + /** + * @dev Fetches the L1 block hash and number from the L1Block contract, then stores it + * Emits an event after successfully setting the block hash + */ function setBlockHash() external { bytes32 l1BlockHash = L1_BLOCK.hash(); diff --git a/contracts/plugin/BasePlugin.sol b/contracts/plugin/BasePlugin.sol index 6b4aaef8..f67ab18a 100644 --- a/contracts/plugin/BasePlugin.sol +++ b/contracts/plugin/BasePlugin.sol @@ -5,33 +5,68 @@ import "../interfaces/IPlugin.sol"; import "../interfaces/ISoulWallet.sol"; import "../interfaces/IPluginManager.sol"; +/** + * @title BasePlugin + * @dev Abstract base contract for creating plugins. Implements the IPlugin interface. + */ abstract contract BasePlugin is IPlugin { + /** + * @dev Bit flags for specifying supported hook types + */ uint8 internal constant GUARD_HOOK = 1 << 0; uint8 internal constant PRE_HOOK = 1 << 1; uint8 internal constant POST_HOOK = 1 << 2; + /** + * @dev Emitted when the plugin is initialized for a wallet + */ event PluginInit(address indexed wallet); + /** + * @dev Emitted when the plugin is deinitialized for a wallet + */ event PluginDeInit(address indexed wallet); + /** + * @dev Internal utility function to get the sender of the transaction + */ function _sender() internal view returns (address) { return msg.sender; } + /** + * @dev Virtual function for plugin-specific initialization logic + */ function _init(bytes calldata data) internal virtual; - + /** + * @dev Virtual function for plugin-specific de-initialization logic + */ function _deInit() internal virtual; - + /** + * @dev Specifies the hook types this plugin supports + */ function _supportsHook() internal pure virtual returns (uint8 hookType); + /** + * @dev Utility function to retrieve the address of the wallet invoking the plugin + */ function _wallet() internal view returns (address wallet) { wallet = _sender(); } + /** + * @notice Checks if this contract implements a specific interface + */ function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { return interfaceId == type(IPlugin).interfaceId; } + /** + * @dev Checks if the plugin is initialized for the given wallet + */ function inited(address wallet) internal view virtual returns (bool); + /** + * @notice Initializes the plugin for a wallet + */ function walletInit(bytes calldata data) external override { address wallet = _wallet(); @@ -43,6 +78,9 @@ abstract contract BasePlugin is IPlugin { emit PluginInit(wallet); } } + /** + * @notice De-initializes the plugin for a wallet + */ function walletDeInit() external override { address wallet = _wallet(); diff --git a/contracts/trustedContractManager/ITrustedContractManager.sol b/contracts/trustedContractManager/ITrustedContractManager.sol index 4773ebe6..33c9386e 100644 --- a/contracts/trustedContractManager/ITrustedContractManager.sol +++ b/contracts/trustedContractManager/ITrustedContractManager.sol @@ -1,9 +1,26 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; +/** + * @title ITrustedContractManager Interface + * @dev This interface defines methods and events for managing trusted contracts + */ interface ITrustedContractManager { + /** + * @dev Emitted when a new trusted contract (module) is added + * @param module Address of the trusted contract added + */ event TrustedContractAdded(address indexed module); + /** + * @dev Emitted when a trusted contract (module) is removed + * @param module Address of the trusted contract removed + */ event TrustedContractRemoved(address indexed module); + /** + * @notice Checks if the specified address is a trusted contract + * @param addr Address to check + * @return Returns true if the address is a trusted contract, false otherwise + */ function isTrustedContract(address addr) external view returns (bool); } diff --git a/contracts/trustedContractManager/TrustedContractManager.sol b/contracts/trustedContractManager/TrustedContractManager.sol index d1b5d13d..bbe33c00 100644 --- a/contracts/trustedContractManager/TrustedContractManager.sol +++ b/contracts/trustedContractManager/TrustedContractManager.sol @@ -4,20 +4,45 @@ pragma solidity ^0.8.20; import "./ITrustedContractManager.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +/** + * @title TrustedContractManager + * @dev Implementation of the ITrustedContractManager interface + * Manages and checks the trusted contracts in the system + */ abstract contract TrustedContractManager is ITrustedContractManager, Ownable { + /// @notice Mapping to keep track of trusted contracts mapping(address => bool) private _trustedContract; + /** + * @dev Sets the initial owner of the contract to `_owner` + * @param _owner Address of the initial owner + */ constructor(address _owner) Ownable(_owner) {} + /** + * @notice Checks if the given address is a trusted contract + * @param module Address of the module to be checked + * @return True if the address is a trusted contract, false otherwise + */ function isTrustedContract(address module) external view returns (bool) { return _trustedContract[module]; } + /** + * @dev Internal function to check if the given address is a contract + * @param addr Address to be checked + * @return isContract True if the address has code (is a contract), false otherwise + */ function _isContract(address addr) private view returns (bool isContract) { assembly { isContract := gt(extcodesize(addr), 0) } } + /** + * @notice Adds one or more contracts to the list of trusted contracts + * Can only be called by the owner + * @param modules Addresses of the contracts to be added + */ function add(address[] memory modules) external onlyOwner { for (uint256 i = 0; i < modules.length; i++) { @@ -27,6 +52,11 @@ abstract contract TrustedContractManager is ITrustedContractManager, Ownable { emit TrustedContractAdded(modules[i]); } } + /** + * @notice Removes one or more contracts from the list of trusted contracts + * Can only be called by the owner + * @param modules Addresses of the contracts to be removed + */ function remove(address[] memory modules) external onlyOwner { for (uint256 i = 0; i < modules.length; i++) { diff --git a/contracts/validator/BaseValidator.sol b/contracts/validator/BaseValidator.sol index 8dd02756..c50ce357 100644 --- a/contracts/validator/BaseValidator.sol +++ b/contracts/validator/BaseValidator.sol @@ -7,15 +7,21 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../libraries/TypeConversion.sol"; import "../libraries/Errors.sol"; import "../libraries/WebAuthn.sol"; +/** + * @title BaseValidator + * @dev An abstract contract providing core signature validation functionalities + */ abstract contract BaseValidator is IValidator { using ECDSA for bytes32; using TypeConversion for address; + // Typehashes used for creating EIP-712 compliant messages bytes32 private constant SOUL_WALLET_MSG_TYPEHASH = keccak256("SoulWalletMessage(bytes32 message)"); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); + // Abstract functions that need to be implemented by derived contracts function _packSignatureHash(bytes32 hash, uint8 signatureType, uint256 validationData) internal @@ -28,6 +34,14 @@ abstract contract BaseValidator is IValidator { pure virtual returns (bytes32); + /** + * @dev Recovers the signer from a signature + * @param signatureType The type of signature + * @param rawHash The message hash that was signed + * @param rawSignature The signature itself + * @return recovered The recovered signer's address or public key as bytes32 + * @return success Indicates whether recovery was successful + */ function recover(uint8 signatureType, bytes32 rawHash, bytes calldata rawSignature) internal @@ -56,6 +70,14 @@ abstract contract BaseValidator is IValidator { revert Errors.INVALID_SIGNTYPE(); } } + /** + * @dev Recovers the signer from a validator signature + * @param rawHash The message hash that was signed + * @param rawSignature The signature itself + * @return validationData same as defined in EIP4337 + * @return recovered The recovered signer's address or public key as bytes32 + * @return success Indicates whether recovery was successful + */ function recoverSignature(bytes32 rawHash, bytes calldata rawSignature) external @@ -71,6 +93,14 @@ abstract contract BaseValidator is IValidator { (recovered, success) = recover(signatureType, hash, signature); } + /** + * @dev Recovers the signer from a EIP-1271 style signature + * @param rawHash The message hash that was signed + * @param rawSignature The signature itself + * @return validationData same as defined in EIP4337 + * @return recovered The recovered signer's address or public key as bytes32 + * @return success Indicates whether recovery was successful + */ function recover1271Signature(bytes32 rawHash, bytes calldata rawSignature) external @@ -84,12 +114,21 @@ abstract contract BaseValidator is IValidator { bytes32 hash = _pack1271SignatureHash(rawHash, signatureType, validationData); (recovered, success) = recover(signatureType, hash, signature); } + /** + * @dev Encodes a raw hash with EIP-712 compliant formatting + * @param rawHash The raw hash to be encoded + * @return The EIP-712 compliant encoded hash + */ function encodeRawHash(bytes32 rawHash) public view returns (bytes32) { bytes32 encode1271MessageHash = keccak256(abi.encode(SOUL_WALLET_MSG_TYPEHASH, rawHash)); bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), address(msg.sender))); return keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, encode1271MessageHash)); } + /** + * @dev Fetches the chain ID. This can be used for EIP-712 signature encoding + * @return The chain ID + */ function getChainId() public view returns (uint256) { uint256 id; diff --git a/contracts/validator/DefaultValidator.sol b/contracts/validator/DefaultValidator.sol index c2f73c95..8282d2b6 100644 --- a/contracts/validator/DefaultValidator.sol +++ b/contracts/validator/DefaultValidator.sol @@ -3,13 +3,27 @@ pragma solidity ^0.8.20; import "./BaseValidator.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +/** + * @title DefaultValidator + * @dev Provides default implementations for signature hash packing + */ contract DefaultValidator is BaseValidator { + // Utility for Ethereum typed structured data hashing using MessageHashUtils for bytes32; + // Utility for converting addresses to bytes32 using TypeConversion for address; /** - * @dev pack hash message with `signatureData.validationData` + * @dev Packs the given hash with the specified validation data based on the signature type + * - Type 0x0: Standard Ethereum signed message + * - Type 0x1: Ethereum signed message combined with validation data + * - Type 0x2: Passkey signature (unchanged hash) + * - Type 0x3: Passkey signature combined with validation data + * @param hash The original hash to be packed + * @param signatureType The type of signature + * @param validationData same as defined in EIP4337 + * @return packedHash The resulting hash after packing based on signature type */ function _packSignatureHash(bytes32 hash, uint8 signatureType, uint256 validationData) internal @@ -31,7 +45,18 @@ contract DefaultValidator is BaseValidator { revert Errors.INVALID_SIGNTYPE(); } } - // 1271 doesn't need toEthSignedMessageHash + /** + * @dev Packs the given hash for EIP-1271 compatible signatures. EIP-1271 represents signatures + * that are verified by smart contracts themselves. + * - Type 0x0: Unchanged hash. + * - Type 0x1: Hash combined with validation data + * - Type 0x2: Unchanged hash + * - Type 0x3: Hash combined with validation data + * @param hash The original hash to be packed + * @param signatureType The type of signature + * @param validationData Additional data used for certain signature types + * @return packedHash The resulting hash after packing + */ function _pack1271SignatureHash(bytes32 hash, uint8 signatureType, uint256 validationData) internal diff --git a/contracts/validator/KeystoreValidator.sol b/contracts/validator/KeystoreValidator.sol index 925bd332..a4af8703 100644 --- a/contracts/validator/KeystoreValidator.sol +++ b/contracts/validator/KeystoreValidator.sol @@ -2,13 +2,21 @@ pragma solidity ^0.8.20; import "./BaseValidator.sol"; +/** + * @title KeystoreValidator + * @dev Validates signatures based on the Keystore standard + */ contract KeystoreValidator is BaseValidator { using ECDSA for bytes32; using TypeConversion for address; /** - * @dev pack hash message with `signatureData.validationData` + * @dev Packs the hash message with `signatureData.validationData` + * @param hash The hash that needs to be packed with validationData + * @param signatureType The type of the signature + * @param validationData The data used for validation as per EIP-4337 + * @return packedHash The resultant packed hash */ function _packSignatureHash(bytes32 hash, uint8 signatureType, uint256 validationData) @@ -31,6 +39,14 @@ contract KeystoreValidator is BaseValidator { revert Errors.INVALID_SIGNTYPE(); } } + /** + * @dev Function for packing 1271 signature hash + * This implementation always reverts because `KeystoreValidator` doesn't support EIP-1271 signatures + * @param hash The hash to be packed + * @param signatureType The type of the signature + * @param validationData The data used for validation as per EIP-4337 + * @return This function always reverts and never returns + */ function _pack1271SignatureHash(bytes32 hash, uint8 signatureType, uint256 validationData) internal