Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hold received SP yield when total deposited amount is low #419

Merged
merged 10 commits into from
Sep 13, 2024
6 changes: 4 additions & 2 deletions contracts/src/ActivePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,11 @@ contract ActivePool is IActivePool {
uint256 remainderToLPs = mintedAmount - spYield;

_boldToken.mint(address(interestRouter), remainderToLPs);
danielattilasimon marked this conversation as resolved.
Show resolved Hide resolved
_boldToken.mint(address(stabilityPool), spYield);

stabilityPool.triggerBoldRewards(spYield);
if (spYield > 0) {
_boldToken.mint(address(stabilityPool), spYield);
stabilityPool.triggerBoldRewards(spYield);
}
}

lastAggUpdateTime = block.timestamp;
Expand Down
1 change: 1 addition & 0 deletions contracts/src/Interfaces/IStabilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ interface IStabilityPool is ILiquityBase, IBoldRewardsReceiver {
function getTotalBoldDeposits() external view returns (uint256);

function getYieldGainsOwed() external view returns (uint256);
function getYieldGainsPending() external view returns (uint256);

/*
* Calculates the Coll gain earned by the deposit since its last snapshots were taken.
Expand Down
56 changes: 42 additions & 14 deletions contracts/src/StabilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {
// TODO: from the contract's perspective, this is a write-only variable. It is only ever read in tests, so it would
// be better to keep it outside the core contract.
uint256 internal yieldGainsOwed;
// Total remaining Bold yield gains (from Trove interest mints) held by SP, not yet paid out to depositors,
// and not accounted for because they were received when the total deposits were too small
uint256 internal yieldGainsPending;

// --- Data structures ---

Expand Down Expand Up @@ -227,6 +230,10 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {
return yieldGainsOwed;
}

function getYieldGainsPending() external view override returns (uint256) {
return yieldGainsPending;
}

// --- External Depositor Functions ---

/* provideToSP():
Expand Down Expand Up @@ -263,10 +270,16 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {

_updateDepositAndSnapshots(msg.sender, newDeposit, newStashedColl);
boldToken.sendToPool(msg.sender, address(this), _topUp);
_updateTotalBoldDeposits(_topUp + keptYieldGain, 0);
uint256 totalBoldDepositsCached = _updateTotalBoldDeposits(_topUp + keptYieldGain, 0);
_decreaseYieldGainsOwed(currentYieldGain);
_sendBoldtoDepositor(msg.sender, yieldGainToSend);
_sendCollGainToDepositor(collToSend);

// If there were pending yields and with the new deposit we are reaching the threshold, let’s move the yield to owed
uint256 yieldGainsPendingCached = yieldGainsPending;
if (yieldGainsPendingCached > 0 && totalBoldDepositsCached >= DECIMAL_PRECISION) {
_updateYieldRewardsSum(yieldGainsPendingCached, totalBoldDepositsCached);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must do the same in withdrawFromSP(). Reason: a depositor could have enough pending yield gains to put total deposits over the threshold of DECIMAL_PRECISION. Remember that yield gains are deposited if they're not claimed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see you already fixed it, thanks!

}

function _getYieldToKeepOrSend(uint256 _currentYieldGain, bool _doClaim) internal pure returns (uint256, uint256) {
Expand Down Expand Up @@ -319,9 +332,15 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {

_updateDepositAndSnapshots(msg.sender, newDeposit, newStashedColl);
_decreaseYieldGainsOwed(currentYieldGain);
_updateTotalBoldDeposits(keptYieldGain, boldToWithdraw);
uint256 totalBoldDepositsCached = _updateTotalBoldDeposits(keptYieldGain, boldToWithdraw);
_sendBoldtoDepositor(msg.sender, boldToWithdraw + yieldGainToSend);
_sendCollGainToDepositor(collToSend);

// If there were pending yields and with the new deposit we are reaching the threshold, let’s move the yield to owed
uint256 yieldGainsPendingCached = yieldGainsPending;
if (yieldGainsPendingCached > 0 && totalBoldDepositsCached >= DECIMAL_PRECISION) {
_updateYieldRewardsSum(yieldGainsPendingCached, totalBoldDepositsCached);
}
}

function _getNewStashedCollAndCollToSend(address _depositor, uint256 _currentCollGain, bool _doClaim)
Expand Down Expand Up @@ -358,20 +377,27 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {

function triggerBoldRewards(uint256 _boldYield) external {
_requireCallerIsActivePool();
assert(_boldYield > 0); // TODO: remove before deploying

uint256 totalBoldDepositsCached = totalBoldDeposits; // cached to save an SLOAD
/*
* When total deposits is 0, B is not updated. In this case, the BOLD issued can not be obtained by later
* depositors - it is missed out on, and remains in the balance of the SP.
*
*/
if (totalBoldDepositsCached == 0 || _boldYield == 0) {

// When total deposits is very small, B is not updated. In this case, the BOLD issued is hold
// until the total deposits reach 1 BOLD (remains in the balance of the SP).
if (totalBoldDepositsCached < DECIMAL_PRECISION) {
yieldGainsPending += _boldYield;
return;
}

yieldGainsOwed += _boldYield;
_updateYieldRewardsSum(yieldGainsPending + _boldYield, totalBoldDepositsCached);
}

uint256 yieldPerUnitStaked = _computeYieldPerUnitStaked(_boldYield, totalBoldDepositsCached);
function _updateYieldRewardsSum(uint256 _accumulatedYield, uint256 _totalBoldDeposits) internal {
assert(_accumulatedYield > 0); // TODO: remove before deploying

yieldGainsOwed += _accumulatedYield;
yieldGainsPending = 0;

uint256 yieldPerUnitStaked = _computeYieldPerUnitStaked(_accumulatedYield, _totalBoldDeposits);

uint256 marginalYieldGain = yieldPerUnitStaked * P;
epochToScaleToB[currentEpoch][currentScale] = epochToScaleToB[currentEpoch][currentScale] + marginalYieldGain;
Expand Down Expand Up @@ -535,11 +561,13 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {
emit StabilityPoolCollBalanceUpdated(newCollBalance);
}

function _updateTotalBoldDeposits(uint256 _depositIncrease, uint256 _depositDecrease) internal {
if (_depositIncrease == 0 && _depositDecrease == 0) return;
function _updateTotalBoldDeposits(uint256 _depositIncrease, uint256 _depositDecrease) internal returns (uint256) {
if (_depositIncrease == 0 && _depositDecrease == 0) return totalBoldDeposits;
uint256 newTotalBoldDeposits = totalBoldDeposits + _depositIncrease - _depositDecrease;
totalBoldDeposits = newTotalBoldDeposits;
emit StabilityPoolBoldBalanceUpdated(newTotalBoldDeposits);

return newTotalBoldDeposits;
}

function _decreaseYieldGainsOwed(uint256 _amount) internal {
Expand Down Expand Up @@ -584,11 +612,11 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {

Snapshots memory snapshots = depositSnapshots[_depositor];

uint256 pendingSPYield = activePool.calcPendingSPYield();
uint256 pendingSPYield = activePool.calcPendingSPYield() + yieldGainsPending;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there are pending yield gains, how are they awarded to depositors?

This function seems to calculate as if all pending gains were immediately awarded pro-rata, including pending yield gains.

But in reality I guess some pending yield gains might not be, right (depending on the SP size). We don't really know when they'll be rewarded. Still, it's fine as an estimate IMO.

Also, I guess and IIRC if there are pending yield gains and a depositor makes a large enough deposit, he receives all those gains, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, Rick, I missed this comment.
You are right, but I think it’s now fixed by the addition of totalBoldDeposits >= DECIMAL_PRECISION condition below. Isnt’t it?

uint256 firstPortionPending;
uint256 secondPortionPending;

if (pendingSPYield > 0 && snapshots.epoch == currentEpoch) {
if (pendingSPYield > 0 && snapshots.epoch == currentEpoch && totalBoldDeposits >= DECIMAL_PRECISION) {
uint256 yieldNumerator = pendingSPYield * DECIMAL_PRECISION + lastYieldError;
uint256 yieldPerUnitStaked = yieldNumerator / totalBoldDeposits;
uint256 marginalYieldGain = yieldPerUnitStaked * P;
Expand Down
Loading