diff --git a/contracts/PeriodClaimVault.sol b/contracts/PeriodClaimVault.sol index 7984873..c81bf22 100644 --- a/contracts/PeriodClaimVault.sol +++ b/contracts/PeriodClaimVault.sol @@ -19,6 +19,7 @@ contract PeriodClaimVault is OwnableUpgradeable { event ChangeRewardPerDevice(uint256 projectId, uint256 rewardPerDevice); event ChangeRecipient(address indexed admin, uint256 projectId, address recipient); event SetInvalidDevice(uint256 projectId, uint256 amount); + event SetProjectCap(uint256 projectId, uint256 cap); IioIDStore public ioIDStore; uint256 public period; @@ -27,6 +28,7 @@ contract PeriodClaimVault is OwnableUpgradeable { mapping(uint256 => address) public projectRecipient; mapping(uint256 => uint256) public projectInvalidDevice; mapping(uint256 => uint256) public lastClaimedTimestamp; + mapping(uint256 => uint256) public projectCap; function initialize(address _ioIDStore) public initializer { require(_ioIDStore != address(0), "zero address"); @@ -73,9 +75,15 @@ contract PeriodClaimVault is OwnableUpgradeable { require(_lastClaimedTimestamp != 0, "invalid project"); require(_lastClaimedTimestamp + period <= block.timestamp, "claim too short"); uint256 _claimablePeriods = (block.timestamp - _lastClaimedTimestamp) / period; - uint256 _rewards = _claimablePeriods * - rewardPerDevice[_projectId] * + + uint256 _periodRewards = rewardPerDevice[_projectId] * (ioIDStore.projectActivedAmount(_projectId) - projectInvalidDevice[_projectId]); + uint256 _cap = projectCap[_projectId]; + if (_cap != 0 && _periodRewards > _cap) { + _periodRewards = _cap; + } + + uint256 _rewards = _claimablePeriods * _periodRewards; require(address(this).balance >= _rewards, "insufficient fund"); lastClaimedTimestamp[_projectId] += (_claimablePeriods * period); address _recipient = projectRecipient[_projectId]; @@ -101,6 +109,32 @@ contract PeriodClaimVault is OwnableUpgradeable { emit ChangeRewardPerDevice(_projectId, _rewardPerDevice); } + function setProjectCap(uint256 _projectId, uint256 _cap) external onlyOwner { + require(_cap > 0, "invalid cap"); + require(projectRecipient[_projectId] != address(0), "invalid project"); + + projectCap[_projectId] = _cap; + emit SetProjectCap(_projectId, _cap); + } + + function claimableRewards(uint256 _projectId) external view returns (uint256) { + uint256 _lastClaimedTimestamp = lastClaimedTimestamp[_projectId]; + require(_lastClaimedTimestamp != 0, "invalid project"); + uint256 _claimablePeriods = (block.timestamp - _lastClaimedTimestamp) / period; + if (_claimablePeriods == 0) { + return 0; + } + + uint256 _periodRewards = rewardPerDevice[_projectId] * + (ioIDStore.projectActivedAmount(_projectId) - projectInvalidDevice[_projectId]); + uint256 _cap = projectCap[_projectId]; + if (_cap != 0 && _periodRewards > _cap) { + _periodRewards = _cap; + } + + return _claimablePeriods * _periodRewards; + } + function setInvalidDevice(uint256 _projectId, uint256 _amount) external onlyOwner { require(ioIDStore.projectActivedAmount(_projectId) >= _amount, "invalid project"); diff --git a/script/11_deploy_claim_vault_implementaion.ts b/script/11_deploy_claim_vault_implementaion.ts new file mode 100644 index 0000000..298ed54 --- /dev/null +++ b/script/11_deploy_claim_vault_implementaion.ts @@ -0,0 +1,19 @@ +import { ethers } from 'hardhat'; +require('dotenv').config(); + +async function main() { + if (!process.env.IOID_STORE) { + console.log(`Please provide IOID_STORE address`); + return; + } + + const vault = await ethers.deployContract('PeriodClaimVault'); + await vault.waitForDeployment(); + + console.log(`PeriodClaimVault implementation deployed to ${vault.target}`); +} + +main().catch(err => { + console.error(err); + process.exitCode = 1; +}); diff --git a/test/TestPeriodClaimVault.ts b/test/TestPeriodClaimVault.ts index f3d4fcd..e26e867 100644 --- a/test/TestPeriodClaimVault.ts +++ b/test/TestPeriodClaimVault.ts @@ -67,6 +67,7 @@ describe('PeriodClaimVault tests', function () { ); await time.increaseTo(startTimestamp + 86400 * 5 + 50000); + expect(await vault.claimableRewards(projectId)).to.be.equals(ethers.parseEther('0.9')); await vault.connect(fakeRecipient).claim(projectId); expect((await ethers.provider.getBalance(projectRecipient)) - balanceRecipient).to.be.equals( ((await ioIDStore.projectActivedAmount(projectId)) - (await vault.projectInvalidDevice(projectId))) * @@ -74,6 +75,13 @@ describe('PeriodClaimVault tests', function () { BigInt(4), ); + await vault.setProjectCap(projectId, ethers.parseEther('0.2')); + await expect(vault.setProjectCap(projectId, ethers.parseEther('0'))).to.revertedWith('invalid cap'); + await expect(vault.setProjectCap(2, ethers.parseEther('10'))).to.revertedWith('invalid project'); + + await time.increaseTo(startTimestamp + 86400 * 8 + 50000); + expect(await vault.claimableRewards(projectId)).to.be.equals(ethers.parseEther('0.6')); + await vault.removeProject(projectId); await expect(vault.claim(projectId)).to.revertedWith('invalid project'); });