Skip to content

Commit

Permalink
Merge pull request #15 from iotexproject/feat/gauge-threshold
Browse files Browse the repository at this point in the history
feat: add threshold to control distribute reward to gauge;
  • Loading branch information
ludete authored Aug 12, 2024
2 parents 2876243 + 9f25dae commit 6bf668a
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 29 deletions.
22 changes: 19 additions & 3 deletions contracts/Voter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ 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) {
forwarder = _forwarder;
Expand Down Expand Up @@ -151,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;
Expand Down Expand Up @@ -265,7 +276,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
Expand All @@ -277,7 +288,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;
Expand All @@ -286,6 +297,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
isAlive[_gauge] = true;
_updateFor(_gauge);
pools.push(_pool);
triggerThreshold[_gauge] = threshold;

emit GaugeCreated(_poolFactory, gaugeFactory, _pool, _gauge, sender);
return _gauge;
Expand Down Expand Up @@ -323,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;
Expand Down Expand Up @@ -360,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
Expand Down
5 changes: 4 additions & 1 deletion contracts/factories/GaugeFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions contracts/gauges/DeviceGauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions contracts/gauges/ERC20Gauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
11 changes: 10 additions & 1 deletion contracts/gauges/RewardGauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 2 additions & 0 deletions contracts/interfaces/IRewardGauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
12 changes: 11 additions & 1 deletion contracts/interfaces/IVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand All @@ -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 .
Expand All @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/factories/IGaugeFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface IGaugeFactory {
address _forwarder,
address _poolOrDeviceNFTOrGauge,
address _incentives,
uint8 _gaugeType
uint8 _gaugeType,
uint256 _threshold
) external returns (address gauge);
}
1 change: 1 addition & 0 deletions contracts/test/TestDeviceNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ contract TestDeviceNFT is IDeviceNFT, ERC721 {

function mint(address to, uint tokenId) external {
_mint(to, tokenId);
weightOf[tokenId] = 1 ether;
}
}
34 changes: 25 additions & 9 deletions test/TestGauge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ 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";
import "../contracts/factories/GaugeFactory.sol";
import "../contracts/factories/FactoryRegistry.sol";
import "../contracts/factories/IncentivesFactory.sol";

contract TestERC20Gauge is Test {
ERC20Gauge public gauge;
DAOForwarder public forwarder;
TestToken public pool;
Voter public voter;
TestStrategyManager public strategyManager;

fallback() external payable {}

function setUp() public {
pool = new TestToken("lp_pool", "pool");
forwarder = new DAOForwarder();
// manager & _factoryRegistry not used in gauge
voter = new Voter(address(forwarder), address(this), 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));
strategyManager = new TestStrategyManager();
strategyManager.setShare(address (this), 100);
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 {
Expand Down Expand Up @@ -70,30 +80,36 @@ 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));
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);
Expand Down
13 changes: 12 additions & 1 deletion test/TestVault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 6bf668a

Please sign in to comment.