Skip to content

Commit

Permalink
allow resetStakes when either rate is unsafe (#1199)
Browse files Browse the repository at this point in the history
  • Loading branch information
tbrent committed Sep 10, 2024
1 parent 95231c0 commit 36ab2de
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 10 deletions.
11 changes: 9 additions & 2 deletions contracts/p0/StRSR.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable {
// stake rate under/over which governance can reset all stakes
uint192 private constant MAX_SAFE_STAKE_RATE = 1e6 * FIX_ONE; // 1e6
uint192 private constant MIN_SAFE_STAKE_RATE = uint192(1e12); // 1e-6
uint192 private constant MAX_SAFE_DRAFT_RATE = 1e6 * FIX_ONE; // 1e6
uint192 private constant MIN_SAFE_DRAFT_RATE = uint192(1e12); // 1e-6

// Withdrawal Leak
uint192 private leaked; // {1} stake fraction that has withdrawn without a refresh
Expand Down Expand Up @@ -386,10 +388,15 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable {
/// @custom:governance
/// Reset all stakes and advance era
function resetStakes() external governance {
uint256 rsrDrafts = rsrBeingWithdrawn();
uint192 draftRate = rsrDrafts > 0 ? divuu(stakeBeingWithdrawn(), rsrDrafts) : FIX_ONE;
uint192 stakeRate = divuu(totalStaked, rsrBacking);
require(
stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE,
"rate still safe"
draftRate <= MIN_SAFE_DRAFT_RATE ||
draftRate >= MAX_SAFE_DRAFT_RATE ||
stakeRate <= MIN_SAFE_STAKE_RATE ||
stakeRate >= MAX_SAFE_STAKE_RATE,
"rates still safe"
);

bankruptStakers();
Expand Down
16 changes: 12 additions & 4 deletions contracts/p1/StRSR.sol
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
// stake rate under/over which governance can reset all stakes
uint192 private constant MAX_SAFE_STAKE_RATE = 1e6 * FIX_ONE; // 1e6 D18{qStRSR/qRSR}
uint192 private constant MIN_SAFE_STAKE_RATE = uint192(1e12); // 1e-6 D18{qStRSR/qRSR}
uint192 private constant MAX_SAFE_DRAFT_RATE = 1e6 * FIX_ONE; // 1e6 D18{qStRSR/qRSR}
uint192 private constant MIN_SAFE_DRAFT_RATE = uint192(1e12); // 1e-6 D18{qStRSR/qRSR}

// ======================

Expand Down Expand Up @@ -478,19 +480,25 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab

/// @custom:governance
/// Reset all stakes and advance era
/// @notice This function is only callable when the stake rate is unsafe.
/// The stake rate is unsafe when it is either too high or too low.
/// @notice This function is only callable when the stake or draft rates are unsafe.
/// The stake/draft rate is unsafe when it is either too high or too low.
/// There is the possibility of the rate reaching the borderline of being unsafe,
/// where users won't stake in fear that a reset might be executed.
/// A user may also grief this situation by staking enough RSR to vote against any reset.
/// This standoff will continue until enough RSR is staked and a reset is executed.
/// There is currently no good and easy way to mitigate the possibility of this situation,
/// and the risk of it occurring is low enough that it is not worth the effort to mitigate.
/// @notice Governance must monitor the draftRate! After multiple seizures it is possible
/// for it to drift near the unsafe bounds. Even if stakes are still safe at this point,
/// resetStakes() should be called by governance anyway.
function resetStakes() external {
_requireGovernanceOnly();
require(
stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE,
"rate still safe"
draftRate <= MIN_SAFE_DRAFT_RATE ||
draftRate >= MAX_SAFE_DRAFT_RATE ||
stakeRate <= MIN_SAFE_STAKE_RATE ||
stakeRate >= MAX_SAFE_STAKE_RATE,
"rates still safe"
);

beginEra();
Expand Down
8 changes: 4 additions & 4 deletions test/ZZStRSR.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2226,7 +2226,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => {
expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt)

// Cannot reset stakes with this rate
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe')
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rates still safe')

// Seize small portion of RSR to increase stake rate - still safe
await whileImpersonating(backingManager.address, async (signer) => {
Expand All @@ -2240,7 +2240,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => {
expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt)

// Attempt to reset stakes, still not possible
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe')
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rates still safe')

// New Seizure - rate will be unsafe
const rsrRemaining = stakeAmt.sub(seizeAmt)
Expand Down Expand Up @@ -2278,7 +2278,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => {
expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt)

// Cannot reset stakes with this rate
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe')
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rates still safe')

// Add RSR to decrease stake rate - still safe
await rsr.connect(owner).transfer(stRSR.address, addAmt1)
Expand All @@ -2300,7 +2300,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => {
expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt)

// Attempt to reset stakes, still not possible
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe')
await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rates still safe')

// Add a large amount of funds - rate will be unsafe
await rsr.connect(owner).mint(owner.address, addAmt2)
Expand Down

0 comments on commit 36ab2de

Please sign in to comment.