From 54e838c9c2f16284b732417808a8ae5fbd838756 Mon Sep 17 00:00:00 2001 From: Xin Date: Wed, 7 Aug 2024 00:20:05 +0800 Subject: [PATCH 1/4] feat: add threshold to control distribute reward to gauge; --- contracts/Voter.sol | 7 +++++-- contracts/factories/GaugeFactory.sol | 5 ++++- contracts/gauges/DeviceGauge.sol | 3 +++ contracts/gauges/ERC20Gauge.sol | 3 +++ contracts/gauges/RewardGauge.sol | 11 ++++++++++- contracts/interfaces/IRewardGauge.sol | 2 ++ contracts/interfaces/factories/IGaugeFactory.sol | 3 ++- 7 files changed, 29 insertions(+), 5 deletions(-) diff --git a/contracts/Voter.sol b/contracts/Voter.sol index b86fa81..16a300b 100644 --- a/contracts/Voter.sol +++ b/contracts/Voter.sol @@ -76,6 +76,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { mapping(address => uint256) internal supplyIndex; /// @inheritdoc IVoter mapping(address => uint256) public claimable; + mapping(address => uint256) public triggerThreshold; constructor(address _forwarder, address _strategyManager, address _factoryRegistry) ERC2771Context(_forwarder) { forwarder = _forwarder; @@ -221,6 +222,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { if (_gauge == address(0)) revert GaugeDoesNotExist(_pool); if (!isAlive[_gauge]) revert GaugeNotAlive(_gauge); if (votes[_voter][_pool] != 0) revert NonZeroVotes(); + require(IRewardGauge(_gauge).depositUserNum() >= triggerThreshold[_gauge], "gauge don't meet condition for receive vote"); uint256 _poolWeight = (_weights[i] * _weight) / _totalVoteWeight; if (_poolWeight == 0) revert ZeroBalance(); @@ -265,7 +267,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { } /// @inheritdoc IVoter - function createGauge(address _poolFactory, address _pool, uint8 _gaugeType) external nonReentrant returns (address) { + function createGauge(address _poolFactory, address _pool, uint8 _gaugeType, uint256 threshold) external nonReentrant returns (address) { if (gauges[_pool] != address(0)) revert GaugeExists(); (address incentiveFactory, address gaugeFactory) = IFactoryRegistry(factoryRegistry).factoriesToPoolFactory( _poolFactory @@ -277,7 +279,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { } address _incentiveReward = IIncentivesFactory(incentiveFactory).createRewards(forwarder, _pool); - address _gauge = IGaugeFactory(gaugeFactory).createGauge(forwarder, _pool, _incentiveReward, _gaugeType); + address _gauge = IGaugeFactory(gaugeFactory).createGauge(forwarder, _pool, _incentiveReward, _gaugeType, threshold); IIncentive(_incentiveReward).setGauge(_gauge); gauges[_pool] = _gauge; @@ -286,6 +288,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { isAlive[_gauge] = true; _updateFor(_gauge); pools.push(_pool); + triggerThreshold[_gauge] = triggerThreshold; emit GaugeCreated(_poolFactory, gaugeFactory, _pool, _gauge, sender); return _gauge; diff --git a/contracts/factories/GaugeFactory.sol b/contracts/factories/GaugeFactory.sol index 990db46..e52de1e 100644 --- a/contracts/factories/GaugeFactory.sol +++ b/contracts/factories/GaugeFactory.sol @@ -14,14 +14,17 @@ contract GaugeFactory is IGaugeFactory { address _forwarder, address _poolOrDeviceNFTOrGauge, address _incentives, - uint8 _gaugeType + uint8 _gaugeType, + uint256 threshold ) external returns (address gauge) { address _gauge; if (_gaugeType == Erc20Gauge) { + require(threshold == 0, "threshold in erc20 gauge should be 0"); _gauge = createERC20Gauge(_forwarder, _poolOrDeviceNFTOrGauge, _incentives); } else if (_gaugeType == DeviceNFTGauge) { _gauge = createDeviceGauge(_forwarder, _poolOrDeviceNFTOrGauge, _incentives); } else if (_gaugeType == WithdrawGauge) { + require(threshold == 0, "threshold in withdraw gauge should be 0"); _gauge = createWithdrawalGauge(_poolOrDeviceNFTOrGauge); } else { revert IncorrectnessGaugeType(); diff --git a/contracts/gauges/DeviceGauge.sol b/contracts/gauges/DeviceGauge.sol index f28e37e..cf8141b 100644 --- a/contracts/gauges/DeviceGauge.sol +++ b/contracts/gauges/DeviceGauge.sol @@ -55,6 +55,9 @@ contract DeviceGauge is RewardGauge, ERC721Holder { delete tokenWeight[_tokenId]; updateWeightBalance(sender); IIncentive(incentive).withdraw(_amount, sender); + if (balanceOf[sender] == 0){ + depositUserNum--; + } emit WithdrawDevice(sender, _amount, _tokenId); } diff --git a/contracts/gauges/ERC20Gauge.sol b/contracts/gauges/ERC20Gauge.sol index af87e17..8c99709 100644 --- a/contracts/gauges/ERC20Gauge.sol +++ b/contracts/gauges/ERC20Gauge.sol @@ -47,6 +47,9 @@ contract ERC20Gauge is RewardGauge { IERC20(stakingToken).safeTransfer(sender, _amount); updateWeightBalance(sender); IIncentive(incentive).withdraw(_amount, sender); + if (balanceOf[sender] == 0){ + depositUserNum--; + } emit Withdraw(sender, _amount); } diff --git a/contracts/gauges/RewardGauge.sol b/contracts/gauges/RewardGauge.sol index d7c986d..6bf3704 100644 --- a/contracts/gauges/RewardGauge.sol +++ b/contracts/gauges/RewardGauge.sol @@ -52,6 +52,8 @@ abstract contract RewardGauge is IRewardGauge, ERC2771Context, ReentrancyGuard { uint256 public totalWeightedBalance; address public incentive; + uint256 public depositUserNum; + constructor( address _forwarder, address _stakingToken, @@ -129,11 +131,18 @@ abstract contract RewardGauge is IRewardGauge, ERC2771Context, ReentrancyGuard { /// @inheritdoc IRewardGauge function deposit(uint256 _amountOrNFTID) external { - _depositFor(_amountOrNFTID, _msgSender()); + address sender = _msgSender(); + if (balanceOf[sender] == 0){ + depositUserNum++; + } + _depositFor(_amountOrNFTID, sender); } /// @inheritdoc IRewardGauge function deposit(uint256 _amountOrNFTID, address _recipient) external { + if (balanceOf[_recipient] == 0){ + depositUserNum++; + } _depositFor(_amountOrNFTID, _recipient); } diff --git a/contracts/interfaces/IRewardGauge.sol b/contracts/interfaces/IRewardGauge.sol index 4a75cb7..a2be6ab 100644 --- a/contracts/interfaces/IRewardGauge.sol +++ b/contracts/interfaces/IRewardGauge.sol @@ -94,4 +94,6 @@ interface IRewardGauge is IGauge { /// @param _user which vote to gauge with share /// @param _share amount of share to deposit in gauge function updateShare(address _user, uint256 _share) external; + + function depositUserNum() external returns (uint256); } diff --git a/contracts/interfaces/factories/IGaugeFactory.sol b/contracts/interfaces/factories/IGaugeFactory.sol index e229cb4..4567f30 100644 --- a/contracts/interfaces/factories/IGaugeFactory.sol +++ b/contracts/interfaces/factories/IGaugeFactory.sol @@ -8,6 +8,7 @@ interface IGaugeFactory { address _forwarder, address _poolOrDeviceNFTOrGauge, address _incentives, - uint8 _gaugeType + uint8 _gaugeType, + uint256 _threshold ) external returns (address gauge); } From 1f2ebef1539b0196e6ec3df50fe4f724a8032bd6 Mon Sep 17 00:00:00 2001 From: Xin <_star@gmail.com> Date: Thu, 8 Aug 2024 08:30:58 +0800 Subject: [PATCH 2/4] feat: add threshold to control distribute reward to gauge; --- contracts/Voter.sol | 19 ++++++++++++++++--- contracts/interfaces/IVoter.sol | 12 +++++++++++- contracts/test/TestDeviceNFT.sol | 1 + test/TestGauge.t.sol | 12 +++++++++++- test/TestVault.t.sol | 13 ++++++++++++- test/TestVoter.t.sol | 23 ++++++++++++----------- test/flow.test.ts | 2 +- 7 files changed, 64 insertions(+), 18 deletions(-) diff --git a/contracts/Voter.sol b/contracts/Voter.sol index 16a300b..ce95cb4 100644 --- a/contracts/Voter.sol +++ b/contracts/Voter.sol @@ -76,6 +76,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { mapping(address => uint256) internal supplyIndex; /// @inheritdoc IVoter mapping(address => uint256) public claimable; + /// @inheritdoc IVoter mapping(address => uint256) public triggerThreshold; constructor(address _forwarder, address _strategyManager, address _factoryRegistry) ERC2771Context(_forwarder) { @@ -152,6 +153,15 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { maxVotingNum = _maxVotingNum; } + /// @inheritdoc IVoter + function setThreshold(address _pool, uint256 _threshold) external { + if (_msgSender() != governor) revert NotGovernor(); + address _gauge = gauges[_pool]; + if (_gauge == address(0)) revert GaugeDoesNotExist(_pool); + + triggerThreshold[_gauge] = _threshold; + } + /// @inheritdoc IVoter function reset() external onlyNewEpoch(msg.sender) nonReentrant { address _user = msg.sender; @@ -222,7 +232,6 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { if (_gauge == address(0)) revert GaugeDoesNotExist(_pool); if (!isAlive[_gauge]) revert GaugeNotAlive(_gauge); if (votes[_voter][_pool] != 0) revert NonZeroVotes(); - require(IRewardGauge(_gauge).depositUserNum() >= triggerThreshold[_gauge], "gauge don't meet condition for receive vote"); uint256 _poolWeight = (_weights[i] * _weight) / _totalVoteWeight; if (_poolWeight == 0) revert ZeroBalance(); @@ -288,7 +297,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { isAlive[_gauge] = true; _updateFor(_gauge); pools.push(_pool); - triggerThreshold[_gauge] = triggerThreshold; + triggerThreshold[_gauge] = threshold; emit GaugeCreated(_poolFactory, gaugeFactory, _pool, _gauge, sender); return _gauge; @@ -326,6 +335,10 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { address sender = _msgSender(); if (sender != vault) revert NotVault(); uint256 _amount = msg.value; + if (totalWeight == 0) { + payable(vault).transfer(_amount); + return; + } uint256 _ratio = (_amount * 1e18) / Math.max(totalWeight, 1); // 1e18 adjustment is removed during claim if (_ratio > 0) { index += _ratio; @@ -363,7 +376,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard { uint256 _delta = _index - _supplyIndex; // see if there is any difference that need to be accrued if (_delta > 0) { uint256 _share = (_supplied * _delta) / 1e18; // add accrued difference for each supplied token - if (isAlive[_gauge]) { + if (isAlive[_gauge] && IRewardGauge(_gauge).depositUserNum() >= triggerThreshold[_gauge]) { claimable[_gauge] += _share; } else { payable(vault).transfer(_share); // send rewards back to Vault so they're not stuck in Voter diff --git a/contracts/interfaces/IVoter.sol b/contracts/interfaces/IVoter.sol index 7740378..eb82a64 100644 --- a/contracts/interfaces/IVoter.sol +++ b/contracts/interfaces/IVoter.sol @@ -150,6 +150,11 @@ interface IVoter { /// @param _governor . function setGovernor(address _governor) external; + /// @notice Set threshold for gauge with the pool + /// @param _pool . + /// @param _threshold . + function setThreshold(address _pool, uint256 _threshold) external; + /// @notice Set new emergency council. /// @dev Throws if not called by emergency council. /// @param _emergencyCouncil . @@ -162,6 +167,10 @@ interface IVoter { /// @param _maxVotingNum . function setMaxVotingNum(uint256 _maxVotingNum) external; + /// @notice get the threshold for the gauge + /// @param _gauge . + function triggerThreshold(address _gauge) external returns (uint256); + /// @notice Whitelist (or unwhitelist) token for use in bribes. /// @dev Throws if not called by governor. /// @param _token . @@ -174,7 +183,8 @@ interface IVoter { /// @param _poolFactory . /// @param _pool . /// @param _gaugeType 0: ERC20Gauge, 1: DeviceNFTGauge, 2: WithdrawGauge - function createGauge(address _poolFactory, address _pool, uint8 _gaugeType) external returns (address); + /// @param threshold only >0 for deviceNFTGauge + function createGauge(address _poolFactory, address _pool, uint8 _gaugeType, uint256 threshold) external returns (address); /// @notice Kills a gauge. The gauge will not receive any new emissions and cannot be deposited into. /// Can still withdraw from gauge. diff --git a/contracts/test/TestDeviceNFT.sol b/contracts/test/TestDeviceNFT.sol index 380d14e..0f03f8d 100644 --- a/contracts/test/TestDeviceNFT.sol +++ b/contracts/test/TestDeviceNFT.sol @@ -25,5 +25,6 @@ contract TestDeviceNFT is IDeviceNFT, ERC721 { function mint(address to, uint tokenId) external { _mint(to, tokenId); + weightOf[tokenId] = 1 ether; } } diff --git a/test/TestGauge.t.sol b/test/TestGauge.t.sol index be96dd9..f0c6525 100644 --- a/test/TestGauge.t.sol +++ b/test/TestGauge.t.sol @@ -9,12 +9,14 @@ import {DAOForwarder} from "../contracts/DAOForwarder.sol"; import {TestToken} from "../contracts/test/TestToken.sol"; import {ProtocolTimeLibrary} from "../contracts/libraries/ProtocolTimeLibrary.sol"; import {Incentives} from "../contracts/rewards/Incentive.sol"; +import "../contracts/test/TestStrategyManager.sol"; contract TestERC20Gauge is Test { ERC20Gauge public gauge; DAOForwarder public forwarder; TestToken public pool; Voter public voter; + TestStrategyManager public strategyManager; fallback() external payable {} @@ -22,7 +24,9 @@ contract TestERC20Gauge is Test { pool = new TestToken("lp_pool", "pool"); forwarder = new DAOForwarder(); // manager & _factoryRegistry not used in gauge - voter = new Voter(address(forwarder), address(this), address(this)); + strategyManager = new TestStrategyManager(); + strategyManager.setShare(address (this), 100); + voter = new Voter(address(forwarder), address(strategyManager), address(this)); Incentives inti = new Incentives(address(forwarder), address(voter), new address[](0)); gauge = new ERC20Gauge(address(forwarder), address(pool), address(voter), address(inti)); vm.prank(address(voter)); @@ -70,6 +74,12 @@ contract TestERC20Gauge is Test { // 2. success rewards-1 // 2.1 deposit rewards to voter + skip(8 days); + address[] memory poolvote = new address[](1); + uint256[] memory weights = new uint256[](1); + poolvote[0] = address(pool); + weights[0] = 5000; + voter.vote(poolvote, weights); voter.notifyRewardAmount{value: 90 ether}(); // 2.2 simulate notify rewards by voter vm.prank(address(voter)); diff --git a/test/TestVault.t.sol b/test/TestVault.t.sol index 8874979..89eb685 100644 --- a/test/TestVault.t.sol +++ b/test/TestVault.t.sol @@ -17,6 +17,7 @@ contract TestVault is Test { Vault public vault; Voter public voter; DAOForwarder public forwarder; + address public poolFactory; GaugeFactory public gaugeFactory; IncentivesFactory public incentiveFactory; FactoryRegistry public factoryRegistry; @@ -27,7 +28,9 @@ contract TestVault is Test { gaugeFactory = new GaugeFactory(); incentiveFactory = new IncentivesFactory(); strategyManager = new TestStrategyManager(); - factoryRegistry = new FactoryRegistry(address(1), address(incentiveFactory), address(gaugeFactory)); + strategyManager.setShare(address (this), 100); + poolFactory = address(1); + factoryRegistry = new FactoryRegistry(poolFactory, address(incentiveFactory), address(gaugeFactory)); voter = new Voter(address(forwarder), address(strategyManager), address(factoryRegistry)); vault = new Vault(); vault.initialize(address(voter), address(strategyManager)); @@ -65,8 +68,16 @@ contract TestVault is Test { uint256 _period = vault.emitReward(); // 5. updatePeriod success + skip(2 hours); payable(address(vault)).transfer(vault.weekly() - 1 ether); voter.initialize(new address[](0), address(vault)); + address pool = address (111); + voter.createGauge(poolFactory, address(pool), 0, 0); + address[] memory poolvote = new address[](1); + uint256[] memory weights = new uint256[](1); + poolvote[0] = address(pool); + weights[0] = 5000; + voter.vote(poolvote, weights); _period = vault.emitReward(); assertEq(7 days, _period); assertEq(address(vault).balance, 0); diff --git a/test/TestVoter.t.sol b/test/TestVoter.t.sol index 9bfd033..65290be 100644 --- a/test/TestVoter.t.sol +++ b/test/TestVoter.t.sol @@ -15,6 +15,7 @@ import {FactoryRegistry} from "../contracts/factories/FactoryRegistry.sol"; import {GaugeFactory} from "../contracts/factories/GaugeFactory.sol"; import {IGaugeFactory} from "../contracts/interfaces/factories/IGaugeFactory.sol"; import {IncentivesFactory} from "../contracts/factories/IncentivesFactory.sol"; +import "../contracts/libraries/ProtocolTimeLibrary.sol"; contract TestVoter is Test { Voter public voter; @@ -43,25 +44,25 @@ contract TestVoter is Test { function test_gauge_actions() external { //1. createGauge success - voter.createGauge(poolFactory, address(pool), 0); + voter.createGauge(poolFactory, address(pool), 0, 0); assertEq(1, voter.length()); assertNotEq(address(0), voter.gauges(address(pool))); //1.1 repeat add same pool so failed vm.expectRevert(IVoter.GaugeExists.selector); - voter.createGauge(poolFactory, address(pool), 0); + voter.createGauge(poolFactory, address(pool), 0, 0); assertEq(1, voter.length()); //1.2 caller not governor & pool is not whitelistedToken address pool2 = address(22); vm.prank(address(2)); vm.expectRevert(IVoter.NotWhitelistedToken.selector); - voter.createGauge(poolFactory, pool2, 0); + voter.createGauge(poolFactory, pool2, 0, 0); assertEq(1, voter.length()); //1.3 set whitelistedToken voter.whitelistToken(pool2, true); - voter.createGauge(poolFactory, pool2, 0); + voter.createGauge(poolFactory, pool2, 0, 0); assertEq(2, voter.length()); //2. kill gauge @@ -77,7 +78,7 @@ contract TestVoter is Test { function test_vote_actions() external { skip(10 days); - voter.createGauge(poolFactory, address(pool), 0); + voter.createGauge(poolFactory, address(pool), 0, 0); strategyManager.setShare(address(this), 500); //1. vote failed due to UnequalLengths @@ -122,7 +123,7 @@ contract TestVoter is Test { function test_notifyReward_updateFor_distribute_claimRewards() external { // 0. setup to create gauge and vote for the gauge - voter.createGauge(poolFactory, address(pool), 0); + voter.createGauge(poolFactory, address(pool), 0, 0); address gauge = voter.gauges(address(pool)); strategyManager.setShare(address(this), 1000); address[] memory poolvote = new address[](1); @@ -153,30 +154,30 @@ contract TestVoter is Test { function test_create_gauge() external { // 1. first create ERC20Gauge - voter.createGauge(poolFactory, address(pool), 0); + voter.createGauge(poolFactory, address(pool), 0, 0); address gauge = voter.gauges(address(pool)); assertTrue(gauge != address(0)); // 2. again create ERC20Gauge should failed for same pool vm.expectRevert(IVoter.GaugeExists.selector); - voter.createGauge(poolFactory, address(pool), 0); + voter.createGauge(poolFactory, address(pool), 0, 0); // 3. create NFT gauge address deviceNFT = address(10); - voter.createGauge(poolFactory, deviceNFT, 1); + voter.createGauge(poolFactory, deviceNFT, 1, 10); gauge = voter.gauges(address(deviceNFT)); assertTrue(gauge != address(0)); // 4. create withdraw gauge address onlyWithdrawGauge = address(11); - voter.createGauge(poolFactory, onlyWithdrawGauge, 2); + voter.createGauge(poolFactory, onlyWithdrawGauge, 2, 0); gauge = voter.gauges(address(onlyWithdrawGauge)); assertTrue(gauge != address(0)); // 5. incorrect gaugeType vm.expectRevert(IGaugeFactory.IncorrectnessGaugeType.selector); address nextPool = address(12); - voter.createGauge(poolFactory, nextPool, 3); + voter.createGauge(poolFactory, nextPool, 3, 0); } receive() external payable {} diff --git a/test/flow.test.ts b/test/flow.test.ts index 1cfa389..586cb80 100644 --- a/test/flow.test.ts +++ b/test/flow.test.ts @@ -28,7 +28,7 @@ describe('Flow', function () { await voter.initialize([], vault.target); const token = await ethers.deployContract('TestToken', ['Test Token', 'TEST']); - await voter.createGauge(poolFactory.target, token.target, 0); + await voter.createGauge(poolFactory.target, token.target, 0, 0); await voter.vote([token.target], [5000]); }); From 9f25dae973e47840d54f89d8d0066926eec0106e Mon Sep 17 00:00:00 2001 From: Xin <_star@gmail.com> Date: Thu, 8 Aug 2024 11:28:30 +0800 Subject: [PATCH 3/4] tests: fix all uint tests; --- test/TestGauge.t.sol | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/TestGauge.t.sol b/test/TestGauge.t.sol index f0c6525..622a6b6 100644 --- a/test/TestGauge.t.sol +++ b/test/TestGauge.t.sol @@ -10,6 +10,9 @@ import {TestToken} from "../contracts/test/TestToken.sol"; import {ProtocolTimeLibrary} from "../contracts/libraries/ProtocolTimeLibrary.sol"; import {Incentives} from "../contracts/rewards/Incentive.sol"; import "../contracts/test/TestStrategyManager.sol"; +import "../contracts/factories/GaugeFactory.sol"; +import "../contracts/factories/FactoryRegistry.sol"; +import "../contracts/factories/IncentivesFactory.sol"; contract TestERC20Gauge is Test { ERC20Gauge public gauge; @@ -26,11 +29,14 @@ contract TestERC20Gauge is Test { // manager & _factoryRegistry not used in gauge strategyManager = new TestStrategyManager(); strategyManager.setShare(address (this), 100); - voter = new Voter(address(forwarder), address(strategyManager), address(this)); - Incentives inti = new Incentives(address(forwarder), address(voter), new address[](0)); - gauge = new ERC20Gauge(address(forwarder), address(pool), address(voter), address(inti)); - vm.prank(address(voter)); - inti.setGauge(address(gauge)); + GaugeFactory gaugeFactory = new GaugeFactory(); + IncentivesFactory incentiveFactory = new IncentivesFactory(); + address poolFactory = address(1); + FactoryRegistry factoryRegistry = new FactoryRegistry(poolFactory, address(incentiveFactory), address(gaugeFactory)); + voter = new Voter(address(forwarder), address(strategyManager), address(factoryRegistry)); + address _gauge = voter.createGauge(poolFactory, address(pool), 0, 0); + gauge = ERC20Gauge(_gauge); + voter.killGauge(_gauge); } function test_deposit() external { @@ -84,26 +90,26 @@ contract TestERC20Gauge is Test { // 2.2 simulate notify rewards by voter vm.prank(address(voter)); gauge.notifyRewardAmount{value: 1 ether}(); - assertEq(1, gauge.lastUpdateTime()); + assertEq(8 days + 1, gauge.lastUpdateTime()); assertEq(ProtocolTimeLibrary.epochNext(block.timestamp), gauge.periodFinish()); uint256 firstRate = gauge.rewardRate(); uint256 firstPeriod = gauge.periodFinish(); uint256 firstRewardPerToken = gauge.rewardPerToken(); console.log("firstRewardPerToken: ", firstRewardPerToken); skip(3000); // blockTime will set after 3000s - assertEq(3001, block.timestamp); + assertEq(8 days + 3000 + 1, block.timestamp); // 3. success reward-2 in same epoch vm.prank(address(voter)); gauge.notifyRewardAmount{value: 1 ether}(); - assertEq(3001, gauge.lastUpdateTime()); + assertEq(8 days + 3000 + 1, gauge.lastUpdateTime()); assertGt(gauge.rewardRate(), firstRate); assertEq(firstPeriod, gauge.periodFinish()); assertEq(2 ether, address(gauge).balance); uint256 secondRewardPerToken = gauge.rewardPerToken(); // 4. view earned in first epoch - skip(7 days - block.timestamp); + skip(14 days - block.timestamp); assertEq(block.timestamp, firstPeriod); uint256 firstEarned = gauge.earned(address(this)); console.log("firstPeriod: ", firstPeriod, "; blocktime: ", block.timestamp); From 0192f7d0a490311c82414069a93e5caf5ba0ad9c Mon Sep 17 00:00:00 2001 From: zhi Date: Fri, 9 Aug 2024 21:19:12 +0800 Subject: [PATCH 4/4] update device gauge --- contracts/gauges/DeviceGauge.sol | 17 +++++++++++------ .../{IDeviceNFT.sol => IWeightedNFT.sol} | 5 ++--- contracts/test/TestDeviceNFT.sol | 8 ++++++-- test/TestVoter.t.sol | 3 ++- 4 files changed, 21 insertions(+), 12 deletions(-) rename contracts/interfaces/{IDeviceNFT.sol => IWeightedNFT.sol} (53%) diff --git a/contracts/gauges/DeviceGauge.sol b/contracts/gauges/DeviceGauge.sol index f28e37e..3dc8af8 100644 --- a/contracts/gauges/DeviceGauge.sol +++ b/contracts/gauges/DeviceGauge.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.0; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IVoter} from "../interfaces/IVoter.sol"; -import {IDeviceNFT} from "../interfaces/IDeviceNFT.sol"; +import {IWeightedNFT} from "../interfaces/IWeightedNFT.sol"; import {RewardGauge} from "./RewardGauge.sol"; import {IIncentive} from "../interfaces/IIncentive.sol"; @@ -15,12 +16,16 @@ contract DeviceGauge is RewardGauge, ERC721Holder { mapping(uint256 => address) public tokenStaker; mapping(uint256 => uint256) public tokenWeight; + address public immutable weightedNFT; + constructor( address _forwarder, - address _stakingToken, + address _weightedNFT, address _voter, address _incentives - ) RewardGauge(_forwarder, _stakingToken, _voter, _incentives) {} + ) RewardGauge(_forwarder, IWeightedNFT(_weightedNFT).nft(), _voter, _incentives) { + weightedNFT = _weightedNFT; + } function _depositFor(uint256 _tokenId, address _recipient) internal override nonReentrant { if (_tokenId == 0) revert ZeroAmount(); @@ -29,8 +34,8 @@ contract DeviceGauge is RewardGauge, ERC721Holder { address sender = _msgSender(); _updateRewards(_recipient); - IDeviceNFT(stakingToken).safeTransferFrom(sender, address(this), _tokenId); - uint256 _amount = IDeviceNFT(stakingToken).weight(_tokenId); + IERC721(stakingToken).safeTransferFrom(sender, address(this), _tokenId); + uint256 _amount = IWeightedNFT(weightedNFT).weight(_tokenId); totalSupply += _amount; balanceOf[_recipient] += _amount; tokenStaker[_tokenId] = _recipient; @@ -50,7 +55,7 @@ contract DeviceGauge is RewardGauge, ERC721Holder { uint256 _amount = tokenWeight[_tokenId]; totalSupply -= _amount; balanceOf[sender] -= _amount; - IDeviceNFT(stakingToken).safeTransferFrom(address(this), sender, _tokenId); + IERC721(stakingToken).safeTransferFrom(address(this), sender, _tokenId); delete tokenStaker[_tokenId]; delete tokenWeight[_tokenId]; updateWeightBalance(sender); diff --git a/contracts/interfaces/IDeviceNFT.sol b/contracts/interfaces/IWeightedNFT.sol similarity index 53% rename from contracts/interfaces/IDeviceNFT.sol rename to contracts/interfaces/IWeightedNFT.sol index 9f4ea9f..3cf63c0 100644 --- a/contracts/interfaces/IDeviceNFT.sol +++ b/contracts/interfaces/IWeightedNFT.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -interface IDeviceNFT is IERC721 { +interface IWeightedNFT { function weight(uint256 tokenId) external view returns (uint256); + function nft() external view returns (address); } diff --git a/contracts/test/TestDeviceNFT.sol b/contracts/test/TestDeviceNFT.sol index 380d14e..af171f5 100644 --- a/contracts/test/TestDeviceNFT.sol +++ b/contracts/test/TestDeviceNFT.sol @@ -1,9 +1,9 @@ pragma solidity ^0.8.0; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {IDeviceNFT} from "../interfaces/IDeviceNFT.sol"; +import {IWeightedNFT} from "../interfaces/IWeightedNFT.sol"; -contract TestDeviceNFT is IDeviceNFT, ERC721 { +contract TestDeviceNFT is IWeightedNFT, ERC721 { mapping(uint256 => uint256) public weightOf; constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { @@ -19,6 +19,10 @@ contract TestDeviceNFT is IDeviceNFT, ERC721 { return weightOf[tokenId]; } + function nft() external view override returns (address) { + return address(this); + } + function setWeight(uint256 _tokenId, uint256 _weight) public { weightOf[_tokenId] = _weight; } diff --git a/test/TestVoter.t.sol b/test/TestVoter.t.sol index 9bfd033..8a9e3b7 100644 --- a/test/TestVoter.t.sol +++ b/test/TestVoter.t.sol @@ -10,6 +10,7 @@ import {IVoter} from "../contracts/interfaces/IVoter.sol"; import {IRewardGauge} from "../contracts/interfaces/IRewardGauge.sol"; import {IVault} from "../contracts/interfaces/IVault.sol"; import {DAOForwarder} from "../contracts/DAOForwarder.sol"; +import {TestDeviceNFT} from "../contracts/test/TestDeviceNFT.sol"; import {TestStrategyManager} from "../contracts/test/TestStrategyManager.sol"; import {FactoryRegistry} from "../contracts/factories/FactoryRegistry.sol"; import {GaugeFactory} from "../contracts/factories/GaugeFactory.sol"; @@ -162,7 +163,7 @@ contract TestVoter is Test { voter.createGauge(poolFactory, address(pool), 0); // 3. create NFT gauge - address deviceNFT = address(10); + address deviceNFT = address(new TestDeviceNFT("name", "symbol")); voter.createGauge(poolFactory, deviceNFT, 1); gauge = voter.gauges(address(deviceNFT)); assertTrue(gauge != address(0));