Skip to content

Commit

Permalink
Merge branch 'develop' into universal-deposit-fee
Browse files Browse the repository at this point in the history
  • Loading branch information
EdNoepel committed Nov 17, 2023
2 parents 70cb64c + 185d71c commit 3bf501a
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 31 deletions.
43 changes: 25 additions & 18 deletions src/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Maths } from './libraries/internal/Maths.sol';
/**
* @title Rewards (staking) Manager contract
* @notice Pool lenders can optionally mint `NFT` that represents their positions.
* The Rewards contract allows pool lenders with positions `NFT` to stake and earn `Ajna` tokens.
* The Rewards contract allows pool lenders with positions `NFT` to stake and earn `Ajna` tokens.
* Lenders with `NFT`s can:
* - `stake` token
* - `update bucket exchange rate` and earn rewards
Expand Down Expand Up @@ -128,12 +128,14 @@ contract RewardsManager is IRewardsManager {

if (isEpochClaimed[tokenId_][epochToClaim_]) revert AlreadyClaimed();

address ajnaPool = stakeInfo.ajnaPool;
uint256 rewardsEarned = _calculateAndClaimAllRewards(
stakeInfo,
tokenId_,
IPool(ajnaPool).currentBurnEpoch(),
epochToClaim_,
true,
stakeInfo.ajnaPool
ajnaPool
);

// transfer rewards to claimer, ensuring amount is not below specified min amount
Expand Down Expand Up @@ -162,13 +164,13 @@ contract RewardsManager is IRewardsManager {
stakeInfo.owner = msg.sender;
stakeInfo.ajnaPool = ajnaPool;

uint96 curBurnEpoch = uint96(IPool(ajnaPool).currentBurnEpoch());
uint256 curBurnEpoch = IPool(ajnaPool).currentBurnEpoch();

// record the staking epoch
stakeInfo.stakingEpoch = curBurnEpoch;
stakeInfo.stakingEpoch = uint96(curBurnEpoch);

// initialize last time interaction at staking epoch
stakeInfo.lastClaimedEpoch = curBurnEpoch;
stakeInfo.lastClaimedEpoch = uint96(curBurnEpoch);

uint256[] memory positionIndexes = positionManager.getPositionIndexes(tokenId_);
uint256 noOfPositions = positionIndexes.length;
Expand All @@ -195,6 +197,7 @@ contract RewardsManager is IRewardsManager {
// calculate rewards for updating exchange rates, if any
uint256 updateReward = _updateBucketExchangeRates(
ajnaPool,
curBurnEpoch,
positionIndexes
);

Expand Down Expand Up @@ -251,7 +254,7 @@ contract RewardsManager is IRewardsManager {
// revert if trying to update exchange rates for a non Ajna pool
if (!positionManager.isAjnaPool(pool_, subsetHash_)) revert NotAjnaPool();

updateReward = _updateBucketExchangeRates(pool_, indexes_);
updateReward = _updateBucketExchangeRates(pool_, IPool(pool_).currentBurnEpoch(), indexes_);

// transfer bucket update rewards to sender even if there's not enough balance for entire amount
_transferAjnaRewards({
Expand Down Expand Up @@ -429,7 +432,7 @@ contract RewardsManager is IRewardsManager {
bucketIndex,
bucketSnapshot.lpsAtStakeTime,
bucketRate
);
);
unchecked { ++i; }
}

Expand Down Expand Up @@ -528,17 +531,19 @@ contract RewardsManager is IRewardsManager {
function _calculateAndClaimAllRewards(
StakeInfo storage stakeInfo_,
uint256 tokenId_,
uint256 curBurnEpoch_,
uint256 epochToClaim_,
bool validateEpoch_,
address ajnaPool_
) internal returns (uint256 rewardsEarned_) {

// revert if higher epoch to claim than current burn epoch
if (validateEpoch_ && epochToClaim_ > IPool(ajnaPool_).currentBurnEpoch()) revert EpochNotAvailable();
if (validateEpoch_ && epochToClaim_ > curBurnEpoch_) revert EpochNotAvailable();

// update bucket exchange rates and claim associated rewards
rewardsEarned_ = _updateBucketExchangeRates(
ajnaPool_,
curBurnEpoch_,
positionManager.getPositionIndexes(tokenId_)
);

Expand Down Expand Up @@ -593,22 +598,22 @@ contract RewardsManager is IRewardsManager {
* @dev Called as part of `stake`, `unstake`, and `claimRewards`, as well as `updateBucketExchangeRatesAndClaim`.
* @dev Caller can claim `5%` of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated.
* @param pool_ Address of the pool whose exchange rates are being updated.
* @param curBurnEpoch_ Current Burn Epoch
* @param indexes_ List of bucket indexes to be updated.
* @return updatedRewards_ Update exchange rate rewards.
*/
function _updateBucketExchangeRates(
address pool_,
uint256 curBurnEpoch_,
uint256[] memory indexes_
) internal returns (uint256 updatedRewards_) {
// get the current burn epoch from the given pool
uint256 curBurnEpoch = IPool(pool_).currentBurnEpoch();

// retrieve epoch values used to determine if updater receives rewards
(
uint256 curBurnTime,
uint256 totalBurnedInEpoch,
uint256 totalInterestEarned
) = _getEpochInfo(pool_, curBurnEpoch);
) = _getEpochInfo(pool_, curBurnEpoch_);

// Update exchange rates without reward if first epoch or if the epoch does not have burned tokens associated with it
if (totalBurnedInEpoch == 0) {
Expand All @@ -618,7 +623,7 @@ contract RewardsManager is IRewardsManager {
_updateBucketExchangeRate(
pool_,
indexes_[i],
curBurnEpoch
curBurnEpoch_
);

// iterations are bounded by array length (which is itself bounded), preventing overflow / underflow
Expand All @@ -637,7 +642,7 @@ contract RewardsManager is IRewardsManager {
updatedRewards_ += _updateBucketExchangeRateAndCalculateRewards(
pool_,
indexes_[i],
curBurnEpoch,
curBurnEpoch_,
totalBurnedInEpoch,
totalInterestEarned
);
Expand All @@ -647,7 +652,7 @@ contract RewardsManager is IRewardsManager {
}

uint256 rewardsCap = Maths.wmul(UPDATE_CAP, totalBurnedInEpoch);
uint256 rewardsClaimedInEpoch = updateRewardsClaimed[curBurnEpoch];
uint256 rewardsClaimedInEpoch = updateRewardsClaimed[curBurnEpoch_];

// update total tokens claimed for updating bucket exchange rates tracker
if (rewardsClaimedInEpoch + updatedRewards_ >= rewardsCap) {
Expand All @@ -656,7 +661,7 @@ contract RewardsManager is IRewardsManager {
}

// accumulate the full amount of additional rewards
updateRewardsClaimed[curBurnEpoch] += updatedRewards_;
updateRewardsClaimed[curBurnEpoch_] += updatedRewards_;
} else {
// block.timestamp is greater than curBurnTime + UPDATE_PERIOD do not emit UpdateExchangeRates
return 0;
Expand Down Expand Up @@ -730,7 +735,7 @@ contract RewardsManager is IRewardsManager {

uint256 burnFactor = Maths.wmul(totalBurned_, bucketDeposit);

// calculate rewards earned for updating bucket exchange rate
// calculate rewards earned for updating bucket exchange rate
rewards_ = interestEarned_ == 0 ? 0 : Maths.wdiv(
Maths.wmul(
UPDATE_CLAIM_REWARD,
Expand All @@ -745,7 +750,7 @@ contract RewardsManager is IRewardsManager {
}
}

/**
/**
* @notice Utility function to unstake the position token.
* @dev Used by `stake` function to unstake and claim rewards.
* @dev Used by `emergencyUnstake` function to unstake without claiming rewards.
Expand All @@ -759,13 +764,15 @@ contract RewardsManager is IRewardsManager {

address ajnaPool = stakeInfo.ajnaPool;
uint256 rewardsEarned;
uint256 curBurnEpoch = IPool(ajnaPool).currentBurnEpoch();

// gracefully unstake, claim rewards if any
if (claimRewards_) {
rewardsEarned = _calculateAndClaimAllRewards(
stakeInfo,
tokenId_,
IPool(ajnaPool).currentBurnEpoch(),
curBurnEpoch,
curBurnEpoch,
false,
ajnaPool
);
Expand Down
9 changes: 5 additions & 4 deletions src/libraries/external/LenderActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,9 @@ library LenderActions {
PoolState calldata poolState_,
MoveQuoteParams calldata params_
) external returns (uint256 fromBucketRedeemedLP_, uint256 toBucketLP_, uint256 movedAmount_, uint256 lup_) {
if (params_.maxAmountToMove == 0)
revert InvalidAmount();
if (params_.fromIndex == params_.toIndex)
revert MoveToSameIndex();
if (params_.maxAmountToMove != 0 && params_.maxAmountToMove < poolState_.quoteTokenScale)
if (params_.maxAmountToMove < poolState_.quoteTokenScale)
revert DustAmountNotExceeded();
if (params_.toIndex == 0 || params_.toIndex > MAX_FENWICK_INDEX)
revert InvalidIndex();
Expand Down Expand Up @@ -814,6 +812,9 @@ library LenderActions {
unscaledRemovedAmount = unscaledDepositAvailable;
}

scaledDepositAvailable -= removedAmount_;
if (scaledDepositAvailable != 0 && scaledDepositAvailable < params_.dustLimit) revert DustAmountNotExceeded();

unscaledRemaining_ = unscaledDepositAvailable - unscaledRemovedAmount;

// revert if (due to rounding) required LP is 0
Expand All @@ -824,4 +825,4 @@ library LenderActions {
// update FenwickTree
Deposits.unscaledRemove(deposits_, params_.index, unscaledRemovedAmount);
}
}
}
23 changes: 22 additions & 1 deletion tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,16 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents {
_pool.removeQuoteToken(amount, index);
}

function _assertRemoveQuoteDustRevert(
address from,
uint256 amount,
uint256 index
) internal {
changePrank(from);
vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector);
_pool.removeQuoteToken(amount, index);
}

function _assertBorrowAuctionActiveRevert(
address from,
uint256 amount,
Expand Down Expand Up @@ -800,6 +810,17 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents {
ERC20Pool(address(_pool)).moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max);
}

function _assertMoveQuoteDustRevert(
address from,
uint256 amount,
uint256 toIndex,
uint256 fromIndex
) internal {
changePrank(from);
vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector);
_pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max);
}

}

abstract contract ERC20HelperContract is ERC20DSTestPlus {
Expand Down Expand Up @@ -896,4 +917,4 @@ abstract contract ERC20FuzzyHelperContract is ERC20DSTestPlus {
_collateral.approve(address(_pool), type(uint256).max);
_quote.approve(address(_pool), type(uint256).max);
}
}
}
4 changes: 2 additions & 2 deletions tests/forge/unit/ERC20Pool/ERC20PoolInputValidation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ contract ERC20PoolBorrowTest is ERC20HelperContract {
}

function testValidateMoveQuoteTokenInput() external tearDown {
// revert on zero amount
vm.expectRevert(IPoolErrors.InvalidAmount.selector);
// revert on dust amount if amount is below quote token scale
vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector);
_pool.moveQuoteToken(0, 1, 2, block.timestamp + 1);
// revert on move to same index
vm.expectRevert(IPoolErrors.MoveToSameIndex.selector);
Expand Down
Loading

0 comments on commit 3bf501a

Please sign in to comment.