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 @@
+
+
+
+
+
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);