diff --git a/.changeset/moody-beds-burn.md b/.changeset/moody-beds-burn.md new file mode 100644 index 00000000..d3350f33 --- /dev/null +++ b/.changeset/moody-beds-burn.md @@ -0,0 +1,5 @@ +--- +"@exactly/protocol": patch +--- + +🐛 rewards: fix released calculation diff --git a/.gas-snapshot b/.gas-snapshot index 1fe0c603..891d5684 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -293,8 +293,8 @@ PreviewerTest:testAccountsWithAccountOnlyDeposit() (gas: 862391) PreviewerTest:testAccountsWithAccountThatHasBalances() (gas: 2265026) PreviewerTest:testAccountsWithEmptyAccount() (gas: 690558) PreviewerTest:testAccountsWithIntermediateOperationsReturningAccurateAmounts() (gas: 17617503) -PreviewerTest:testActualTimeBeforeStartDistributionRewards() (gas: 7753427) -PreviewerTest:testEmptyExactly() (gas: 5645469) +PreviewerTest:testActualTimeBeforeStartDistributionRewards() (gas: 7774796) +PreviewerTest:testEmptyExactly() (gas: 5666767) PreviewerTest:testExactlyReturningInterestRateModelData() (gas: 688149) PreviewerTest:testFixedAvailableLiquidityProjectingNewFloatingDebt() (gas: 13301596) PreviewerTest:testFixedPoolsA() (gas: 19318341) @@ -305,7 +305,7 @@ PreviewerTest:testFlexibleAvailableLiquidity() (gas: 17242437) PreviewerTest:testFlexibleBorrowSharesAndAssets() (gas: 4401038) PreviewerTest:testFloatingAvailableLiquidityProjectingNewFloatingDebt() (gas: 12554334) PreviewerTest:testFloatingRateAndUtilization() (gas: 1128246) -PreviewerTest:testJustUpdatedRewardRatesShouldStillReturnRate() (gas: 7174821) +PreviewerTest:testJustUpdatedRewardRatesShouldStillReturnRate() (gas: 7196163) PreviewerTest:testMaxBorrowAssetsCapacity() (gas: 2469700) PreviewerTest:testMaxBorrowAssetsCapacityForAccountWithShortfall() (gas: 10991846) PreviewerTest:testMaxBorrowAssetsCapacityPerMarket() (gas: 13181978) @@ -352,12 +352,12 @@ PreviewerTest:testPreviewWithdrawAtMaturityWithOneUnit() (gas: 251718) PreviewerTest:testPreviewWithdrawAtMaturityWithSameTimestamp() (gas: 233406) PreviewerTest:testPreviewWithdrawAtMaturityWithZeroAmount() (gas: 251675) PreviewerTest:testReserveFactor() (gas: 707280) -PreviewerTest:testReturnRewardAssetUsdPrice() (gas: 6697322) -PreviewerTest:testRewardsRateAfterDistributionEnd() (gas: 7481122) -PreviewerTest:testRewardsRateOnlyWithFixedBorrows() (gas: 6788297) -PreviewerTest:testRewardsRateWithDifferentRewardLengths() (gas: 19241268) -PreviewerTest:testRewardsRateWithMarketWithDifferentDecimals() (gas: 18372292) -PreviewerTest:testRewardsRateX() (gas: 8127662) +PreviewerTest:testReturnRewardAssetUsdPrice() (gas: 6718669) +PreviewerTest:testRewardsRateAfterDistributionEnd() (gas: 7502464) +PreviewerTest:testRewardsRateOnlyWithFixedBorrows() (gas: 6809617) +PreviewerTest:testRewardsRateWithDifferentRewardLengths() (gas: 19262635) +PreviewerTest:testRewardsRateWithMarketWithDifferentDecimals() (gas: 18393705) +PreviewerTest:testRewardsRateX() (gas: 8149075) PriceFeedDoubleTest:testPriceFeedDoubleReturningAccurateDecimals() (gas: 597567) PriceFeedDoubleTest:testPriceFeedDoubleReturningPrice() (gas: 53190) PriceFeedDoubleTest:testPriceFeedDoubleWithActualOnChainValues() (gas: 76310) @@ -374,69 +374,70 @@ PriceFeedWrapperTest:testPriceFeedWrapperReturningPriceAfterRebase() (gas: 48989 PriceFeedWrapperTest:testPriceFeedWrapperWithActualOnChainValues() (gas: 75210) PriceFeedWrapperTest:testPriceFeedWrapperWithNegativePriceShouldRevert() (gas: 164216) PriceFeedWrapperTest:testPriceFeedWrapperWithUsdPriceFeed() (gas: 1243191) -RewardsControllerTest:testAccrueRewardsForWholeDistributionPeriod() (gas: 1245256) -RewardsControllerTest:testAccrueRewardsWithBadDebtClearingOfFixedBorrow() (gas: 3338579) -RewardsControllerTest:testAccrueRewardsWithRepayOfBorrowBalance() (gas: 1602527) -RewardsControllerTest:testAccrueRewardsWithRepayOfFixedBorrowBalance() (gas: 1791601) -RewardsControllerTest:testAccrueRewardsWithSeizeOfAllDepositShares() (gas: 1991092) -RewardsControllerTest:testAfterDistributionPeriodEnd() (gas: 1818279) -RewardsControllerTest:testAllClaimableUSDCWithAnotherAccountInPool() (gas: 2273354) -RewardsControllerTest:testAllClaimableUSDCWithDeposit() (gas: 1627706) -RewardsControllerTest:testAllClaimableUSDCWithFloatingBorrow() (gas: 1560044) -RewardsControllerTest:testAllClaimableUSDCWithFloatingRefund() (gas: 1667236) -RewardsControllerTest:testAllClaimableUSDCWithFloatingRepay() (gas: 1673750) -RewardsControllerTest:testAllClaimableUSDCWithMint() (gas: 1284296) -RewardsControllerTest:testAllClaimableUSDCWithRedeem() (gas: 1640399) -RewardsControllerTest:testAllClaimableUSDCWithTransfer() (gas: 2220308) -RewardsControllerTest:testAllClaimableUSDCWithTransferFrom() (gas: 2135247) -RewardsControllerTest:testAllClaimableUSDCWithWithdraw() (gas: 1641434) -RewardsControllerTest:testAllClaimableWETH() (gas: 1247789) -RewardsControllerTest:testAllClaimableWithMaturedFixedPool() (gas: 1127875) -RewardsControllerTest:testAllClaimableWithTimeElapsedZero() (gas: 1624639) -RewardsControllerTest:testClaim() (gas: 1192161) -RewardsControllerTest:testClaimAll() (gas: 2188738) -RewardsControllerTest:testClaimMarketWithoutRewards() (gas: 1240983) -RewardsControllerTest:testClaimWithNotEnabledRewardAsset() (gas: 1222637) -RewardsControllerTest:testConfigSettingNewStartWithOnGoingDistributionShouldNotUpdate() (gas: 430182) -RewardsControllerTest:testConfigWithDistributionNotYetStartedShouldNotFail() (gas: 613404) -RewardsControllerTest:testConfigWithTransitionFactorHigherOrEqThanCap() (gas: 107189) -RewardsControllerTest:testConfigWithZeroDepositAllocationWeightFactorShouldRevert() (gas: 71542) -RewardsControllerTest:testDifferentDistributionTimeForDifferentRewards() (gas: 2025649) -RewardsControllerTest:testEmitAccrue() (gas: 1317916) -RewardsControllerTest:testEmitClaimRewards() (gas: 1112375) -RewardsControllerTest:testEmitConfigUpdate() (gas: 439567) -RewardsControllerTest:testEmitIndexUpdate() (gas: 1445530) -RewardsControllerTest:testLastUndistributed() (gas: 2189661) -RewardsControllerTest:testOperationAfterDistributionEnded() (gas: 722976) -RewardsControllerTest:testOperationsBeforeDistributionStart() (gas: 1674576) -RewardsControllerTest:testPermitClaim() (gas: 1275282) -RewardsControllerTest:testSetDistributionConfigWithDifferentDecimals() (gas: 11445059) -RewardsControllerTest:testSetDistributionOperationShouldUpdateIndex() (gas: 136200) -RewardsControllerTest:testSetDistributionWithOnGoingMarketOperations() (gas: 1202358) -RewardsControllerTest:testSetHigherTotalDistribution() (gas: 1831201) -RewardsControllerTest:testSetLowerAndEqualDistributionPeriodThanCurrentTimestampShouldRevert() (gas: 1274696) -RewardsControllerTest:testSetLowerAndEqualTotalDistributionThanReleasedShouldRevert() (gas: 1267721) -RewardsControllerTest:testSetLowerDistributionPeriod() (gas: 2284176) -RewardsControllerTest:testSetLowerDistributionPeriodAndLowerTotalDistribution() (gas: 2286901) -RewardsControllerTest:testSetLowerTotalDistribution() (gas: 1831114) -RewardsControllerTest:testSetNewDistributionPeriod() (gas: 3143972) -RewardsControllerTest:testSetNewDistributionPeriodAfterDistributionEnds() (gas: 1406666) -RewardsControllerTest:testSetNewTargetDebt() (gas: 1671525) -RewardsControllerTest:testSetNewTargetDebtAfterDistributionEnds() (gas: 1734966) -RewardsControllerTest:testSetNewTargetDebtWithClaimOnlyAtEnd() (gas: 1389661) -RewardsControllerTest:testSetNewTreasuryFeeShouldImpactAllocation() (gas: 658882) -RewardsControllerTest:testSetTargetDebtMultipleTimes() (gas: 2719324) -RewardsControllerTest:testSetTargetDebtMultipleTimesAfterEnd() (gas: 2756094) -RewardsControllerTest:testSetTotalDistributionMultipleTimes() (gas: 1838222) -RewardsControllerTest:testTriggerHandleBorrowHookBeforeUpdatingFloatingDebt() (gas: 1879339) -RewardsControllerTest:testUpdateConfig() (gas: 1328855) -RewardsControllerTest:testUpdateIndexesWithUtilizationEqualToOne() (gas: 1257771) -RewardsControllerTest:testUpdateIndexesWithUtilizationHigherThanOne() (gas: 1352512) -RewardsControllerTest:testUpdateWithTotalDebtZeroShouldUpdateLastUndistributed() (gas: 575506) -RewardsControllerTest:testUtilizationEqualZero() (gas: 921863) -RewardsControllerTest:testWithTwelveFixedPools() (gas: 8055024) +RewardsControllerTest:testAccrueRewardsForWholeDistributionPeriod() (gas: 1245322) +RewardsControllerTest:testAccrueRewardsWithBadDebtClearingOfFixedBorrow() (gas: 3338667) +RewardsControllerTest:testAccrueRewardsWithRepayOfBorrowBalance() (gas: 1602615) +RewardsControllerTest:testAccrueRewardsWithRepayOfFixedBorrowBalance() (gas: 1791645) +RewardsControllerTest:testAccrueRewardsWithSeizeOfAllDepositShares() (gas: 1991114) +RewardsControllerTest:testAfterDistributionPeriodEnd() (gas: 1818322) +RewardsControllerTest:testAllClaimableUSDCWithAnotherAccountInPool() (gas: 2274252) +RewardsControllerTest:testAllClaimableUSDCWithDeposit() (gas: 1628756) +RewardsControllerTest:testAllClaimableUSDCWithFloatingBorrow() (gas: 1560744) +RewardsControllerTest:testAllClaimableUSDCWithFloatingRefund() (gas: 1667674) +RewardsControllerTest:testAllClaimableUSDCWithFloatingRepay() (gas: 1674188) +RewardsControllerTest:testAllClaimableUSDCWithMint() (gas: 1284996) +RewardsControllerTest:testAllClaimableUSDCWithRedeem() (gas: 1640815) +RewardsControllerTest:testAllClaimableUSDCWithTransfer() (gas: 2221471) +RewardsControllerTest:testAllClaimableUSDCWithTransferFrom() (gas: 2136365) +RewardsControllerTest:testAllClaimableUSDCWithWithdraw() (gas: 1641850) +RewardsControllerTest:testAllClaimableWETH() (gas: 1248402) +RewardsControllerTest:testAllClaimableWithMaturedFixedPool() (gas: 1128007) +RewardsControllerTest:testAllClaimableWithTimeElapsedZero() (gas: 1624747) +RewardsControllerTest:testClaim() (gas: 1192249) +RewardsControllerTest:testClaimAll() (gas: 2188892) +RewardsControllerTest:testClaimMarketWithoutRewards() (gas: 1241017) +RewardsControllerTest:testClaimWithNotEnabledRewardAsset() (gas: 1222725) +RewardsControllerTest:testConfigSettingNewStartWithOnGoingDistributionShouldNotUpdate() (gas: 430252) +RewardsControllerTest:testConfigWithDistributionNotYetStartedShouldNotFail() (gas: 613248) +RewardsControllerTest:testConfigWithTransitionFactorHigherOrEqThanCap() (gas: 107077) +RewardsControllerTest:testConfigWithZeroDepositAllocationWeightFactorShouldRevert() (gas: 71475) +RewardsControllerTest:testDifferentDistributionTimeForDifferentRewards() (gas: 2025778) +RewardsControllerTest:testEmitAccrue() (gas: 1317982) +RewardsControllerTest:testEmitClaimRewards() (gas: 1112419) +RewardsControllerTest:testEmitConfigUpdate() (gas: 439411) +RewardsControllerTest:testEmitIndexUpdate() (gas: 1445640) +RewardsControllerTest:testLastUndistributed() (gas: 2189926) +RewardsControllerTest:testOperationAfterDistributionEnded() (gas: 723020) +RewardsControllerTest:testOperationsBeforeDistributionStart() (gas: 1674553) +RewardsControllerTest:testPermitClaim() (gas: 1275326) +RewardsControllerTest:testSetDistributionConfigWithDifferentDecimals() (gas: 11445190) +RewardsControllerTest:testSetDistributionOperationShouldUpdateIndex() (gas: 136066) +RewardsControllerTest:testSetDistributionWithOnGoingMarketOperations() (gas: 1202335) +RewardsControllerTest:testSetHigherTotalDistribution() (gas: 1831337) +RewardsControllerTest:testSetLowerAndEqualDistributionPeriodThanCurrentTimestampShouldRevert() (gas: 1274818) +RewardsControllerTest:testSetLowerAndEqualTotalDistributionThanReleasedShouldRevert() (gas: 1267821) +RewardsControllerTest:testSetLowerDistributionPeriod() (gas: 2284312) +RewardsControllerTest:testSetLowerDistributionPeriodAndLowerTotalDistribution() (gas: 2287059) +RewardsControllerTest:testSetLowerTotalDistribution() (gas: 1831250) +RewardsControllerTest:testSetNewDistributionPeriod() (gas: 3144152) +RewardsControllerTest:testSetNewDistributionPeriodAfterDistributionEnds() (gas: 1406553) +RewardsControllerTest:testSetNewTargetDebt() (gas: 1671639) +RewardsControllerTest:testSetNewTargetDebtAfterDistributionEnds() (gas: 1735009) +RewardsControllerTest:testSetNewTargetDebtWithClaimOnlyAtEnd() (gas: 1389753) +RewardsControllerTest:testSetNewTreasuryFeeShouldImpactAllocation() (gas: 658948) +RewardsControllerTest:testSetTargetDebtMultipleTimes() (gas: 2719552) +RewardsControllerTest:testSetTargetDebtMultipleTimesAfterEnd() (gas: 2756047) +RewardsControllerTest:testSetTotalDistributionMultipleTimes() (gas: 1838384) +RewardsControllerTest:testTriggerHandleBorrowHookBeforeUpdatingFloatingDebt() (gas: 1879494) +RewardsControllerTest:testUpdateConfig() (gas: 1328946) +RewardsControllerTest:testUpdateConfigIncreaseRewardDistribution() (gas: 405501) +RewardsControllerTest:testUpdateIndexesWithUtilizationEqualToOne() (gas: 1257815) +RewardsControllerTest:testUpdateIndexesWithUtilizationHigherThanOne() (gas: 1352623) +RewardsControllerTest:testUpdateWithTotalDebtZeroShouldUpdateLastUndistributed() (gas: 575527) +RewardsControllerTest:testUtilizationEqualZero() (gas: 921840) +RewardsControllerTest:testWithTwelveFixedPools() (gas: 8055310) RewardsControllerTest:testWithdrawAllRewardBalance() (gas: 71913) -RewardsControllerTest:testWithdrawOnlyAdminRole() (gas: 122309) +RewardsControllerTest:testWithdrawOnlyAdminRole() (gas: 122331) SwapperTest:testSwapBasic() (gas: 216831) SwapperTest:testSwapWithAllowance() (gas: 481530) SwapperTest:testSwapWithInaccurateSlippageSendsETHToAccount() (gas: 297968) diff --git a/contracts/RewardsController.sol b/contracts/RewardsController.sol index 9b7b064f..3e8c4c47 100644 --- a/contracts/RewardsController.sol +++ b/contracts/RewardsController.sol @@ -227,6 +227,14 @@ contract RewardsController is Initializable, AccessControlUpgradeable { return (rewardData.start, rewardData.end, rewardData.lastUpdate); } + /// @notice Gets the release reward rate of a given market and reward. + /// @param market The market to get the release rate. + /// @param reward The reward asset. + /// @return The release reward rate. + function releaseRate(Market market, ERC20 reward) external view returns (uint256) { + return distribution[market].rewards[reward].releaseRate; + } + /// @notice Retrieves all rewards addresses. function allRewards() external view returns (ERC20[] memory) { return rewardList; @@ -501,7 +509,7 @@ contract RewardsController is Initializable, AccessControlUpgradeable { } uint256 rewards; { - uint256 releaseRate = rewardData.releaseRate; + uint256 rate = rewardData.releaseRate; uint256 lastUndistributed = rewardData.lastUndistributed; t.period = t.end - t.start; uint256 distributionFactor = t.period > 0 @@ -512,11 +520,11 @@ contract RewardsController is Initializable, AccessControlUpgradeable { uint256 exponential = uint256((-int256(distributionFactor * deltaTime)).expWad()); newUndistributed = lastUndistributed.mulWadDown(exponential) + - releaseRate.mulDivDown(1e18 - target, distributionFactor).mulWadUp(1e18 - exponential); + rate.mulDivDown(1e18 - target, distributionFactor).mulWadUp(1e18 - exponential); } else { - newUndistributed = lastUndistributed + releaseRate.mulWadDown(1e18 - target) * deltaTime; + newUndistributed = lastUndistributed + rate.mulWadDown(1e18 - target) * deltaTime; } - rewards = uint256(int256(releaseRate * deltaTime) - (int256(newUndistributed) - int256(lastUndistributed))); + rewards = uint256(int256(rate * deltaTime) - (int256(newUndistributed) - int256(lastUndistributed))); } else if (rewardData.lastUpdate > t.end) { newUndistributed = lastUndistributed - @@ -529,13 +537,13 @@ contract RewardsController is Initializable, AccessControlUpgradeable { exponential = uint256((-int256(distributionFactor * deltaTime)).expWad()); newUndistributed = lastUndistributed.mulWadDown(exponential) + - releaseRate.mulDivDown(1e18 - target, distributionFactor).mulWadUp(1e18 - exponential); + rate.mulDivDown(1e18 - target, distributionFactor).mulWadUp(1e18 - exponential); } else { - newUndistributed = lastUndistributed + releaseRate.mulWadDown(1e18 - target) * deltaTime; + newUndistributed = lastUndistributed + rate.mulWadDown(1e18 - target) * deltaTime; } exponential = uint256((-int256(distributionFactor * (block.timestamp - t.end))).expWad()); newUndistributed = newUndistributed - newUndistributed.mulWadUp(1e18 - exponential); - rewards = uint256(int256(releaseRate * deltaTime) - (int256(newUndistributed) - int256(lastUndistributed))); + rewards = uint256(int256(rate * deltaTime) - (int256(newUndistributed) - int256(lastUndistributed))); } if (rewards == 0) return (rewardData.borrowIndex, rewardData.depositIndex, newUndistributed); } @@ -678,7 +686,7 @@ contract RewardsController is Initializable, AccessControlUpgradeable { released = rewardData.lastConfigReleased + rewardData.releaseRate * - (block.timestamp - rewardData.lastConfig); + (block.timestamp - Math.max(rewardData.lastConfig, start)); elapsed = block.timestamp - start; if (configs[i].totalDistribution <= released || configs[i].distributionPeriod <= elapsed) { revert InvalidConfig(); diff --git a/test/RewardsController.t.sol b/test/RewardsController.t.sol index b68fa3f7..3e411a2a 100644 --- a/test/RewardsController.t.sol +++ b/test/RewardsController.t.sol @@ -637,6 +637,59 @@ contract RewardsControllerTest is Test { assertEq(config.start, 0); } + function testUpdateConfigIncreaseRewardDistribution() external { + vm.warp(20 weeks); + + opRewardAsset.mint(address(rewardsController), 4_000 ether); + RewardsController.Config[] memory configs = new RewardsController.Config[](1); + configs[0] = RewardsController.Config({ + market: marketUSDC, + reward: opRewardAsset, + priceFeed: MockPriceFeed(address(0)), + targetDebt: 40_000e6, + totalDistribution: 4_000 ether, + start: uint32(40 weeks), + distributionPeriod: 10 weeks, + undistributedFactor: 0.5e18, + flipSpeed: 2e18, + compensationFactor: 0.85e18, + transitionFactor: 0.64e18, + borrowAllocationWeightFactor: 0, + depositAllocationWeightAddend: 0.02e18, + depositAllocationWeightFactor: 0.01e18 + }); + rewardsController.config(configs); + + uint256 oldReleaseRate = rewardsController.releaseRate(marketUSDC, opRewardAsset); + + vm.warp(42 weeks); + opRewardAsset.mint(address(rewardsController), 5_000 ether); + configs[0] = RewardsController.Config({ + market: marketUSDC, + reward: opRewardAsset, + priceFeed: MockPriceFeed(address(0)), + targetDebt: 40_000e6, + totalDistribution: 9_000 ether, + start: uint32(40 weeks), + distributionPeriod: 10 weeks, + undistributedFactor: 0.5e18, + flipSpeed: 2e18, + compensationFactor: 0.85e18, + transitionFactor: 0.64e18, + borrowAllocationWeightFactor: 0, + depositAllocationWeightAddend: 0.02e18, + depositAllocationWeightFactor: 0.01e18 + }); + rewardsController.config(configs); + + uint256 newReleaseRate = rewardsController.releaseRate(marketUSDC, opRewardAsset); + + assertApproxEqAbs(oldReleaseRate * 10 weeks, 4_000 ether, 1e9); + assertApproxEqAbs(oldReleaseRate * 2 weeks, 800 ether, 1e9); + assertApproxEqAbs(newReleaseRate * 8 weeks, 8_200 ether, 1e9); + assertApproxEqAbs(oldReleaseRate * 2 weeks + newReleaseRate * 8 weeks, 9_000 ether, 1e9); + } + function testConfigWithDistributionNotYetStartedShouldNotFail() external { RewardsController.Config[] memory configs = new RewardsController.Config[](1); configs[0] = RewardsController.Config({ diff --git a/test/hardhat/19_rewards_controller.ts b/test/hardhat/19_rewards_controller.ts index e80f7ca4..d32fdb6e 100644 --- a/test/hardhat/19_rewards_controller.ts +++ b/test/hardhat/19_rewards_controller.ts @@ -54,6 +54,12 @@ describe("RewardsController", function () { expect(claimedBalance).to.be.greaterThan(0); }); + it("THEN the release rate is positive", async () => { + const releaseRate = await rewardsController.releaseRate(marketUSDC, op.target); + + expect(releaseRate).to.be.greaterThan(0); + }); + it("AND trying to claim with invalid market THEN the claimable amount is 0", async () => { const marketOps = [{ market: alice.address, operations: [false, true] }]; const claimableBalance = await rewardsController.claimable(marketOps, alice.address, op.target);