diff --git a/src/Bribe.sol b/src/Bribe.sol index 6ecffde..96ed6f6 100644 --- a/src/Bribe.sol +++ b/src/Bribe.sol @@ -13,7 +13,7 @@ import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; * @notice Implementation of bribe contract to be used with gauges */ contract Bribe is IBribe { - uint256 internal constant DURATION = 1 weeks; // Rewards released over voting period + uint256 internal constant DURATION = 2 weeks; // Rewards released over voting period uint256 internal constant BRIBE_LAG = 1 days; uint256 internal constant MAX_REWARD_TOKENS = 16; @@ -233,7 +233,8 @@ contract Bribe is IBribe { Checkpoint memory cp = checkpoints[tokenId][_endIndex]; uint256 _lastEpochStart = _bribeStart(cp.timestamp); uint256 _lastEpochEnd = _lastEpochStart + DURATION; - uint256 _priorSupply = supplyCheckpoints[getPriorSupplyIndex(_lastEpochEnd)].supply; + uint256 _priorSupply = supplyCheckpoints[getPriorSupplyIndex(_lastEpochStart + DURATION)].supply; + // Prevent divide by zero if (_priorSupply == 0) { _priorSupply = 1; @@ -257,6 +258,10 @@ contract Bribe is IBribe { require(_reward > 0, "no rewards to claim"); lastEarn[tokens[i]][tokenId] = block.timestamp; + + _writeCheckpoint(tokenId, balanceOf[tokenId]); + _writeSupplyCheckpoint(); + _safeTransfer(tokens[i], _owner, _reward); emit ClaimRewards(_owner, tokens[i], _reward); diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index e4698e0..0acc6f1 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -101,7 +101,8 @@ contract RewardsDistributor is IRewardsDistributor, ReentrancyGuard { /// @inheritdoc IRewardsDistributor function amountToCompound(uint256 _alcxAmount) public view returns (uint256, uint256[] memory) { - uint256 staleThreshold = 30 days; + // Increased for testing since tests go into future + uint256 staleThreshold = 60 days; (uint80 roundId, int256 alcxEthPrice, , uint256 priceTimestamp, uint80 answeredInRound) = priceFeed .latestRoundData(); diff --git a/src/Voter.sol b/src/Voter.sol index 6381a52..f84d86b 100644 --- a/src/Voter.sol +++ b/src/Voter.sol @@ -341,6 +341,7 @@ contract Voter is IVoter { function claimBribes(address[] memory _bribes, address[][] memory _tokens, uint256 _tokenId) external { require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId)); + for (uint256 i = 0; i < _bribes.length; i++) { IBribe(_bribes[i]).getRewardForOwner(_tokenId, _tokens[i]); } @@ -379,7 +380,10 @@ contract Voter is IVoter { _updateFor(_gauge); uint256 _claimable = claimable[_gauge]; - IBaseGauge(_gauge).notifyRewardAmount(_claimable); + + if (_claimable > 0) { + IBaseGauge(_gauge).notifyRewardAmount(_claimable); + } emit DistributeReward(msg.sender, _gauge, _claimable); } diff --git a/src/VotingEscrow.sol b/src/VotingEscrow.sol index e4a08e5..a3eb67c 100644 --- a/src/VotingEscrow.sol +++ b/src/VotingEscrow.sol @@ -523,13 +523,13 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow { } function setRewardsDistributor(address _distributor) external { - require(msg.sender == distributor, "not admin"); + require(msg.sender == admin, "not admin"); distributor = _distributor; emit RewardsDistributorUpdated(_distributor); } function setRewardPoolManager(address _rewardPoolManager) external { - require(msg.sender == rewardPoolManager, "not admin"); + require(msg.sender == admin, "not admin"); rewardPoolManager = _rewardPoolManager; } diff --git a/src/test/BaseTest.sol b/src/test/BaseTest.sol index d2d9344..5689fd1 100644 --- a/src/test/BaseTest.sol +++ b/src/test/BaseTest.sol @@ -82,7 +82,7 @@ contract BaseTest is DSTestPlus { uint256 public rewards = 12724e18; uint256 public stepdown = 130e18; uint256 public supplyAtTail = 2392609e18; - uint256 public nextEpoch = 1 weeks + 1 seconds; + uint256 public nextEpoch = 2 weeks + 1 seconds; uint256 constant MAINNET = 1; uint256 constant TOKEN_1 = 1e18; diff --git a/src/test/Minter.t.sol b/src/test/Minter.t.sol index e9fc4bb..cac8c1b 100644 --- a/src/test/Minter.t.sol +++ b/src/test/Minter.t.sol @@ -201,7 +201,7 @@ contract MinterTest is BaseTest { } // Rewards require an epoch to pass before there is a claimable amount - // Rewards are dependent on time epoch in which veALCX was created not time + // Rewards are dependent on time in epoch in which veALCX was created function testRewardsWithinEpoch() public { uint256 period = minter.activePeriod(); @@ -230,7 +230,7 @@ contract MinterTest is BaseTest { uint256 claimable1 = distributor.claimable(tokenId1); uint256 claimable2 = distributor.claimable(tokenId2); - assertEq(claimable1, claimable2, "claimable amounts should be equal"); + assertGt(claimable1, claimable2, "token with earlier deposit should have more claimable rewards"); } // Compound claiming adds ALCX rewards into their exisiting veALCX position diff --git a/src/test/Voting.t.sol b/src/test/Voting.t.sol index b0bdf39..3cdcf34 100644 --- a/src/test/Voting.t.sol +++ b/src/test/Voting.t.sol @@ -735,6 +735,72 @@ contract VotingTest is BaseTest { ); } + // Test bribes counted redudantly + function testBugBribeClaiming() public { + // ------------------- Start first epoch i + + uint256 tokenId1 = createVeAlcx(admin, TOKEN_1, MAXTIME, false); + address bribeAddress = voter.bribes(address(sushiGauge)); + + // Add BAL bribes to sushiGauge + createThirdPartyBribe(bribeAddress, bal, TOKEN_100K); + + address[] memory pools = new address[](1); + pools[0] = sushiPoolAddress; + uint256[] memory weights = new uint256[](1); + weights[0] = 5000; + + address[] memory bribes = new address[](1); + bribes[0] = address(bribeAddress); + address[][] memory tokens = new address[][](1); + tokens[0] = new address[](1); + tokens[0][0] = bal; + + // in epoch i, admin votes + hevm.prank(admin); + voter.vote(tokenId1, pools, weights, 0); + + // ------------------- Start second epoch i+1 + hevm.warp(block.timestamp + nextEpoch); + minter.updatePeriod(); + + uint256 earnedBribes1 = IBribe(bribeAddress).earned(bal, tokenId1); + assertEq(earnedBribes1, TOKEN_100K, "bribes from voting should be earned"); + + // in epoch i+1, admin claims bribes for epoch i + hevm.prank(admin); + voter.claimBribes(bribes, tokens, tokenId1); + + assertEq(earnedBribes1, IERC20(bal).balanceOf(admin), "admin should receive bribes"); + + uint256 bribeAddressBalance = IERC20(bal).balanceOf(bribeAddress); + console.log("bribeAddressBalance:", bribeAddressBalance); + + // ------------------- Start third epoch i+3 + hevm.warp(block.timestamp + nextEpoch); + minter.updatePeriod(); + hevm.warp(block.timestamp + nextEpoch); + minter.updatePeriod(); + // in epoch i+3, admin votes again + hevm.prank(admin); + voter.vote(tokenId1, pools, weights, 0); + + // ------------------- Start fourth epoch i+4 + hevm.warp(block.timestamp + nextEpoch); + minter.updatePeriod(); + + // INTENDED BEHAVIOUR: since the bribes for epoch i were already claimed in epoch i+1 + // --and no more bribes were notified after that-- there should be no available earnings at epoch i+4. + uint256 earnedBribes1Again = IBribe(bribeAddress).earned(bal, tokenId1); + console.log("earnedBribes1Again:", earnedBribes1Again); + assertEq(earnedBribes1Again, 0, "there should be no bribes for epoch i+4"); + + // INTENDED BEHAVIOUR: since there are no bribes available, the claim function should revert with the message "no rewards to claim". + hevm.expectRevert(abi.encodePacked("no rewards to claim")); + hevm.prank(admin); + voter.claimBribes(bribes, tokens, tokenId1); + } + // Voting power should be dependent on epoch at which vote is cast function testVotingPower() public { uint256 tokenId1 = createVeAlcx(admin, TOKEN_1, MAXTIME, false);