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

Remove reliance on stakeRate #1125

Closed
wants to merge 11 commits into from
Closed
83 changes: 39 additions & 44 deletions contracts/p1/StRSR.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import "./mixins/Component.sol";

// solhint-disable max-states-count

/*
/**
* @title StRSRP1
* @notice StRSR is an ERC20 token contract that allows people to stake their RSR as
* over-collateralization behind an RToken. As compensation stakers receive a share of revenues
Expand Down Expand Up @@ -64,6 +64,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
uint256 private totalStakes; // Total of all stakes {qStRSR}
uint256 private stakeRSR; // Amount of RSR backing all stakes {qRSR}
uint192 private stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR}
// DEPRECATED in 3.4.0 in favor of totalStakes / stakeRSR

uint192 private constant MAX_STAKE_RATE = 1e9 * FIX_ONE; // 1e9 D18{qStRSR/qRSR}

Expand Down Expand Up @@ -111,11 +112,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab

// ==== Invariants ====
// [total-stakes]: totalStakes == sum(bal[acct] for acct in bal)
// [max-stake-rate]: 0 < stakeRate <= MAX_STAKE_RATE
// [stake-rate]: if totalStakes == 0, then stakeRSR == 0 and stakeRate == FIX_ONE
// else, stakeRSR * stakeRate >= totalStakes * 1e18
// (ie, stakeRSR covers totalStakes at stakeRate)
//
// [max-stake-rate]: 0 < divuu(totalStakes, stakeRSR) <= MAX_STAKE_RATE
// [total-drafts]: totalDrafts == sum(draftSum(draft[acct]) for acct in draft)
// [max-draft-rate]: 0 < draftRate <= MAX_DRAFT_RATE
// [draft-rate]: if totalDrafts == 0, then draftRSR == 0 and draftRate == FIX_ONE
Expand Down Expand Up @@ -218,9 +215,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
//
// effects:
// stakeRSR' = stakeRSR + rsrAmount
// totalStakes' = stakeRSR' * stakeRate / 1e18 (as required by invariant)
// totalStakes' = stakeRSR' * totalStakes / stakeRSR (as required by invariant)
// bal'[caller] = bal[caller] + (totalStakes' - totalStakes)
// stakeRate' = stakeRate (this could go without saying, but it's important!)
//
// actions:
// rsr.transferFrom(account, this, rsrAmount)
Expand All @@ -247,9 +243,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
// effects:
// totalStakes' = totalStakes - stakeAmount
// bal'[caller] = bal[caller] - stakeAmount
// stakeRSR' = ceil(totalStakes' * 1e18 / stakeRate)
// stakeRate' = stakeRate (no change)
//
// stakeRSR' = ceil(totalStakes' * stakeRSR / totalStakes)
// draftRSR' + stakeRSR' = draftRSR + stakeRSR
// draftRate' = draftRate (no change)
// totalDrafts' = floor(draftRSR' + draftRate' / 1e18)
Expand All @@ -266,13 +260,17 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
_payoutRewards();

// ==== Compute changes to stakes and RSR accounting
// rsrAmount: how many RSR to move from the stake pool to the draft pool
// pick rsrAmount as big as we can such that (newTotalStakes <= newStakeRSR * stakeRate)
uint256 prevTotalStakes = totalStakes;
_burn(account, stakeAmount);

// newStakeRSR: {qRSR} = D18 * {qStRSR} / D18{qStRSR/qRSR}
uint256 newStakeRSR = (FIX_ONE_256 * totalStakes + (stakeRate - 1)) / stakeRate;
uint256 rsrAmount = stakeRSR - newStakeRSR;
// rsrAmount: how many RSR to move from the stake pool to the draft pool
// pick rsrAmount so that (newStakeRSR >= stakeRSR * totalStakes / prevTotalStakes)

// {qRSR} = {qRSR} * {qStRSR} / {qStRSR}
uint256 newStakeRSR = prevTotalStakes != 0
? (stakeRSR * totalStakes + (prevTotalStakes - 1)) / prevTotalStakes
: stakeAmount;
uint256 rsrAmount = newStakeRSR < stakeRSR ? stakeRSR - newStakeRSR : 0;
stakeRSR = newStakeRSR;

// Create draft
Expand Down Expand Up @@ -321,8 +319,11 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
firstRemainingDraft[draftEra][account] = endId;

// ==== Compute RSR amount
// rsrAmount: how many RSR to withdraw from the draft pool
// pick rsrAmount as big as we can such that (newDraftRSR >= newTotalDrafts / draftRate)

uint256 newTotalDrafts = totalDrafts - draftAmount;
// newDraftRSR: {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR}
// {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR}
uint256 newDraftRSR = (newTotalDrafts * FIX_ONE_256 + (draftRate - 1)) / draftRate;
uint256 rsrAmount = draftRSR - newDraftRSR;

Expand Down Expand Up @@ -368,8 +369,11 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
firstRemainingDraft[draftEra][account] = endId;

// ==== Compute RSR amount
// rsrAmount: how many RSR to move from the draft pool to the stake pool
// pick rsrAmount as big as we can such that (newDraftRSR >= newTotalDrafts / draftRate)

uint256 newTotalDrafts = totalDrafts - draftAmount;
// newDraftRSR: {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR}
// {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR}
uint256 newDraftRSR = (newTotalDrafts * FIX_ONE_256 + (draftRate - 1)) / draftRate;
uint256 rsrAmount = draftRSR - newDraftRSR;

Expand Down Expand Up @@ -401,7 +405,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
// effects, in two phases. Phase 1: (from x to x')
// stakeRSR' = floor(stakeRSR * keepRatio)
// totalStakes' = totalStakes
// stakeRate' = ceil(totalStakes' * 1e18 / stakeRSR')
//
// draftRSR' = floor(draftRSR * keepRatio)
// totalDrafts' = totalDrafts
Expand All @@ -413,7 +416,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
// draftRSR'' = (draftRSR' <= MAX_DRAFT_RATE) ? draftRSR' : 0
// if draftRSR'' = 0, then totalDrafts'' = 0 and draftRate'' = FIX_ONE
// stakeRSR'' = (stakeRSR' <= MAX_STAKE_RATE) ? stakeRSR' : 0
// if stakeRSR'' = 0, then totalStakes'' = 0 and stakeRate'' = FIX_ONE
// if stakeRSR'' = 0, then totalStakes'' = 0
//
// actions:
// as (this), rsr.transfer(backingManager, seized)
Expand Down Expand Up @@ -442,12 +445,14 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
stakeRSR -= stakeRSRToTake;
seizedRSR = stakeRSRToTake;

// update stakeRate, possibly beginning a new stake era
if (stakeRSR != 0) {
// Downcast is safe: totalStakes is 1e38 at most so expression maximum value is 1e56
stakeRate = uint192((FIX_ONE_256 * totalStakes + (stakeRSR - 1)) / stakeRSR);
}
if (stakeRSR == 0 || stakeRate > MAX_STAKE_RATE) {
// Removed in 3.4.0
// // update stakeRate, possibly beginning a new stake era
// if (stakeRSR != 0) {
// // Downcast is safe: totalStakes is 1e38 at most so expression maximum value is 1e56
// stakeRate = uint192((FIX_ONE_256 * totalStakes + (stakeRSR - 1)) / stakeRSR);
// }

if (stakeRSR == 0 || (FIX_ONE * totalStakes) / stakeRSR > MAX_STAKE_RATE) {
seizedRSR += stakeRSR;
beginEra();
}
Expand Down Expand Up @@ -489,8 +494,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
/// and the risk of it occurring is low enough that it is not worth the effort to mitigate.
function resetStakes() external {
_requireGovernanceOnly();
uint256 _stakeRate = stakeRSR != 0 ? (FIX_ONE * totalStakes) / stakeRSR : FIX_ONE;
require(
stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE,
_stakeRate <= MIN_SAFE_STAKE_RATE || _stakeRate >= MAX_SAFE_STAKE_RATE,
"rate still safe"
);

Expand All @@ -500,8 +506,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab

/// @return D18{qRSR/qStRSR} The exchange rate between RSR and StRSR
function exchangeRate() public view returns (uint192) {
// D18{qRSR/qStRSR} = D18 * D18 / D18{qStRSR/qRSR}
return (FIX_SCALE_SQ + (stakeRate / 2)) / stakeRate; // ROUND method
// downcast is safe: expression is at most 1e18 * 1e29 / 1 = 1e47
// D18{qRSR/qStRSR} = D18 * {qRSR} / {qStRSR}
return uint192(totalStakes != 0 ? (FIX_ONE * stakeRSR) / totalStakes : FIX_ONE);
}

/// Return the maximum value of endId such that withdraw(endId) can immediately work
Expand Down Expand Up @@ -566,16 +573,13 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
/// @dev do this by effecting stakeRSR and payoutLastPaid as appropriate, given the current
/// value of rsrRewards()
/// @dev perhaps astonishingly, this _isn't_ a refresher

// let
// N = numPeriods; the number of whole rewardPeriods since the last payout
// payout = rsrRewards() * (1 - (1 - rewardRatio)^N) (see [strsr-payout-formula])
//
// effects:
// stakeRSR' = stakeRSR + payout
// rsrRewards'() = rsrRewards() - payout (implicit in the code, but true)
// stakeRate' = ceil(totalStakes' * 1e18 / stakeRSR') (because [stake-rate])
// unless totalStakes == 0 or stakeRSR == 0, in which case stakeRate' = FIX_ONE
// totalStakes' = totalStakes
//
// [strsr-payout-formula]:
Expand Down Expand Up @@ -613,15 +617,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
payoutLastPaid += numPeriods;
rsrRewardsAtLastPayout = rsrRewards();

// stakeRate else case: D18{qStRSR/qRSR} = {qStRSR} * D18 / {qRSR}
// downcast is safe: it's at most 1e38 * 1e18 = 1e56
// untestable:
// the second half of the OR comparison is untestable because of the invariant:
// if totalStakes == 0, then stakeRSR == 0
stakeRate = (stakeRSR == 0 || totalStakes == 0)
? FIX_ONE
: uint192((totalStakes * FIX_ONE_256 + (stakeRSR - 1)) / stakeRSR);

emit RewardsPaid(payout);
emit ExchangeRateSet(initRate, exchangeRate());
}
Expand All @@ -644,7 +639,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
// draftAmount: how many drafts to create and assign to the user
// pick draftAmount as big as we can such that (newTotalDrafts <= newDraftRSR * draftRate)
draftRSR += rsrAmount;
// newTotalDrafts: {qDrafts} = D18{qDrafts/qRSR} * {qRSR} / D18
// {qDrafts} = D18{qDrafts/qRSR} * {qRSR} / D18
uint256 newTotalDrafts = (draftRate * draftRSR) / FIX_ONE;
uint256 draftAmount = newTotalDrafts - totalDrafts;
totalDrafts = newTotalDrafts;
Expand Down Expand Up @@ -725,8 +720,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
// stakeAmount: how many stRSR the user shall receive.
// pick stakeAmount as big as we can such that (newTotalStakes <= newStakeRSR * stakeRate)
uint256 newStakeRSR = stakeRSR + rsrAmount;
// newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18
uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE;
// {qStRSR} = {qStRSR} * {qRSR} / {qRSR}
uint256 newTotalStakes = stakeRSR != 0 ? (totalStakes * newStakeRSR) / stakeRSR : rsrAmount;
uint256 stakeAmount = newTotalStakes - totalStakes;

// Transfer RSR from account to this contract
Expand Down
Loading