Skip to content

Commit

Permalink
feat: implement get reward with address, used by voter only (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
simplyoptimistic authored Jan 15, 2024
1 parent 0ff9379 commit fb14d0f
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 0 deletions.
4 changes: 4 additions & 0 deletions contracts/core/interfaces/IVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ interface IVoter {

function emergencyCouncil() external view returns (address);

/// @notice Claim emissions from gauges.
/// @param _gauges Array of gauges to collect emissions from.
function claimRewards(address[] memory _gauges) external;

/// @notice Claim fees for a given NFT.
/// @dev Utility to help batch fee claims.
/// @param _fees Array of FeesVotingReward contracts to collect from.
Expand Down
16 changes: 16 additions & 0 deletions contracts/gauge/CLGauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ contract CLGauge is ICLGauge, ERC721Holder, ReentrancyGuard {
return claimable;
}

/// @inheritdoc ICLGauge
function getReward(address account) external override nonReentrant {
require(msg.sender == address(voter), "NV");

uint256[] memory tokenIds = _stakes[account].values();
uint256 length = tokenIds.length;
uint256 tokenId;
int24 tickLower;
int24 tickUpper;
for (uint256 i = 0; i < length; i++) {
tokenId = tokenIds[i];
(,,,,, tickLower, tickUpper,,,,,) = nft.positions(tokenId);
_getReward(tickLower, tickUpper, tokenId, account);
}
}

/// @inheritdoc ICLGauge
function getReward(uint256 tokenId) external override nonReentrant {
require(_stakes[msg.sender].contains(tokenId), "NA");
Expand Down
5 changes: 5 additions & 0 deletions contracts/gauge/interfaces/ICLGauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ interface ICLGauge {
/// @return The amount of claimable reward
function earned(address account, uint256 tokenId) external view returns (uint256);

/// @notice Retrieve rewards for all tokens owned by an account
/// @dev Throws if not called by the voter
/// @param account The account of the user
function getReward(address account) external;

/// @notice Retrieve rewards for a tokenId
/// @dev Throws if not called by the position owner
/// @param tokenId The tokenId of the position
Expand Down
7 changes: 7 additions & 0 deletions contracts/test/MockVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,11 @@ contract MockVoter is IVoter {
}

function vote(uint256 _tokenId, address[] calldata _poolVote, uint256[] calldata _weights) external override {}

function claimRewards(address[] memory _gauges) external override {
uint256 _length = _gauges.length;
for (uint256 i = 0; i < _length; i++) {
ICLGauge(_gauges[i]).getReward(msg.sender);
}
}
}
78 changes: 78 additions & 0 deletions test/unit/concrete/CLGauge/getReward.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ contract GetRewardTest is CLGaugeTest {
skipToNextEpoch(0);
}

function labelContracts() internal override {
super.labelContracts();

vm.label({account: address(pool), newLabel: "Pool"});
vm.label({account: address(gauge), newLabel: "Gauge"});
}

function test_RevertIf_CallerIsNotOwner() public {
uint256 tokenId = nftCallee.mintNewFullRangePositionForUserWith60TickSpacing(TOKEN_1, TOKEN_1, users.alice);

Expand Down Expand Up @@ -502,4 +509,75 @@ contract GetRewardTest is CLGaugeTest {
// gauge should have 0 rewards left (not counting dust)
assertLe(gaugeRewardTokenBalance, 1e6);
}

function test_RevertIf_CallerIsNotVoter() public {
uint256 tokenId = nftCallee.mintNewFullRangePositionForUserWith60TickSpacing(TOKEN_1, TOKEN_1, users.alice);

nft.approve(address(gauge), tokenId);
gauge.deposit(tokenId);

vm.startPrank(users.charlie);
vm.expectRevert(abi.encodePacked("NV"));
gauge.getReward(users.alice);
}

function test_GetRewardAsVoterWithSingleDeposit() public {
uint256 tokenId = nftCallee.mintNewFullRangePositionForUserWith60TickSpacing(TOKEN_1, TOKEN_1, users.alice);

nft.approve(address(gauge), tokenId);
gauge.deposit(tokenId);

uint256 reward = TOKEN_1;
addRewardToGauge(address(voter), address(gauge), reward);

skip(2 days);

vm.startPrank(users.alice);

address[] memory gauges = new address[](1);
gauges[0] = address(gauge);

vm.expectEmit(true, true, false, true, address(gauge));
emit ClaimRewards(users.alice, 285714285714259199);
voter.claimRewards(gauges);

uint256 aliceRewardBalance = rewardToken.balanceOf(users.alice);
// alice should have 2 days worth of rewards
assertApproxEqAbs(aliceRewardBalance, reward / 7 * 2, 1e5);
assertEq(gauge.rewards(tokenId), 0);
assertEq(gauge.lastUpdateTime(tokenId), 777600);
}

function test_GetRewardAsVoterWithMultipleDeposits() public {
uint256 tokenId = nftCallee.mintNewFullRangePositionForUserWith60TickSpacing(TOKEN_1, TOKEN_1, users.alice);
uint256 tokenId2 = nftCallee.mintNewFullRangePositionForUserWith60TickSpacing(TOKEN_1, TOKEN_1, users.alice);

nft.approve(address(gauge), tokenId);
gauge.deposit(tokenId);
nft.approve(address(gauge), tokenId2);
gauge.deposit(tokenId2);

uint256 reward = TOKEN_1;
addRewardToGauge(address(voter), address(gauge), reward);

skip(2 days);

vm.startPrank(users.alice);

address[] memory gauges = new address[](1);
gauges[0] = address(gauge);

vm.expectEmit(true, true, false, true, address(gauge));
emit ClaimRewards(users.alice, 142857142857129599);
emit ClaimRewards(users.alice, 142857142857129599);
voter.claimRewards(gauges);

uint256 aliceRewardBalance = rewardToken.balanceOf(users.alice);
// alice should have 2 days worth of rewards
assertApproxEqAbs(aliceRewardBalance, reward / 7 * 2, 1e5);
assertEq(gauge.rewards(tokenId), 0);
assertEq(gauge.lastUpdateTime(tokenId), 777600);
assertEq(gauge.rewards(tokenId2), 0);
assertEq(gauge.lastUpdateTime(tokenId2), 777600);
}
}

0 comments on commit fb14d0f

Please sign in to comment.