diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecebbd0..b7d0a95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,5 +24,5 @@ jobs: - name: Run Forge build run: forge build --sizes - - name: Run Forge tests - run: forge test -vvv + - name: Run Forge tests --ffi + run: forge test -vvv \ No newline at end of file diff --git a/classDiagram.png b/classDiagram.png new file mode 100644 index 0000000..5f3586a Binary files /dev/null and b/classDiagram.png differ diff --git a/classDiagram.svg b/classDiagram.svg new file mode 100644 index 0000000..f8c1804 --- /dev/null +++ b/classDiagram.svg @@ -0,0 +1,133 @@ + + + + + + +UmlClassDiagram + + + +0 + +IncomeVault +src/IncomeVault.sol + +Private: +   __gap: uint256[50] + +Internal: +    __IncomeVault_init(admin: address, ERC20TokenPayment_: IERC20, cmtat_token: ICMTATSnapshot, ruleEngine_: IRuleEngine, authorizationEngineIrrevocable: IAuthorizationEngine) <<onlyInitializing>> +    _msgSender(): (sender: address) +    _msgData(): bytes +    _contextSuffixLength(): uint256 +Public: +    constructor(forwarderIrrevocable: address) +    initialize(admin: address, ERC20TokenPayment_: IERC20, cmtat_token: ICMTATSnapshot, ruleEngine_: IRuleEngine, authorizationEngineIrrevocable: IAuthorizationEngine) <<initializer>> + + + +3 + +<<Abstract>> +IncomeVaultOpen +src/public/IncomeVaultOpen.sol + +Private: +   __gap: uint256[50] + +Public: +    claimDividend(time: uint256) <<nonReentrant>> +    claimDividendBatch(time: uint256) <<nonReentrant>> + + + +0->3 + + + + + +4 + +<<Abstract>> +IncomeVaultRestricted +src/public/IncomeVaultRestricted.sol + +Private: +   __gap: uint256[50] + +Public: +    deposit(time: uint256, amount: uint256) <<onlyRole>> +    withdraw(time: uint256, amount: uint256, withdrawAddress: address) <<onlyRole>> +    withdrawAll(amount: uint256, withdrawAddress: address) <<onlyRole>> +    distributeDividend(addresses: address[], time: uint256) <<onlyRole>> +    setStatusClaim(time: uint256, status: bool) <<onlyRole>> + + + +0->4 + + + + + +1 + +<<Abstract>> +IncomeVaultInternal +src/lib/IncomeVaultInternal.sol + +Public: +   CMTAT_TOKEN: ICMTATSnapshot +   ERC20TokenPayment: IERC20 +   claimedDividend: mapping(address=>mapping(uint256=>bool)) +   segragatedDividend: mapping(uint256=>uint256) +   segragatedClaim: mapping(uint256=>bool) + +Internal: +    _computeDividendBatch(time: uint256, tokenHolders: address[], tokenHoldersBalance: uint256[], tokenTotalSupply: uint256): (tokenHolderDividend: uint256[]) +    _computeDividend(time: uint256, senderBalance: uint256, tokenTotalSupply: uint256): (tokenHolderDividend: uint256) +    _transferDividend(time: uint256, tokenHolder: address, tokenHolderDividend: uint256) + + + +2 + +<<Abstract>> +IncomeVaultInvariantStorage +src/lib/IncomeVaultInvariantStorage.sol + +Public: +   INCOME_VAULT_OPERATOR_ROLE: bytes32 +   INCOME_VAULT_DEPOSIT_ROLE: bytes32 +   INCOME_VAULT_DISTRIBUTE_ROLE: bytes32 +   INCOME_VAULT_WITHDRAW_ROLE: bytes32 + +Public: +    <<event>> newDeposit(time: uint256, sender: address, dividend: uint256) +    <<event>> DividendClaimed(time: uint256, sender: address, dividend: uint256) + + + +1->2 + + + + + +3->1 + + + + + +4->1 + + + + + diff --git a/doc/schema/Debt b/doc/schema/Debt index 1911f8c..f28029c 100644 --- a/doc/schema/Debt +++ b/doc/schema/Debt @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/package.json b/package.json index f998f08..819ced5 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "uml": "npx sol2uml class src", "uml:test": "npx sol2uml class test", "surya:report": "npx surya mdreport surya_report_debtVault.md src/DebtVault.sol", - "surya:graph": "npx surya graph src/DebtVault.sol | dot -Tpng > surya_graph_DebtVault.png ", + "surya:graph": "npx surya graph src/IncomeVault.sol | dot -Tpng > surya_graph_IncomeVault.png && npx surya graph src/public/IncomeVaultRestricted.sol | dot -Tpng > surya_graph_IncomeVaultRestricted.png && npx surya graph src/public/IncomeVaultOpen.sol | dot -Tpng > surya_graph_IncomeVaultOpen.png ", "docgen": "npx hardhat docgen" }, "devDependencies": { diff --git a/src/DebtVault.sol b/src/DebtVault.sol deleted file mode 100644 index 30a5c43..0000000 --- a/src/DebtVault.sol +++ /dev/null @@ -1,356 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -pragma solidity ^0.8.20; - -//import "OZ/token/ERC20/IERC20.sol"; -import "OZ/token/ERC20/utils/SafeERC20.sol"; -import "lib/CMTAT/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; -import "CMTAT/interfaces/ICMTATSnapshot.sol"; -import "CMTAT/modules/wrapper/controllers/ValidationModule.sol"; -import "CMTAT/modules/wrapper/extensions/MetaTxModule.sol"; -import "./invariantStorage/DebtVaultInvariantStorage.sol"; -//import "OZUpgradeable/proxy/utils/Initializable.sol"; - -/** -* @title Debt Vault to distribute dividend -*/// AuthorizationModuleStandalone -contract DebtVault is Initializable, ContextUpgradeable, ReentrancyGuardUpgradeable, DebtVaultInvariantStorage, MetaTxModule, ValidationModule { - // CMTAT token - ICMTATSnapshot CMTAT_TOKEN; - //IRuleEngine ruleEngine; - - uint128 internal constant POINTS_MULTIPLIER = type(uint64).max; - IERC20 public ERC20TokenPayment; - mapping(address => mapping (uint256 => bool)) public claimedDividend; - mapping(uint256 => uint256) public segragatedDividend; - mapping(uint256 => bool) public segragatedClaim; - - // Security - using SafeERC20 for IERC20; - - /** - * @param forwarderIrrevocable Address of the forwarder, required for the gasless support - */ - /// @custom:oz-upgrades-unsafe-allow constructor - constructor( - address forwarderIrrevocable - ) MetaTxModule(forwarderIrrevocable) { - // Disable the possibility to initialize the implementation - _disableInitializers(); - } - - /** - * @notice - * initialize the proxy contract - * The calls to this function will revert if the contract was deployed without a proxy - * @param admin Address of the contract (Access Control) - * @param ERC20TokenPayment_ ERC20 token to perform the payment - */ - function initialize( - address admin, - IERC20 ERC20TokenPayment_, - ICMTATSnapshot cmtat_token, - IRuleEngine ruleEngine_, - IAuthorizationEngine authorizationEngineIrrevocable - ) public initializer { - __DebtVault_init( - admin, - ERC20TokenPayment_, - cmtat_token, - ruleEngine_, - authorizationEngineIrrevocable - ); - } - - /** - * @dev calls the different initialize functions from the different modules - */ - function __DebtVault_init( - address admin, - IERC20 ERC20TokenPayment_, - ICMTATSnapshot cmtat_token, - IRuleEngine ruleEngine_, - IAuthorizationEngine authorizationEngineIrrevocable - ) internal onlyInitializing { - if(admin == address(0)){ - revert AdminWithAddressZeroNotAllowed(); - } - if(address(ERC20TokenPayment_) == address(0)){ - revert TokenPaymentWithAddressZeroNotAllowed(); - } - __Validation_init_unchained(ruleEngine_); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(DEBT_VAULT_OPERATOR_ROLE, admin); - CMTAT_TOKEN = cmtat_token; - ERC20TokenPayment = ERC20TokenPayment_; - - // Initialization - __AccessControl_init_unchained(); - __Pausable_init_unchained(); - __Validation_init_unchained(ruleEngine_); - - __AuthorizationModule_init_unchained(admin, authorizationEngineIrrevocable); - // PauseModule_init_unchained is called before ValidationModule_init_unchained due to inheritance - __PauseModule_init_unchained(); - __ValidationModule_init_unchained(); - } - - /** - * @notice claim your payment - * @param time provide the date where you want to receive your payment - */ - function claimDividend(uint256 time) public nonReentrant() { - // Check if the claim is activated - if(!segragatedClaim[time]){ - revert claimNotActivated(); - } - address sender = _msgSender(); - // At the beginning since no external call to do - if (claimedDividend[sender][time]){ - revert dividendAlreadyClaimed(); - } - // External call to the CMTAT to retrieve the total supply and the sender balance - (uint256 senderBalance, uint256 TokenTotalSupply) = CMTAT_TOKEN.snapshotInfo(time, sender); - if (senderBalance == 0){ - revert noDividendToClaim(); - } - /** - SenderBalance = 300 - totalSupply = 900 - Dividend total supply= 200 - If POINTS_MULTIPLIER = 100, then - 300 * 100 / 900 = 30000 / 900 = 33 (33.333333333) - dividend = 200 * 33 / 100 = 66 - Other formule - dividend = (300 * 200) / 900 = 60000 / 900 = 600/9 = 66.6 = 66 - */ - //uint256 partShare = (senderBalance * POINTS_MULTIPLIER) / TokenTotalSupply; - //uint256 dividendTotalSupply = segragatedDividend[time]; - //uint256 dividend = (dividendTotalSupply * partShare) / POINTS_MULTIPLIER; - - uint256 senderDividend = _computeDividend(time, senderBalance, TokenTotalSupply); - - // Transfer restriction - if (!ValidationModule._operateOnTransfer(address(this), sender, senderDividend)) { - revert Errors.CMTAT_InvalidTransfer(address(this), sender, senderDividend); - } - _transferDividend(time, sender, senderDividend); - } - - /** - * @notice claim your payment - * @param time provide the date where you want to receive your payment - */ - function claimDividendBatch(uint256 time) public nonReentrant() { - // Check if the claim is activated - if(!segragatedClaim[time]){ - revert claimNotActivated(); - } - address sender = _msgSender(); - // At the beginning since no external call to do - if (claimedDividend[sender][time]){ - revert dividendAlreadyClaimed(); - } - // External call to the CMTAT to retrieve the total supply and the sender balance - (uint256 senderBalance, uint256 TokenTotalSupply) = CMTAT_TOKEN.snapshotInfo(time, sender); - if (senderBalance == 0){ - revert noDividendToClaim(); - } - - uint256 senderDividend = _computeDividend(time, senderBalance, TokenTotalSupply); - - // Transfer restriction - if (!ValidationModule._operateOnTransfer(address(this), sender, senderDividend)) { - revert Errors.CMTAT_InvalidTransfer(address(this), sender, senderDividend); - } - _transferDividend(time, sender, senderDividend); - } - - /** - * @notice deposit an amount to pay the dividends. - * @param time provide the date where you want to perform a deposit - * @param amount the amount to deposit - */ - function deposit(uint256 time, uint256 amount) public onlyRole(DEBT_VAULT_DEPOSIT_ROLE) { - address sender = _msgSender(); - if(amount == 0) { - revert noAmountSend(); - } - segragatedDividend[time] += amount; - emit newDeposit(time, sender, amount); - // Will revert in case of failure - ERC20TokenPayment.safeTransferFrom(sender, address(this), amount); - } - - /** - * @notice withdraw a certain amount at a specified time. - * @param time provide the date where you want to perform a deposit - * @param amount the amount to withdraw - * @param withdrawAddress address to receive `amount`of tokens - */ - function withdraw(uint256 time, uint256 amount, address withdrawAddress) public onlyRole(DEBT_VAULT_WITHDRAW_ROLE) { - // TODO: check why it is necessary - ERC20TokenPayment.approve(address(this), amount); - if(segragatedDividend[time] < amount) { - revert notEnoughAmount(); - } - segragatedDividend[time] -= amount; - // Will revert in case of failure - ERC20TokenPayment.safeTransferFrom(address(this), withdrawAddress, amount); - } - - /** - * @notice withdraw all tokens from ERC20TokenPayment contracts deposited - * @param amount the amount to withdraw - * @param withdrawAddress address to receive `amount`of tokens - */ - function withdrawAll(uint256 amount, address withdrawAddress) public onlyRole(DEBT_VAULT_WITHDRAW_ROLE) { - // TODO: check why it is necessary - ERC20TokenPayment.approve(address(this), amount); - // Will revert in case of failure - ERC20TokenPayment.safeTransferFrom(address(this), withdrawAddress, amount); - } - - /** - * @notice deposit an amount to pay the dividends. - * @param addresses compute and transfer dividend for these holders - * @param time dividend time - */ - function distributeDividend(address[] calldata addresses, uint256 time) public onlyRole(DEBT_VAULT_DISTRIBUTE_ROLE) { - // Check if the claim is activated - if(!segragatedClaim[time]){ - revert claimNotActivated(); - } - // Get info from the token - (uint256[] memory tokenHolderBalance, uint256 totalSupply) = CMTAT_TOKEN.snapshotInfoBatch(time, addresses); - // Compute dividend for all token holders - uint256[] memory tokenHolderDividend = _computeDividendBatch(time, addresses, tokenHolderBalance, totalSupply); - // transfer the dividends for all token holders - for(uint256 i = 0; i < addresses.length; ++i){ - // Do nothing if the token holder has already claimed its dividends. - if (!claimedDividend[addresses[i]][time]){ - // Compute dividend - if(tokenHolderDividend[i] > 0){ - _transferDividend(time, addresses[i], tokenHolderDividend[i]); - } - } - } - } - //snapshotBalanceOfBatch - - /** - * @notice set the status to open or close the claims for a given time - * @param time target time - * @param status boolean (true or false) - * - */ - function setStatusClaim(uint256 time, bool status) public onlyRole(DEBT_VAULT_OPERATOR_ROLE){ - segragatedClaim[time] = status; - } - - /*function setTokenPayment(IERC20 ERC20TokenPayment_) public onlyRole(DEBT_VAULT_OPERATOR_ROLE){ - if(address(ERC20TokenPayment_) != address(0)){ - ERC20TokenPayment = ERC20TokenPayment_; - } - }*/ - - /*function setTokenCMTAT(CMTAT_BASE cmtat_token) public onlyRole(DEBT_VAULT_OPERATOR_ROLE){ - if(address(cmtat_token) != address(0) && address(CMTAT_TOKEN) == address(0)){ - CMTAT_TOKEN = cmtat_token; - } - }*/ - - /** - * @param time dividend time - * @param tokenHolders addresses to compute dividend - * @param tokenHoldersBalance the sender balance - * @param tokenTotalSupply the total supply - */ - function _computeDividendBatch(uint256 time, address[] calldata tokenHolders, uint256[] memory tokenHoldersBalance, uint256 tokenTotalSupply) internal view returns(uint256[] memory tokenHolderDividend){ - tokenHolderDividend = new uint256[](tokenHolders.length); - uint256 dividendTotalSupply = segragatedDividend[time]; - for(uint256 i = 0; i < tokenHolders.length; ++i){ - if(tokenHoldersBalance[i] > 0) { - tokenHolderDividend[i] = (tokenHoldersBalance[i] * dividendTotalSupply) / tokenTotalSupply; - } - } - } - - /** - * @param time dividend time - * @param senderBalance token holder balance - * @param tokenTotalSupply the total supply - */ - function _computeDividend(uint256 time, uint256 senderBalance, uint256 tokenTotalSupply) internal view returns(uint256 tokenHolderDividend){ - if (senderBalance == 0){ - revert noDividendToClaim(); - } - /** - SenderBalance = 300 - totalSupply = 900 - Dividend total supply= 200 - If POINTS_MULTIPLIER = 100, then - 300 * 100 / 900 = 30000 / 900 = 33 (33.333333333) - dividend = 200 * 33 / 100 = 66 - Other formule - dividend = (300 * 200) / 900 = 60000 / 900 = 600/9 = 66.6 = 66 - */ - //uint256 partShare = (senderBalance * POINTS_MULTIPLIER) / TokenTotalSupply; - uint256 dividendTotalSupply = segragatedDividend[time]; - //uint256 dividend = (dividendTotalSupply * partShare) / POINTS_MULTIPLIER; - - // 300 * 200 * 100000000 / 900 / 100000000 = 66 - // 300 * 200 / 900 = 66 - // 603 * 404 / 2115 = 115 - - tokenHolderDividend = (senderBalance * dividendTotalSupply) / tokenTotalSupply; - } - - /** - * @param time dividend time - * @param tokenHolder addresses to send the dividends - * @param tokenHolderDividend the computed dividends - */ - function _transferDividend(uint256 time, address tokenHolder, uint256 tokenHolderDividend) internal{ - // Before ERC-20 transfer to avoid re-entrancy attack - claimedDividend[tokenHolder][time] = true; - emit DividendClaimed(time, tokenHolder, tokenHolderDividend); - // transfer - // We don't revert if SenderBalance == 0 to record the claim - if(tokenHolderDividend != 0){ - // Will revert in case of failure - // We should put that in a try catch for the batch version ??? - ERC20TokenPayment.safeTransfer(tokenHolder, tokenHolderDividend); - } - } - - /** - * @dev This surcharge is not necessary if you do not use the MetaTxModule - */ - function _msgSender() - internal - view - override(ERC2771ContextUpgradeable, ContextUpgradeable) - returns (address sender) - { - return ERC2771ContextUpgradeable._msgSender(); - } - - /** - * @dev This surcharge is not necessary if you do not use the MetaTxModule - */ - function _msgData() - internal - view - override(ERC2771ContextUpgradeable, ContextUpgradeable) - returns (bytes calldata) - { - return ERC2771ContextUpgradeable._msgData(); - } - - function _contextSuffixLength() internal view - override(ERC2771ContextUpgradeable, ContextUpgradeable) - returns (uint256) { - return ERC2771ContextUpgradeable._contextSuffixLength(); - } -} diff --git a/src/IncomeVault.sol b/src/IncomeVault.sol new file mode 100644 index 0000000..1b5a1f4 --- /dev/null +++ b/src/IncomeVault.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + + +import "CMTAT/modules/wrapper/extensions/MetaTxModule.sol"; +import "./public/IncomeVaultRestricted.sol"; +import "./public/IncomeVaultOpen.sol"; + +/** +* @title Income Vault to distribute dividends +*/ +contract IncomeVault is Initializable, ContextUpgradeable, IncomeVaultRestricted, IncomeVaultOpen, MetaTxModule{ + + /** + * @param forwarderIrrevocable Address of the forwarder, required for the gasless support + */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor( + address forwarderIrrevocable + ) MetaTxModule(forwarderIrrevocable) { + // Disable the possibility to initialize the implementation + _disableInitializers(); + } + + /** + * @notice + * initialize the proxy contract + * The calls to this function will revert if the contract was deployed without a proxy + * @param admin Address of the contract (Access Control) + * @param ERC20TokenPayment_ ERC20 token to perform the payment + */ + function initialize( + address admin, + IERC20 ERC20TokenPayment_, + ICMTATSnapshot cmtat_token, + IRuleEngine ruleEngine_, + IAuthorizationEngine authorizationEngineIrrevocable + ) public initializer { + __IncomeVault_init( + admin, + ERC20TokenPayment_, + cmtat_token, + ruleEngine_, + authorizationEngineIrrevocable + ); + } + + /** + * @dev calls the different initialize functions from the different modules + */ + function __IncomeVault_init( + address admin, + IERC20 ERC20TokenPayment_, + ICMTATSnapshot cmtat_token, + IRuleEngine ruleEngine_, + IAuthorizationEngine authorizationEngineIrrevocable + ) internal onlyInitializing { + if(admin == address(0)){ + revert AdminWithAddressZeroNotAllowed(); + } + if(address(ERC20TokenPayment_) == address(0)){ + revert TokenPaymentWithAddressZeroNotAllowed(); + } + __Validation_init_unchained(ruleEngine_); + _grantRole(DEFAULT_ADMIN_ROLE, admin); + _grantRole(INCOME_VAULT_OPERATOR_ROLE, admin); + CMTAT_TOKEN = cmtat_token; + ERC20TokenPayment = ERC20TokenPayment_; + + // Initialization + __AccessControl_init_unchained(); + __Pausable_init_unchained(); + __Validation_init_unchained(ruleEngine_); + + __AuthorizationModule_init_unchained(admin, authorizationEngineIrrevocable); + // PauseModule_init_unchained is called before ValidationModule_init_unchained due to inheritance + __PauseModule_init_unchained(); + __ValidationModule_init_unchained(); + } + + /** + * @dev This surcharge is not necessary if you do not use the MetaTxModule + */ + function _msgSender() + internal + view + override(ERC2771ContextUpgradeable, ContextUpgradeable) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); + } + + /** + * @dev This surcharge is not necessary if you do not use the MetaTxModule + */ + function _msgData() + internal + view + override(ERC2771ContextUpgradeable, ContextUpgradeable) + returns (bytes calldata) + { + return ERC2771ContextUpgradeable._msgData(); + } + + function _contextSuffixLength() internal view + override(ERC2771ContextUpgradeable, ContextUpgradeable) + returns (uint256) { + return ERC2771ContextUpgradeable._contextSuffixLength(); + } + + uint256[50] private __gap; +} diff --git a/src/invariantStorage/DebtVaultInvariantStorage.sol b/src/invariantStorage/DebtVaultInvariantStorage.sol deleted file mode 100644 index d08a636..0000000 --- a/src/invariantStorage/DebtVaultInvariantStorage.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -pragma solidity ^0.8.20; - -abstract contract DebtVaultInvariantStorage { - // Role - bytes32 public constant DEBT_VAULT_OPERATOR_ROLE = keccak256("DEBT_VAULT_OPERATOR_ROLE"); - bytes32 public constant DEBT_VAULT_DEPOSIT_ROLE = keccak256("DEBT_VAULT_DEPOSIT_ROLE"); - bytes32 public constant DEBT_VAULT_DISTRIBUTE_ROLE = keccak256("DEBT_VAULT_DEPOSIT_ROLE"); - bytes32 public constant DEBT_VAULT_WITHDRAW_ROLE = keccak256("DEBT_VAULT_WITHDRAW_ROLE"); - - // errors - error claimNotActivated(); - error dividendAlreadyClaimed(); - error noDividendToClaim(); - error AdminWithAddressZeroNotAllowed(); - error TokenPaymentWithAddressZeroNotAllowed(); - error noAmountSend(); - error notEnoughAmount(); - - // event - event newDeposit(uint256 indexed time, address indexed sender, uint256 dividend); - event DividendClaimed(uint256 indexed time, address indexed sender, uint256 dividend); -} \ No newline at end of file diff --git a/src/public/IncomeVaultOpen.sol b/src/public/IncomeVaultOpen.sol new file mode 100644 index 0000000..87b8101 --- /dev/null +++ b/src/public/IncomeVaultOpen.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + + +import "lib/CMTAT/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import "../lib/IncomeVaultInternal.sol"; +import "CMTAT/modules/wrapper/controllers/ValidationModule.sol"; +/** +* @title Debt Vault to distribute dividend +*/ +abstract contract IncomeVaultOpen is ReentrancyGuardUpgradeable, ValidationModule , IncomeVaultInternal { + /** + * @notice claim your payment + * @param time provide the date where you want to receive your payment + */ + function claimDividend(uint256 time) public nonReentrant() { + // Check if the claim is activated + if(!segragatedClaim[time]){ + revert claimNotActivated(); + } + address sender = _msgSender(); + // At the beginning since no external call to do + if (claimedDividend[sender][time]){ + revert dividendAlreadyClaimed(); + } + // External call to the CMTAT to retrieve the total supply and the sender balance + (uint256 senderBalance, uint256 TokenTotalSupply) = CMTAT_TOKEN.snapshotInfo(time, sender); + if (senderBalance == 0){ + revert noDividendToClaim(); + } + + uint256 senderDividend = _computeDividend(time, senderBalance, TokenTotalSupply); + + // Transfer restriction + if (!ValidationModule._operateOnTransfer(address(this), sender, senderDividend)) { + revert Errors.CMTAT_InvalidTransfer(address(this), sender, senderDividend); + } + _transferDividend(time, sender, senderDividend); + } + + /** + * @notice claim your payment + * @param times provide the dates where you want to receive your payment + * @dev Don't check if the dividends have been already claimed before external call to CMTAT. + */ + function claimDividendBatch(uint256[] memory times) public nonReentrant() { + // Check if the claim is activated for each times + for(uint256 i = 0; i < times.length; ++i){ + if(!segragatedClaim[times[i]]){ + revert claimNotActivated(); + } + } + address sender = _msgSender(); + address[] memory senders = new address[](1); + senders[0] = sender; + //function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses) public view returns (uint256[][] memory ownerBalances, uint256[] memory totalSupply) { + // External call to the CMTAT to retrieve the total supply and the sender balance + (uint256[][] memory senderBalances, uint256[] memory TokenTotalSupplys) = CMTAT_TOKEN.snapshotInfoBatch(times, senders); + for(uint256 i = 0; i < times.length; ++i){ + if (!claimedDividend[sender][times[i]] && (senderBalances[i][0] > 0)){ + uint256 senderDividend = _computeDividend(times[i], senderBalances[i][0], TokenTotalSupplys[i]); + // Transfer restriction + // External Call + if (!ValidationModule._operateOnTransfer(address(this), sender, senderDividend)) { + revert Errors.CMTAT_InvalidTransfer(address(this), sender, senderDividend); + } + // internal call performing an ERC-20 external call + _transferDividend(times[i], sender, senderDividend); + } + } + } + uint256[50] private __gap; +} diff --git a/src/public/IncomeVaultRestricted.sol b/src/public/IncomeVaultRestricted.sol new file mode 100644 index 0000000..16bf857 --- /dev/null +++ b/src/public/IncomeVaultRestricted.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + +import "OZ/token/ERC20/utils/SafeERC20.sol"; +import "CMTAT/modules/wrapper/controllers/ValidationModule.sol"; +import "../lib/IncomeVaultInternal.sol"; + +/** +* @title Debt Vault to distribute dividend +*/// AuthorizationModuleStandalone +abstract contract IncomeVaultRestricted is ValidationModule, IncomeVaultInternal { + + + // Security + using SafeERC20 for IERC20; + + /** + * @notice deposit an amount to pay the dividends. + * @param time provide the date where you want to perform a deposit + * @param amount the amount to deposit + */ + function deposit(uint256 time, uint256 amount) public onlyRole(INCOME_VAULT_DEPOSIT_ROLE) { + address sender = _msgSender(); + if(amount == 0) { + revert noAmountSend(); + } + segragatedDividend[time] += amount; + emit newDeposit(time, sender, amount); + // Will revert in case of failure + ERC20TokenPayment.safeTransferFrom(sender, address(this), amount); + } + + /** + * @notice withdraw a certain amount at a specified time. + * @param time provide the date where you want to perform a deposit + * @param amount the amount to withdraw + * @param withdrawAddress address to receive `amount`of tokens + */ + function withdraw(uint256 time, uint256 amount, address withdrawAddress) public onlyRole(INCOME_VAULT_WITHDRAW_ROLE) { + // TODO: check why it is necessary + ERC20TokenPayment.approve(address(this), amount); + if(segragatedDividend[time] < amount) { + revert notEnoughAmount(); + } + segragatedDividend[time] -= amount; + // Will revert in case of failure + ERC20TokenPayment.safeTransferFrom(address(this), withdrawAddress, amount); + } + + /** + * @notice withdraw all tokens from ERC20TokenPayment contracts deposited + * @param amount the amount to withdraw + * @param withdrawAddress address to receive `amount`of tokens + */ + function withdrawAll(uint256 amount, address withdrawAddress) public onlyRole(INCOME_VAULT_WITHDRAW_ROLE) { + // TODO: check why it is necessary + ERC20TokenPayment.approve(address(this), amount); + // Will revert in case of failure + ERC20TokenPayment.safeTransferFrom(address(this), withdrawAddress, amount); + } + + /** + * @notice deposit an amount to pay the dividends. + * @param addresses compute and transfer dividend for these holders + * @param time dividend time + */ + function distributeDividend(address[] calldata addresses, uint256 time) public onlyRole(INCOME_VAULT_DISTRIBUTE_ROLE) { + // Check if the claim is activated + if(!segragatedClaim[time]){ + revert claimNotActivated(); + } + // Get info from the token + (uint256[] memory tokenHolderBalance, uint256 totalSupply) = CMTAT_TOKEN.snapshotInfoBatch(time, addresses); + // Compute dividend for all token holders + uint256[] memory tokenHolderDividend = _computeDividendBatch(time, addresses, tokenHolderBalance, totalSupply); + // transfer the dividends for all token holders + for(uint256 i = 0; i < addresses.length; ++i){ + // Do nothing if the token holder has already claimed its dividends. + if (!claimedDividend[addresses[i]][time]){ + // Compute dividend + if(tokenHolderDividend[i] > 0){ + _transferDividend(time, addresses[i], tokenHolderDividend[i]); + } + } + } + } + + /** + * @notice set the status to open or close the claims for a given time + * @param time target time + * @param status boolean (true or false) + * + */ + function setStatusClaim(uint256 time, bool status) public onlyRole(INCOME_VAULT_OPERATOR_ROLE){ + segragatedClaim[time] = status; + } + + /*function setTokenPayment(IERC20 ERC20TokenPayment_) public onlyRole(DEBT_VAULT_OPERATOR_ROLE){ + if(address(ERC20TokenPayment_) != address(0)){ + ERC20TokenPayment = ERC20TokenPayment_; + } + }*/ + + /*function setTokenCMTAT(CMTAT_BASE cmtat_token) public onlyRole(DEBT_VAULT_OPERATOR_ROLE){ + if(address(cmtat_token) != address(0) && address(CMTAT_TOKEN) == address(0)){ + CMTAT_TOKEN = cmtat_token; + } + }*/ + uint256[50] private __gap; +} diff --git a/surya_graph_DebtVault.png b/surya_graph_DebtVault.png new file mode 100644 index 0000000..e69de29 diff --git a/surya_graph_IncomeVault.png b/surya_graph_IncomeVault.png new file mode 100644 index 0000000..e0bd643 Binary files /dev/null and b/surya_graph_IncomeVault.png differ diff --git a/surya_graph_IncomeVaultOpen.png b/surya_graph_IncomeVaultOpen.png new file mode 100644 index 0000000..5addb41 Binary files /dev/null and b/surya_graph_IncomeVaultOpen.png differ diff --git a/surya_graph_IncomeVaultRestricted.png b/surya_graph_IncomeVaultRestricted.png new file mode 100644 index 0000000..c141d54 Binary files /dev/null and b/surya_graph_IncomeVaultRestricted.png differ diff --git a/test/HelperContract.sol b/test/HelperContract.sol index 6a5684b..2cf90ed 100644 --- a/test/HelperContract.sol +++ b/test/HelperContract.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "CMTAT/CMTAT_STANDALONE.sol"; -import "../src/DebtVault.sol"; +import "../src/IncomeVault.sol"; //import "../src/invariantStorage/DebtVaultInvariantStorage.sol"; import "RuleEngine/RuleEngine.sol"; import "RuleEngine/rules/validation/RuleWhitelist.sol"; @@ -13,7 +13,7 @@ import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; /** * @title Constants used by the tests */ -abstract contract HelperContract is DebtVaultInvariantStorage { +abstract contract HelperContract is IncomeVaultInvariantStorage { // EOA to perform tests address constant ZERO_ADDRESS = address(0); address constant DEFAULT_ADMIN_ADDRESS = address(1); @@ -49,7 +49,7 @@ abstract contract HelperContract is DebtVaultInvariantStorage { // Contracts CMTAT_STANDALONE tokenPayment; - DebtVault debtVault; + IncomeVault debtVault; // CMTAT value uint256 FLAG = 5; uint8 DECIMALS = 0; diff --git a/test/DebtVault.t.sol b/test/IncomeVault.t.sol similarity index 75% rename from test/DebtVault.t.sol rename to test/IncomeVault.t.sol index 49e084a..5ce3afc 100644 --- a/test/DebtVault.t.sol +++ b/test/IncomeVault.t.sol @@ -5,13 +5,13 @@ import "forge-std/Test.sol"; import "./HelperContract.sol"; import "CMTAT/interfaces/engine/IRuleEngine.sol"; import "CMTAT/interfaces/engine/IAuthorizationEngine.sol"; -import {DebtVault} from "../src/DebtVault.sol"; +import {IncomeVault} from "../src/IncomeVault.sol"; //import {Upgrades,} from "openzeppelin-foundry-upgrades/Upgrades.sol"; /** * @title Test for DebtVault */ -contract debtVaultTest is Test, HelperContract { +contract IncomeVaultTest is Test, HelperContract { uint256 resUint256; uint8 resUint8; bool resBool; @@ -25,7 +25,7 @@ contract debtVaultTest is Test, HelperContract { // Arrange function setUp() public { // Deploy CMTAT - CMTAT_CONTRACT = new CMTAT_STANDALONE( + CMTAT_CONTRACT = new CMTAT_STANDALONE( ZERO_ADDRESS, CMTAT_ADMIN, IAuthorizationEngine(address(0)), @@ -56,16 +56,16 @@ contract debtVaultTest is Test, HelperContract { Options memory opts; opts.constructorData = abi.encode(ZERO_ADDRESS); address proxy = Upgrades.deployTransparentProxy( - "DebtVault.sol", + "IncomeVault.sol", DEFAULT_ADMIN_ADDRESS, - abi.encodeCall(DebtVault.initialize, ( DEFAULT_ADMIN_ADDRESS, + abi.encodeCall(IncomeVault.initialize, ( DEFAULT_ADMIN_ADDRESS, tokenPayment, ICMTATSnapshot(address(CMTAT_CONTRACT)), IRuleEngine(ZERO_ADDRESS), IAuthorizationEngine(ZERO_ADDRESS))), opts ); - debtVault = DebtVault(proxy); + debtVault = IncomeVault(proxy); // Deploy DebtVault /*debtVault = new DebtVault( ZERO_ADDRESS @@ -86,26 +86,6 @@ contract debtVaultTest is Test, HelperContract { } - function testDepositRoleCanPerformDeposit() public { - uint256 time = 200; - // Allowance - vm.prank(DEFAULT_ADMIN_ADDRESS); - tokenPayment.approve(address(debtVault), defaultDepositAmount); - // Act - vm.prank(DEFAULT_ADMIN_ADDRESS); - //Event - vm.expectEmit(true, true, false, true); - emit newDeposit( - time, - DEFAULT_ADMIN_ADDRESS, - defaultDepositAmount - ); - debtVault.deposit(time, defaultDepositAmount); - // Assert - resUint256 = debtVault.segragatedDividend(time); - assertEq(resUint256, defaultDepositAmount); - } - function testHolderCanClaimWithZeroDeposit() public { // Arrange // Configure snapshot @@ -175,6 +155,48 @@ contract debtVaultTest is Test, HelperContract { assertEq(resUint256, defaultDepositAmount); } + function testHolderCanBatchClaimWithDepositAndOneHolder() public { + // Arrange + // First deposit + _performDeposit(); + + + + + // Second deposit + uint256 newTime = defaultSnapshotTime + 50; + uint256[] memory times = new uint256[](2); + times[0] = defaultSnapshotTime; + times[1] = newTime; + // Set the new approval + vm.prank(DEFAULT_ADMIN_ADDRESS); + tokenPayment.approve(address(debtVault), defaultDepositAmount * 2); + + // Deposit + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.deposit(newTime, defaultDepositAmount); + + // Timeout + uint256 timeout = newTime + 50; + vm.warp(timeout); + + // Open claim first deposit + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.setStatusClaim(defaultSnapshotTime, true); + + // Open claim second deposit + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.setStatusClaim(newTime, true); + + // Claim deposit + vm.prank(ADDRESS1); + debtVault.claimDividendBatch(times); + + // Check balance + resUint256 = tokenPayment.balanceOf(ADDRESS1); + assertEq(resUint256, defaultDepositAmount * 2); + } + function testHolderCannotClaimIfPaused() public { // Arrange _performDeposit(); @@ -268,56 +290,4 @@ contract debtVaultTest is Test, HelperContract { // Dividends are shared between the two token holders assertEq(resUint256, defaultDepositAmount / 2); } - - function testAdminCanWithdrawAll() public { - // Arrange - // Deposit - uint256 snapshotTime1 = block.timestamp + 50; - uint256 snapshotTime2 = block.timestamp + 50; - uint256 depositAmount1 = 2000; - uint256 depositAmount2 = 3000; - uint256 ALLOWANCE_NEEDED = 2000 + 3000; - // Allowance - vm.prank(DEFAULT_ADMIN_ADDRESS); - tokenPayment.approve(address(debtVault), ALLOWANCE_NEEDED); - // Deposit 1 - vm.prank(DEFAULT_ADMIN_ADDRESS); - debtVault.deposit(snapshotTime1, depositAmount1); - // Deposit 2 - vm.prank(DEFAULT_ADMIN_ADDRESS); - debtVault.deposit(snapshotTime2, depositAmount2); - - // Withdraw - vm.prank(DEFAULT_ADMIN_ADDRESS); - debtVault.withdrawAll(ALLOWANCE_NEEDED, ADDRESS2); - - // Assert - assertEq(tokenPayment.balanceOf(ADDRESS2), ALLOWANCE_NEEDED); - } - - function testAdminCanWithdrawSpecificTime() public { - // Arrange - // Deposit - uint256 snapshotTime1 = block.timestamp + 50; - uint256 snapshotTime2 = block.timestamp + 50; - uint256 depositAmount1 = 2000; - uint256 depositAmount2 = 3000; - uint256 ALLOWANCE_NEEDED = 2000 + 3000; - // Allowance - vm.prank(DEFAULT_ADMIN_ADDRESS); - tokenPayment.approve(address(debtVault), ALLOWANCE_NEEDED); - // Deposit 1 - vm.prank(DEFAULT_ADMIN_ADDRESS); - debtVault.deposit(snapshotTime1, depositAmount1); - // Deposit 2 - vm.prank(DEFAULT_ADMIN_ADDRESS); - debtVault.deposit(snapshotTime2, depositAmount2); - - // Withdraw - vm.prank(DEFAULT_ADMIN_ADDRESS); - debtVault.withdraw(snapshotTime1, depositAmount1, ADDRESS2); - - // Assert - assertEq(tokenPayment.balanceOf(ADDRESS2),depositAmount1); - } } diff --git a/test/IncomeVaultRestricted.t.sol b/test/IncomeVaultRestricted.t.sol new file mode 100644 index 0000000..e909977 --- /dev/null +++ b/test/IncomeVaultRestricted.t.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "./HelperContract.sol"; +import "CMTAT/interfaces/engine/IRuleEngine.sol"; +import "CMTAT/interfaces/engine/IAuthorizationEngine.sol"; +import {IncomeVault} from "../src/IncomeVault.sol"; +//import {Upgrades,} from "openzeppelin-foundry-upgrades/Upgrades.sol"; + +/** +* @title Test for DebtVault +*/ +contract IncomeVaultRestrictedTest is Test, HelperContract { + uint256 resUint256; + uint8 resUint8; + bool resBool; + bool resCallBool; + string resString; + uint8 CODE_NONEXISTENT = 255; + + // ADMIN balance payment + uint256 tokenBalance = 5000; + + // Arrange + function setUp() public { + // Deploy CMTAT + CMTAT_CONTRACT = new CMTAT_STANDALONE( + ZERO_ADDRESS, + CMTAT_ADMIN, + IAuthorizationEngine(address(0)), + "CMTA Token", + "CMTAT", + DECIMALS, + "CMTAT_ISIN", + "https://cmta.ch", + IRuleEngine(address(0)), + "CMTAT_info", + FLAG + ); + + // Token payment + tokenPayment = new CMTAT_STANDALONE( + ZERO_ADDRESS, + TOKEN_PAYMENT_ADMIN, + IAuthorizationEngine(address(0)), + "CMTA Token", + "CMTAT", + DECIMALS, + "CMTAT_ISIN", + "https://cmta.ch", + IRuleEngine(address(0)), + "CMTAT_info", + FLAG + ); + Options memory opts; + opts.constructorData = abi.encode(ZERO_ADDRESS); + address proxy = Upgrades.deployTransparentProxy( + "IncomeVault.sol", + DEFAULT_ADMIN_ADDRESS, + abi.encodeCall(IncomeVault.initialize, ( DEFAULT_ADMIN_ADDRESS, + tokenPayment, + ICMTATSnapshot(address(CMTAT_CONTRACT)), + IRuleEngine(ZERO_ADDRESS), + IAuthorizationEngine(ZERO_ADDRESS))), + opts + ); + debtVault = IncomeVault(proxy); + // Deploy DebtVault + /*debtVault = new DebtVault( + ZERO_ADDRESS + );*/ + /*debtVault.initialize( + DEFAULT_ADMIN_ADDRESS, + tokenPayment, + ICMTATSnapshot(address(CMTAT_CONTRACT)), + IRuleEngine(ZERO_ADDRESS), + IAuthorizationEngine(ZERO_ADDRESS) + );*/ + /** + vm.prank(CMTAT_ADMIN); + CMTAT_CONTRACT.mint(DEFAULT_ADMIN_ADDRESS, ADDRESS1_INITIAL_AMOUNT); + */ + vm.prank(TOKEN_PAYMENT_ADMIN); + tokenPayment.mint(DEFAULT_ADMIN_ADDRESS, tokenBalance); + + } + + function testDepositRoleCanPerformDeposit() public { + uint256 time = 200; + // Allowance + vm.prank(DEFAULT_ADMIN_ADDRESS); + tokenPayment.approve(address(debtVault), defaultDepositAmount); + // Act + vm.prank(DEFAULT_ADMIN_ADDRESS); + //Event + vm.expectEmit(true, true, false, true); + emit newDeposit( + time, + DEFAULT_ADMIN_ADDRESS, + defaultDepositAmount + ); + debtVault.deposit(time, defaultDepositAmount); + // Assert + resUint256 = debtVault.segragatedDividend(time); + assertEq(resUint256, defaultDepositAmount); + } + + + function _performOnlyDeposit() internal { + // Allowance + vm.prank(DEFAULT_ADMIN_ADDRESS); + tokenPayment.approve(address(debtVault), defaultDepositAmount); + // Act + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.deposit(defaultSnapshotTime, defaultDepositAmount); + } + + function _performDeposit() internal { + _performOnlyDeposit(); + // Configure snapshot + + vm.prank(CMTAT_ADMIN); + CMTAT_CONTRACT.scheduleSnapshot(defaultSnapshotTime); + + // Mint token for Address 1 + vm.prank(CMTAT_ADMIN); + CMTAT_CONTRACT.mint(ADDRESS1, ADDRESS1_INITIAL_AMOUNT); + } + + function testAdminCanWithdrawAll() public { + // Arrange + // Deposit + uint256 snapshotTime1 = block.timestamp + 50; + uint256 snapshotTime2 = block.timestamp + 50; + uint256 depositAmount1 = 2000; + uint256 depositAmount2 = 3000; + uint256 ALLOWANCE_NEEDED = 2000 + 3000; + // Allowance + vm.prank(DEFAULT_ADMIN_ADDRESS); + tokenPayment.approve(address(debtVault), ALLOWANCE_NEEDED); + // Deposit 1 + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.deposit(snapshotTime1, depositAmount1); + // Deposit 2 + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.deposit(snapshotTime2, depositAmount2); + + // Withdraw + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.withdrawAll(ALLOWANCE_NEEDED, ADDRESS2); + + // Assert + assertEq(tokenPayment.balanceOf(ADDRESS2), ALLOWANCE_NEEDED); + } + + function testAdminCanWithdrawSpecificTime() public { + // Arrange + // Deposit + uint256 snapshotTime1 = block.timestamp + 50; + uint256 snapshotTime2 = block.timestamp + 50; + uint256 depositAmount1 = 2000; + uint256 depositAmount2 = 3000; + uint256 ALLOWANCE_NEEDED = 2000 + 3000; + // Allowance + vm.prank(DEFAULT_ADMIN_ADDRESS); + tokenPayment.approve(address(debtVault), ALLOWANCE_NEEDED); + // Deposit 1 + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.deposit(snapshotTime1, depositAmount1); + // Deposit 2 + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.deposit(snapshotTime2, depositAmount2); + + // Withdraw + vm.prank(DEFAULT_ADMIN_ADDRESS); + debtVault.withdraw(snapshotTime1, depositAmount1, ADDRESS2); + + // Assert + assertEq(tokenPayment.balanceOf(ADDRESS2),depositAmount1); + } +} diff --git a/test/RuleEngineIntegration.t.sol b/test/RuleEngineIntegration.t.sol index 8968024..0017b61 100644 --- a/test/RuleEngineIntegration.t.sol +++ b/test/RuleEngineIntegration.t.sol @@ -70,16 +70,16 @@ contract RuleEngineIntegration is RuleWhitelistInvariantStorage, Test, HelperCon Options memory opts; opts.constructorData = abi.encode(ZERO_ADDRESS); address proxy = Upgrades.deployTransparentProxy( - "DebtVault.sol", + "IncomeVault.sol", DEFAULT_ADMIN_ADDRESS, - abi.encodeCall(DebtVault.initialize, ( DEFAULT_ADMIN_ADDRESS, + abi.encodeCall(IncomeVault.initialize, ( DEFAULT_ADMIN_ADDRESS, tokenPayment, ICMTATSnapshot(address(CMTAT_CONTRACT)), IRuleEngine(ZERO_ADDRESS), IAuthorizationEngine(ZERO_ADDRESS))), opts ); - debtVault = DebtVault(proxy); + debtVault = IncomeVault(proxy); // We set the Rule Engine vm.prank(DEFAULT_ADMIN_ADDRESS);