From bedabe32abf67555e6d5eec76a90037bc29d3f84 Mon Sep 17 00:00:00 2001 From: Oleg Komendant <44612825+Hrom131@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:52:53 +0300 Subject: [PATCH] Natspec comments (#3) * Add interfaces * Add doc comments for core contracts * Add doc comments for modules --- contracts/core/AgentAccessControl.sol | 2 + contracts/core/KYCCompliance.sol | 3 + contracts/core/RegulatoryCompliance.sol | 4 + contracts/core/TokenF.sol | 54 +++---- .../core/storages/KYCComplianceStorage.sol | 14 +- .../storages/RegulatoryComplianceStorage.sol | 14 +- contracts/interfaces/IAgentAccessControl.sol | 24 +++ contracts/interfaces/IKYCCompliance.sol | 54 ++++++- contracts/interfaces/IKYCComplianceView.sol | 23 +++ .../interfaces/IRegulatoryCompliance.sol | 64 +++++++- .../interfaces/IRegulatoryComplianceView.sol | 23 +++ contracts/interfaces/ITokenF.sol | 149 ++++++++++++++++++ contracts/modules/AbstractKYCModule.sol | 10 ++ contracts/modules/AbstractModule.sol | 149 +++++++++++++++++- .../modules/AbstractRegulatoryModule.sol | 6 + contracts/modules/kyc/RarimoModule.sol | 4 + .../regulatory/TransferLimitsModule.sol | 4 + 17 files changed, 549 insertions(+), 52 deletions(-) create mode 100644 contracts/interfaces/IKYCComplianceView.sol create mode 100644 contracts/interfaces/IRegulatoryComplianceView.sol create mode 100644 contracts/interfaces/ITokenF.sol diff --git a/contracts/core/AgentAccessControl.sol b/contracts/core/AgentAccessControl.sol index 6029c67..0bdd687 100644 --- a/contracts/core/AgentAccessControl.sol +++ b/contracts/core/AgentAccessControl.sol @@ -18,6 +18,7 @@ abstract contract AgentAccessControl is AgentAccessControlStorage, DiamondAccessControl { + /// @inheritdoc IAgentAccessControl bytes32 public constant AGENT_ROLE = keccak256("AGENT_ROLE"); function __AgentAccessControl_init() @@ -27,6 +28,7 @@ abstract contract AgentAccessControl is grantRole(AGENT_ROLE, msg.sender); } + /// @inheritdoc IAgentAccessControl function checkRole(bytes32 role_, address account_) public view virtual { _checkRole(role_, account_); } diff --git a/contracts/core/KYCCompliance.sol b/contracts/core/KYCCompliance.sol index bfc0f23..a73b01e 100644 --- a/contracts/core/KYCCompliance.sol +++ b/contracts/core/KYCCompliance.sol @@ -25,18 +25,21 @@ abstract contract KYCCompliance is IKYCCompliance, KYCComplianceStorage, AgentAc function __KYCCompliance_init() internal onlyInitializing(KYC_COMPLIANCE_STORAGE_SLOT) {} + /// @inheritdoc IKYCCompliance function addKYCModules( address[] memory kycModules_ ) public virtual onlyRole(_KYCComplianceRole()) { _addKYCModules(kycModules_); } + /// @inheritdoc IKYCCompliance function removeKYCModules( address[] memory kycModules_ ) public virtual onlyRole(_KYCComplianceRole()) { _removeKYCModules(kycModules_); } + /// @inheritdoc IKYCCompliance function isKYCed(TokenF.Context calldata ctx_) public view virtual returns (bool) { address[] memory regulatoryModules_ = getKYCModules(); diff --git a/contracts/core/RegulatoryCompliance.sol b/contracts/core/RegulatoryCompliance.sol index 4fc7166..4012157 100644 --- a/contracts/core/RegulatoryCompliance.sol +++ b/contracts/core/RegulatoryCompliance.sol @@ -37,18 +37,21 @@ abstract contract RegulatoryCompliance is onlyInitializing(REGULATORY_COMPLIANCE_STORAGE_SLOT) {} + /// @inheritdoc IRegulatoryCompliance function addRegulatoryModules( address[] memory rModules_ ) public virtual onlyRole(_regulatoryComplianceRole()) { _addRegulatoryModules(rModules_); } + /// @inheritdoc IRegulatoryCompliance function removeRegulatoryModules( address[] memory rModules_ ) public virtual onlyRole(_regulatoryComplianceRole()) { _removeRegulatoryModules(rModules_); } + /// @inheritdoc IRegulatoryCompliance function transferred(TokenF.Context calldata ctx_) public virtual onlyThis { address[] memory regulatoryModules_ = getRegulatoryModules(); @@ -57,6 +60,7 @@ abstract contract RegulatoryCompliance is } } + /// @inheritdoc IRegulatoryCompliance function canTransfer(TokenF.Context calldata ctx_) public view virtual returns (bool) { address[] memory regulatoryModules_ = getRegulatoryModules(); diff --git a/contracts/core/TokenF.sol b/contracts/core/TokenF.sol index 5739b9f..6f0b280 100644 --- a/contracts/core/TokenF.sol +++ b/contracts/core/TokenF.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; + import {Diamond} from "@solarity/solidity-lib/diamond/Diamond.sol"; import {DiamondERC20} from "@solarity/solidity-lib/diamond/tokens/ERC20/DiamondERC20.sol"; +import {ITokenF} from "../interfaces/ITokenF.sol"; import {IKYCCompliance} from "../interfaces/IKYCCompliance.sol"; import {IRegulatoryCompliance} from "../interfaces/IRegulatoryCompliance.sol"; @@ -12,18 +15,7 @@ import {TokenFStorage} from "./storages/TokenFStorage.sol"; import {RegulatoryComplianceStorage} from "./storages/RegulatoryComplianceStorage.sol"; import {KYCComplianceStorage} from "./storages/KYCComplianceStorage.sol"; -/** - * @notice The TokenF contract - * - * The TokenF is a Diamond-based ERC20 token implementation enabling the storage of all core contracts under the same Diamond proxy. - * - * The TokenF provides flexibility for implementing eligibility checks through the integration of compliance modules without - * affecting the standard ERC20 behaviour. - * - * Transfer methods forward the entire transfer context to compliance modules, ensuring adherence to specific requirements, - * such as regulatory standards or KYC protocols. - */ -abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessControl { +abstract contract TokenF is ITokenF, TokenFStorage, Diamond, DiamondERC20, AgentAccessControl { bytes4 public constant TRANSFER_SELECTOR = this.transfer.selector; bytes4 public constant TRANSFER_FROM_SELECTOR = this.transferFrom.selector; bytes4 public constant MINT_SELECTOR = this.mint.selector; @@ -31,15 +23,6 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon bytes4 public constant FORCED_TRANSFER_SELECTOR = this.forcedTransfer.selector; bytes4 public constant RECOVERY_SELECTOR = this.recovery.selector; - struct Context { - bytes4 selector; - address from; - address to; - uint256 amount; - address operator; - bytes data; - } - function __TokenF_init( address regulatoryCompliance_, address kycCompliance_, @@ -70,7 +53,11 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon _diamondCut(new Facet[](0), kycCompliance_, initKYC_); } - function transfer(address to_, uint256 amount_) public virtual override returns (bool) { + /// @inheritdoc IERC20 + function transfer( + address to_, + uint256 amount_ + ) public virtual override(DiamondERC20, IERC20) returns (bool) { _canTransfer(msg.sender, to_, amount_, address(0)); _isKYCed(msg.sender, to_, amount_, address(0)); @@ -81,11 +68,12 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon return true; } + /// @inheritdoc IERC20 function transferFrom( address from_, address to_, uint256 amount_ - ) public virtual override returns (bool) { + ) public virtual override(DiamondERC20, IERC20) returns (bool) { _canTransfer(from_, to_, amount_, msg.sender); _isKYCed(from_, to_, amount_, msg.sender); @@ -96,10 +84,11 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon return true; } + /// @inheritdoc ITokenF function mint( address account_, uint256 amount_ - ) public virtual onlyRole(_mintRole()) returns (bool) { + ) public virtual override onlyRole(_mintRole()) returns (bool) { _canTransfer(address(0), account_, amount_, msg.sender); _isKYCed(address(0), account_, amount_, msg.sender); @@ -110,10 +99,11 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon return true; } + /// @inheritdoc ITokenF function burn( address account_, uint256 amount_ - ) public virtual onlyRole(_burnRole()) returns (bool) { + ) public virtual override onlyRole(_burnRole()) returns (bool) { _canTransfer(account_, address(0), amount_, msg.sender); _isKYCed(account_, address(0), amount_, msg.sender); @@ -124,11 +114,12 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon return true; } + /// @inheritdoc ITokenF function forcedTransfer( address from_, address to_, uint256 amount_ - ) public virtual onlyRole(_forcedTransferRole()) returns (bool) { + ) public virtual override onlyRole(_forcedTransferRole()) returns (bool) { _canTransfer(from_, to_, amount_, msg.sender); _isKYCed(from_, to_, amount_, msg.sender); @@ -139,10 +130,11 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon return true; } + /// @inheritdoc ITokenF function recovery( address oldAccount_, address newAccount_ - ) public virtual onlyRole(_recoveryRole()) returns (bool) { + ) public virtual override onlyRole(_recoveryRole()) returns (bool) { uint256 oldBalance_ = balanceOf(oldAccount_); _canTransfer(oldAccount_, newAccount_, oldBalance_, msg.sender); @@ -155,15 +147,19 @@ abstract contract TokenF is TokenFStorage, Diamond, DiamondERC20, AgentAccessCon return true; } - function diamondCut(Facet[] memory modules_) public virtual onlyRole(_diamondCutRole()) { + /// @inheritdoc ITokenF + function diamondCut( + Facet[] memory modules_ + ) public virtual override onlyRole(_diamondCutRole()) { diamondCut(modules_, address(0), ""); } + /// @inheritdoc ITokenF function diamondCut( Facet[] memory modules_, address initModule_, bytes memory initData_ - ) public virtual onlyRole(_diamondCutRole()) { + ) public virtual override onlyRole(_diamondCutRole()) { _diamondCut(modules_, initModule_, initData_); } diff --git a/contracts/core/storages/KYCComplianceStorage.sol b/contracts/core/storages/KYCComplianceStorage.sol index 0ccae0b..ffeda5a 100644 --- a/contracts/core/storages/KYCComplianceStorage.sol +++ b/contracts/core/storages/KYCComplianceStorage.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.20; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -abstract contract KYCComplianceStorage { +import {IKYCComplianceView} from "../../interfaces/IKYCComplianceView.sol"; + +abstract contract KYCComplianceStorage is IKYCComplianceView { using EnumerableSet for EnumerableSet.AddressSet; bytes32 internal constant KYC_COMPLIANCE_STORAGE_SLOT = @@ -13,12 +15,14 @@ abstract contract KYCComplianceStorage { EnumerableSet.AddressSet kycModules; } - function getKYCModules() public view virtual returns (address[] memory) { - return _getKYCComplianceStorage().kycModules.values(); + /// @inheritdoc IKYCComplianceView + function getKYCModulesCount() public view virtual override returns (uint256) { + return _getKYCComplianceStorage().kycModules.length(); } - function getKYCModulesCount() public view virtual returns (uint256) { - return _getKYCComplianceStorage().kycModules.length(); + /// @inheritdoc IKYCComplianceView + function getKYCModules() public view virtual override returns (address[] memory) { + return _getKYCComplianceStorage().kycModules.values(); } function _getKYCComplianceStorage() internal pure returns (KYCCStorage storage _kyccStorage) { diff --git a/contracts/core/storages/RegulatoryComplianceStorage.sol b/contracts/core/storages/RegulatoryComplianceStorage.sol index fe86166..3e48137 100644 --- a/contracts/core/storages/RegulatoryComplianceStorage.sol +++ b/contracts/core/storages/RegulatoryComplianceStorage.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.20; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -abstract contract RegulatoryComplianceStorage { +import {IRegulatoryComplianceView} from "../../interfaces/IRegulatoryComplianceView.sol"; + +abstract contract RegulatoryComplianceStorage is IRegulatoryComplianceView { using EnumerableSet for EnumerableSet.AddressSet; bytes32 internal constant REGULATORY_COMPLIANCE_STORAGE_SLOT = @@ -13,12 +15,14 @@ abstract contract RegulatoryComplianceStorage { EnumerableSet.AddressSet regulatoryModules; } - function getRegulatoryModules() public view returns (address[] memory) { - return _getRegulatoryComplianceStorage().regulatoryModules.values(); + /// @inheritdoc IRegulatoryComplianceView + function getRegulatoryModulesCount() public view virtual override returns (uint256) { + return _getRegulatoryComplianceStorage().regulatoryModules.length(); } - function getRegulatoryModulesCount() public view virtual returns (uint256) { - return _getRegulatoryComplianceStorage().regulatoryModules.length(); + /// @inheritdoc IRegulatoryComplianceView + function getRegulatoryModules() public view virtual override returns (address[] memory) { + return _getRegulatoryComplianceStorage().regulatoryModules.values(); } function _getRegulatoryComplianceStorage() diff --git a/contracts/interfaces/IAgentAccessControl.sol b/contracts/interfaces/IAgentAccessControl.sol index 078c026..a36ce80 100644 --- a/contracts/interfaces/IAgentAccessControl.sol +++ b/contracts/interfaces/IAgentAccessControl.sol @@ -3,8 +3,32 @@ pragma solidity ^0.8.20; import {IAccessControl} from "@solarity/solidity-lib/diamond/access/access-control/DiamondAccessControl.sol"; +/** + * @notice The `AgentAccessControl` contract is an add-on to Solarity's `DiamondAccessControl` and adds one basic role, `AGENT_ROLE`, to its implementation. + * This role is used in the base version of the `TokenF` framework for all privileged functions such as `mint`, `burn`, `addKYCModules`, etc. + */ interface IAgentAccessControl is IAccessControl { + /** + * @notice Function that returns the key for the base role is `AGENT_ROLE`. + * + * All addresses that own this role are privileged and can call various functions to manage parts of the token. + * + * In a basic implementation of `TokenF`, a user with the Agent role can call absolutely all the privileged functions, such as `mint`, `burn` and etc. + * + * The Agent role key itself is created as follows - `keccak256("AGENT_ROLE")` + * + * @return The key for the agent role + */ function AGENT_ROLE() external view returns (bytes32); + /** + * @notice Function that is required to check whether a particular user has the required role for the contract logic. + * + * If the user does not have the required role, the transaction will fail with the error + * `AccessControl: account ** is missing role **`. + * + * @param role_ The role key to check + * @param account_ The account for role verification + */ function checkRole(bytes32 role_, address account_) external view; } diff --git a/contracts/interfaces/IKYCCompliance.sol b/contracts/interfaces/IKYCCompliance.sol index d12f700..cc36939 100644 --- a/contracts/interfaces/IKYCCompliance.sol +++ b/contracts/interfaces/IKYCCompliance.sol @@ -1,12 +1,60 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {TokenF} from "../core/TokenF.sol"; +import {ITokenF} from "./ITokenF.sol"; +import {IKYCComplianceView} from "./IKYCComplianceView.sol"; -interface IKYCCompliance { +/** + * @notice `KYCCompliance` contract is used to manage KYC Compliance modules. + * It performs storage, addition of new KYC modules and deletion of existing KYC modules. + * + * It also implements the `isKYCed` hook, which in turn is called by the `TokenF` contract. + * + * All actions for module management can only be done by users who have a special role. + * In the basic version of `TokenF` this role is the Agent role. + * + * It is possible to override the role that is required to configure the module list. + * + * Also this contract is used as a facet in the `TokenF` contract. + */ +interface IKYCCompliance is IKYCComplianceView { + /** + * @notice Function is required to add new KYC modules to a `KYCCompliance` contract. + * + * If you try to add a module that already exists in the list of KYC modules, + * the transaction will fail with an error - `SetHelper: element already exists`. + * + * This function in the basic `TokenF` implementation can only be called by users who have Agent role. + * + * An internal function `_KYCComplianceRole` is used to retrieve the role that is used in the validation, + * which can be overridden if you want to use a role other than Agent. + * + * @param kycModules_ The array of KYC modules to add + */ function addKYCModules(address[] memory kycModules_) external; + /** + * @notice Function is required to remove existing KYC modules from the list in the `KYCCompliance` contract. + * + * If you try to delete a module that is not in the list of KYC modules, + * the transaction will fail with an error - `SetHelper: no such element`. + * + * This function in the basic `TokenF` implementation can only be called by users who have the Agent role. + * + * An internal function `_KYCComplianceRole` is used to retrieve the role that is used in the validation, + * which can be overridden if you want to use a role other than Agent. + * + * @param kycModules_ The array with KYC modules to remove + */ function removeKYCModules(address[] memory kycModules_) external; - function isKYCed(TokenF.Context calldata ctx_) external view returns (bool); + /** + * @notice Function that is used to verify that all required KYC rules that have been added to `KYCCompliance` are met. + * + * The entire transaction context is passed to the checker, giving modules full information for further checks. + * + * @param ctx_ The context of the transaction + * @return true if the passed context satisfies the checks on all modules + */ + function isKYCed(ITokenF.Context calldata ctx_) external view returns (bool); } diff --git a/contracts/interfaces/IKYCComplianceView.sol b/contracts/interfaces/IKYCComplianceView.sol new file mode 100644 index 0000000..1c859da --- /dev/null +++ b/contracts/interfaces/IKYCComplianceView.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @notice `IKYCComplianceView` interface stores all view functions that are in the `KYCCompliance` contract + */ +interface IKYCComplianceView { + /** + * @notice Function to get the total number of KYC modules, + * that are currently added to the list of `KYCCompliance` contract modules + * + * @return Total number of all KYC modules in the `KYCCompliance` contract + */ + function getKYCModulesCount() external view returns (uint256); + + /** + * @notice Function to get the address list of all KYC modules, + * that are currently added to the list of `KYCCompliance` contract modules + * + * @return Array of addresses of all KYC modules in the `KYCCompliance` contract + */ + function getKYCModules() external view returns (address[] memory); +} diff --git a/contracts/interfaces/IRegulatoryCompliance.sol b/contracts/interfaces/IRegulatoryCompliance.sol index f888054..30772d1 100644 --- a/contracts/interfaces/IRegulatoryCompliance.sol +++ b/contracts/interfaces/IRegulatoryCompliance.sol @@ -1,14 +1,70 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {TokenF} from "../core/TokenF.sol"; +import {ITokenF} from "./ITokenF.sol"; +import {IRegulatoryComplianceView} from "./IRegulatoryComplianceView.sol"; -interface IRegulatoryCompliance { +/** + * @notice `RegulatoryCompliance` contract is used to manage Regulatory Compliance modules. + * It manages the storage, addition of new and deletion of existing Regulatory Compliance modules. + * + * It also implements the `canTransfer` and `transferred` hooks, which in turn are called by the `TokenF` contract. + * + * All actions for module management can only be done by users who have a special role. + * In the basic version of `TokenF` this role is the Agent role. + * + * It is possible to override the role that is required to configure the module list. + * + * Also this contract is used as a facet in the `TokenF` contract. + */ +interface IRegulatoryCompliance is IRegulatoryComplianceView { + /** + * @notice Function is required to add new regulatory modules to the `RegulatoryCompliance` contract. + * + * If you try to add a module that already exists in the list of regulatory modules, + * the transaction will fail with an error - `SetHelper: element already exists`. + * + * This function in the basic `TokenF` implementation can only be called by users who have the Agent role. + * + * An internal function `_regulatoryComplianceRole` is used to retrieve the role that is used during validation, + * which can be overridden if you want to use a role other than Agent. + * + * @param rModules_ The array with regulatory modules to add + */ function addRegulatoryModules(address[] memory rModules_) external; + /** + * @notice Function is required to delete existing regulatory modules from the list in the `RegulatoryCompliance` contract. + * + * If you try to delete a module that is not in the list of regulatory modules, + * the transaction will crash with an error - `SetHelper: no such element`. + * + * This function in the basic `TokenF` implementation can only be called by users who have the Agent role. + * + * An internal function `_regulatoryComplianceRole` is used to retrieve the role that is used during validation, + * which can be overridden if you want to use a role other than Agent + * + * @param rModules_ The array with regulatory modules to remove + */ function removeRegulatoryModules(address[] memory rModules_) external; - function transferred(TokenF.Context calldata ctx_) external; + /** + * @notice Function that is needed to write some information to modules after the main transaction logic has been executed. + * + * This hook can be used, for example, to restrict token transfers within one day. + * That is, it is an addition to modules that need to save an additional state to check a rule. + * + * @param ctx_ The context of transaction + */ + function transferred(ITokenF.Context calldata ctx_) external; - function canTransfer(TokenF.Context calldata ctx_) external view returns (bool); + /** + * @notice Function that is used to verify that all necessary regulatory rules that have been added to `RegulatoryCompliance` have been met. + * + * The entire transaction context is passed for validation, giving modules full information for further checks. + * + * @param ctx_ The context of transaction + * @return true if the passed context satisfies the rules in all installed regulatory modules. + */ + function canTransfer(ITokenF.Context calldata ctx_) external view returns (bool); } diff --git a/contracts/interfaces/IRegulatoryComplianceView.sol b/contracts/interfaces/IRegulatoryComplianceView.sol new file mode 100644 index 0000000..1665bc7 --- /dev/null +++ b/contracts/interfaces/IRegulatoryComplianceView.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @notice The `IRegulatoryComplianceView` interface stores all the view functions that are in the `RegulatoryCompliance` contract + */ +interface IRegulatoryComplianceView { + /** + * @notice Function to get the total number of regulatory modules, + * that are currently added to the list of `RegulatoryCompliance` contract modules + * + * @return Total number of all regulatory modules in the `RegulatoryCompliance` contract + */ + function getRegulatoryModulesCount() external view returns (uint256); + + /** + * @notice Function to get a list of addresses of all regulatory modules, + * that are currently added to the list of `RegulatoryCompliance` contract modules + * + * @return Array of addresses of all regulatory modules in the `RegulatoryCompliance` contract + */ + function getRegulatoryModules() external view returns (address[] memory); +} diff --git a/contracts/interfaces/ITokenF.sol b/contracts/interfaces/ITokenF.sol new file mode 100644 index 0000000..64cfdd6 --- /dev/null +++ b/contracts/interfaces/ITokenF.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +import {Diamond} from "@solarity/solidity-lib/diamond/Diamond.sol"; + +/** + * @notice The `TokenF` contract + * + * The `TokenF` is a Diamond-based ERC20 token implementation enabling the storage of all core contracts under the same Diamond proxy. + * + * The `TokenF` provides flexibility for implementing eligibility checks through the integration of compliance modules without + * affecting the standard ERC20 behaviour. + * + * Transfer methods forward the entire transfer context to compliance modules, ensuring adherence to specific requirements, + * such as regulatory standards or KYC protocols. + * + * `TokenF` is also inherited from `AgentAccessControl`, which is built on Solarity's `DiamondAccessControl`. + * This inheritance allows to realise a rather flexible system of roles for controlling privileged functions in the whole system. + */ +interface ITokenF is IERC20Metadata { + struct Context { + bytes4 selector; + address from; + address to; + uint256 amount; + address operator; + bytes data; + } + + /** + * @notice Function to create new `TokenF` contract tokens. + * + * The `isKYCed` hook from the `KYCCompliance` contract is used inside the function to check KYC. + * The `canTransfer` and `transferred` hooks are used to check the established regulatory rules + * from `RegulatoryCompliance` contract + * + * This function can only be called by users who have a special role. + * In the base version of `TokenF` this role is the Agent role. + * + * To change the role used for access validation, + * you need to override `_mintRole` according to the requirements of your business task. + * + * @param account_ The address to which tokens should be minted + * @param amount_ The amount of tokens to be minted + * @return true in all cases if not reverted + */ + function mint(address account_, uint256 amount_) external returns (bool); + + /** + * @notice Function to burn existing `TokenF` contract tokens. + * + * The `isKYCed` hook from the `KYCCompliance` contract is used within the function to check KYC. + * The `canTransfer` and `transferred` hooks are used to check the established regulatory rules + * from the `RegulatoryCompliance` contract. + * + * This function can only be called by users who have a special role. + * In the base version of `TokenF` this role is the Agent role. + * + * To change the role used for access verification, + * you must override `_burnRole` according to the requirements of your business task. + * + * @param account_ The address of the user whose balance you want to burn tokens from + * @param amount_ The amount of tokens to be burned + * @return true in all cases if not reverted + */ + function burn(address account_, uint256 amount_) external returns (bool); + + /** + * @notice Function for forced transfering from one address to another. + * + * This logic may be needed for smart contracts that will operate with `TokenF` tokens. + * It will be convenient because users will not need to call `approve` function additionally. + * + * The `isKYCed` hook from the `KYCCompliance` contract is used inside the function to check KYC. + * The `canTransfer` and `transferred` hooks are used to check the established regulatory rules + * from `RegulatoryCompliance` contract. + * + * This function can only be called by users who have a special role. + * In the base version of `TokenF` this role is the Agent role. + * + * To change the role used for access verification, + * you must override `_forcedTransferRole` according to the requirements of your business task. + * + * @param from_ The user address where the tokens will be transferred from + * @param to_ The user address to whom tokens will be transferred + * @param amount_ The amount of tokens to be transferred + * @return true in all cases if not reverted + */ + function forcedTransfer(address from_, address to_, uint256 amount_) external returns (bool); + + /** + * @notice Function for balance transfer from one account to another. + * + * This function can be useful if the user has lost the private key to his account. + * In this case, the system can check the user's KYC and transfer the user's funds to a new account. + * + * The `isKYCed` hook from the `KYCCompliance` contract is used inside the function to check the KYC. + * The `canTransfer` and `transferred` hooks are used to check the established regulatory rules + * from `RegulatoryCompliance` contract. + * + * This function can only be called by users who have a special role. + * In the base version of `TokenF` this role is the Agent role. + * + * To change the role used for access verification, + * you need to redefine `_recoveryRole` according to the requirements of your business task. + * + * @param oldAccount_ The address of the user's old account + * @param newAccount_ The address of the new user account to which the tokens will be migrated + * @return true in all cases if not reverted + */ + function recovery(address oldAccount_, address newAccount_) external returns (bool); + + /** + * @notice The function is required to manage the list of facets in a `TokenF` contract. + * It can be used to add, delete and update existing facets. + * + * This function can only be called by users who have a special role. + * In the basic version of `TokenF` this role is the Agent role. + * + * To change the role used for access validation, + * you must override `_diamondCutRole` according to the requirements of your business task + * + * @param modules_ The array of modules to update + */ + function diamondCut(Diamond.Facet[] memory modules_) external; + + /** + * @notice This function overloads another `diamondCut` function `TokenF` of the contract, + * allowing you to pass an additional calldata to call the necessary methods using `delegateCall` on the facet address. + * Often this calldata is needed to call various init functions. + * + * This function can only be called by users who have a special role. + * In the basic version of `TokenF` this role is the Agent role. + * + * To change the role used for access validation, + * you must override `_diamondCutRole` according to the requirements of your business task. + * + * @param modules_ The array of modules to update + * @param initModule_ The address of the module to execute the passed caldata + * @param initData_ The calldata to be executed + */ + function diamondCut( + Diamond.Facet[] memory modules_, + address initModule_, + bytes memory initData_ + ) external; +} diff --git a/contracts/modules/AbstractKYCModule.sol b/contracts/modules/AbstractKYCModule.sol index 3d6d2ad..61a8183 100644 --- a/contracts/modules/AbstractKYCModule.sol +++ b/contracts/modules/AbstractKYCModule.sol @@ -5,6 +5,16 @@ import {TokenF} from "../core/TokenF.sol"; import {AbstractModule} from "./AbstractModule.sol"; +/** + * @notice The `AbstractKYCModule` contract is the standard base implementation for KYC modules. + * + * This implementation overrides the `_getExtContexts` and `_getClaimTopicKey` functions so that, + * that `keccak256(selector, TransferParty)` is used to create the claim topic key, + * and `_getExtContexts` returns extended contexts to validate each `TransferParty`, + * where `TransferParty` is a `SENDER`, `RECIPIENT`, or `OPERATOR`. + * + * Existing overrides allow you to specify checks for a specific function and a specific `TransferParty`. + */ abstract contract AbstractKYCModule is AbstractModule { enum TransferParty { Sender, diff --git a/contracts/modules/AbstractModule.sol b/contracts/modules/AbstractModule.sol index 1844bc8..b8245e6 100644 --- a/contracts/modules/AbstractModule.sol +++ b/contracts/modules/AbstractModule.sol @@ -11,9 +11,9 @@ import {IAgentAccessControl} from "../interfaces/IAgentAccessControl.sol"; import {TokenF} from "../core/TokenF.sol"; /** - * @notice The AbstractModule contract + * @notice The `AbstractModule` contract * - * The AbstractModule contract provides a framework for implementing compliance modules. + * The `AbstractModule` contract provides a framework for implementing compliance modules. * * Each module is capable of matching claim topics to corresponding handlers, with claim topics organized under * user-defined claim topic keys. @@ -67,10 +67,17 @@ abstract contract AbstractModule is Initializable { _handlerer(); } - function _handlerer() internal virtual; - - function _getClaimTopicKey(TokenF.Context memory ctx_) internal view virtual returns (bytes32); - + /** + * @notice Function for adding an array of claim topics for the corresponding claim topic key. + * + * This function in the basic `TokenF` implementation can only be called by users who have the Agent role. + * + * An internal function `_complianceModuleRole` is used to retrieve the role that is used in the validation, + * which can be overridden if you want to use a role other than Agent. + * + * @param claimTopicKey_ The key of the claim topics + * @param claimTopics_ Array of claim topics to add + */ function addClaimTopics( bytes32 claimTopicKey_, bytes32[] memory claimTopics_ @@ -78,6 +85,17 @@ abstract contract AbstractModule is Initializable { _addClaimTopics(claimTopicKey_, claimTopics_); } + /** + * @notice Function for removing an array of claim topics from the list of the corresponding claim topic key. + * + * This function in the basic `TokenF` implementation can only be called by users who have the Agent role. + * + * An internal function `_complianceModuleRole` is used to retrieve the role that is used in the validation, + * which can be overridden if you want to use a role other than Agent. + * + * @param claimTopicKey_ The key of the claim topics + * @param claimTopics_ Array of claim topics to be removed + */ function removeClaimTopics( bytes32 claimTopicKey_, bytes32[] memory claimTopics_ @@ -85,16 +103,36 @@ abstract contract AbstractModule is Initializable { _removeClaimTopics(claimTopicKey_, claimTopics_); } + /** + * @notice Function to retrieve all stored claim topics by the passed claim topic key. + * + * @param claimTopicsKey_ The key of the claim topics for which the array should be obtained + * @return claim topics array + */ function getClaimTopics( bytes32 claimTopicsKey_ ) public view virtual returns (bytes32[] memory) { return _claimTopics[claimTopicsKey_].values(); } + /** + * @notice Function to get the `TokenF` address of the contract to which this module contract is bound. + * + * @return address of `TokenF` contract + */ function getTokenF() public view virtual returns (address) { return _tokenF; } + /** + * @notice Internal function to add an array of claim topics for the passed claim topic key. + * + * In case it is necessary to change or extend the logic of adding claim topics, + * you can override this function and make any necessary changes. + * + * @param claimTopicKey_ The claim topic key + * @param claimTopics_ The array of claim topics to add + */ function _addClaimTopics( bytes32 claimTopicKey_, bytes32[] memory claimTopics_ @@ -102,6 +140,15 @@ abstract contract AbstractModule is Initializable { _claimTopics[claimTopicKey_].strictAdd(claimTopics_); } + /** + * @notice Internal function to remove the claim topics array for the passed claim topic key. + * + * In case you want to change or extend the logic of deleting claim topics, + * you can override this function and make any necessary changes. + * + * @param claimTopicKey_ The claim topic key + * @param claimTopics_ The array of claim topicsto be removed + */ function _removeClaimTopics( bytes32 claimTopicKey_, bytes32[] memory claimTopics_ @@ -109,6 +156,15 @@ abstract contract AbstractModule is Initializable { _claimTopics[claimTopicKey_].strictRemove(claimTopics_); } + /** + * @notice Function to save a function handler to a storedge by a specific key. + * Often this function will be used in pair with the `_handlerer` function. + * + * If you need to extend the logic, you can also override this function. + * + * @param claimTopic_ The label of the topic for which the handler is to be set + * @param handler_ Pointer to the handler function + */ function _setHandler( bytes32 claimTopic_, function(TokenF.Context memory) internal view returns (bool) handler_ @@ -119,6 +175,53 @@ abstract contract AbstractModule is Initializable { _handler.handler = handler_; } + /** + * @notice Function that sets internally all existing handlers in the storage. + * + * When adding new handlers, you must override this function with a call to `super._handlerer()` inside the new implementation, + * so as not to lose any handlers already configured. + * + * An example of a possible implementation: + * + * ```solidity + * function _handlerer() internal virtual override { + * _setHandler(, _yourHandlerFunc); + * } + * ``` + */ + function _handlerer() internal virtual; + + /** + * @notice Function to retrieve the claim topic key from the transaction context. + * + * The `bytes32` type has been chosen for the claim topic key so that it could be customised. + * Depending on the future purpose of the module, it will be possible to define the process of creating a claim topic key, + * which allows the modules to be quite flexible. + * + * An example of a possible implementation, where the claim topic key is a hash of the function selector, + * i.e. the module only needs to be able to define different rules for different functions: + * + * ```solidity + * function _getClaimTopicKey( + * TokenF.Context memory ctx_ + * ) internal view virtual override returns (bytes32) { + * return keccak256(abi.encodePacked(ctx_.selector)); + * } + * ``` + * + * @param ctx_ The transaction context + * @return claim topic key + */ + function _getClaimTopicKey(TokenF.Context memory ctx_) internal view virtual returns (bytes32); + + /** + * @notice The main function to process the passed transaction context. + * + * Within it, all the set claim topics are retrieved by the claim topic key. + * Then for each claim topic a handler function is obtained, which processes the passed context. + * + * @param ctx_ The transaction context + */ function _handle(TokenF.Context calldata ctx_) internal view virtual returns (bool) { TokenF.Context[] memory ctxs_ = _getExtContexts(ctx_); @@ -141,6 +244,15 @@ abstract contract AbstractModule is Initializable { return true; } + /** + * @notice Function to retrieve a previously saved handler function by claim topic. + * + * In case no function handler has been set for the passed claim topic, + * transaction will fail with the error - `AModule: handler is not set`. + * + * @param claimTopic_ The claim topic for which a handler function is to be retrieved + * @return pointer to the previously saved handler function + */ function _getHandler( bytes32 claimTopic_ ) @@ -156,6 +268,31 @@ abstract contract AbstractModule is Initializable { return _handler.handler; } + /** + * @notice The function is required to extend the main context of the transaction. + * + * In case when it is necessary to make several checks for different parts of the transaction, + * it is necessary to have corresponding contexts for each check. This is what this function is for. + * + * For example in the case of KYC checks it may be necessary to have different checks for different transferParties (from, to or operator). + * In this way it is possible to extend the contexts and add transferParty information to the data field: + * + * ```solidity + * function _getExtContexts( + * TokenF.Context calldata ctx_ + * ) internal view virtual override returns (TokenF.Context[] memory) { + * TokenF.Context[] memory ctxs_ = new TokenF.Context[](3); + * ctxs_[0] = _getExtContext(ctx_, TransferParty.Sender); + * ctxs_[1] = _getExtContext(ctx_, TransferParty.Recipient); + * ctxs_[2] = _getExtContext(ctx_, TransferParty.Operator); + * + * return ctxs_; + * } + * ``` + * + * @param ctx_ The initial transaction context + * @return array of extended contexts + */ function _getExtContexts( TokenF.Context calldata ctx_ ) internal view virtual returns (TokenF.Context[] memory) { diff --git a/contracts/modules/AbstractRegulatoryModule.sol b/contracts/modules/AbstractRegulatoryModule.sol index c761e61..f271eae 100644 --- a/contracts/modules/AbstractRegulatoryModule.sol +++ b/contracts/modules/AbstractRegulatoryModule.sol @@ -5,6 +5,12 @@ import {TokenF} from "../core/TokenF.sol"; import {AbstractModule} from "./AbstractModule.sol"; +/** + * @notice `AbstractRegulatoryModule` contract is the standard base implementation for regulatory modules. + * + * In this implementation, the overridden function `_getClaimTopicKey` creates a claim topic key as follows - `keccak256(selector)`, + * which allows the rules to be defined separately for each function. + */ abstract contract AbstractRegulatoryModule is AbstractModule { function __AbstractRegulatoryModule_init() internal onlyInitializing {} diff --git a/contracts/modules/kyc/RarimoModule.sol b/contracts/modules/kyc/RarimoModule.sol index f2dd5cc..ec3dac2 100644 --- a/contracts/modules/kyc/RarimoModule.sol +++ b/contracts/modules/kyc/RarimoModule.sol @@ -8,6 +8,10 @@ import {ISBT} from "@solarity/solidity-lib/interfaces/tokens/ISBT.sol"; import {TokenF} from "../../core/TokenF.sol"; import {AbstractKYCModule} from "../AbstractKYCModule.sol"; +/** + * @notice `RarimoModule` is an example of a possible KYC module implementation, + * within which the user's SBT token is checked. + */ abstract contract RarimoModule is AbstractKYCModule { using Address for address; diff --git a/contracts/modules/regulatory/TransferLimitsModule.sol b/contracts/modules/regulatory/TransferLimitsModule.sol index 87f4fbf..bb3bc2b 100644 --- a/contracts/modules/regulatory/TransferLimitsModule.sol +++ b/contracts/modules/regulatory/TransferLimitsModule.sol @@ -5,6 +5,10 @@ import {TokenF} from "../../core/TokenF.sol"; import {AbstractRegulatoryModule} from "../AbstractRegulatoryModule.sol"; +/** + * @notice `TransferLimitsModule` is an example of a possible implementation of a regulatory module, + * which adds rules for minimum and maximum amount for transfers. + */ abstract contract TransferLimitsModule is AbstractRegulatoryModule { bytes32 public constant MIN_TRANSFER_LIMIT_TOPIC = keccak256("MIN_TRANSFER_LIMIT"); bytes32 public constant MAX_TRANSFER_LIMIT_TOPIC = keccak256("MAX_TRANSFER_LIMIT");