From 36ab2decc7cac66c51ac620351da9c0983b495df Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 10 Sep 2024 14:58:16 -0400 Subject: [PATCH] allow resetStakes when either rate is unsafe (#1199) --- contracts/p0/StRSR.sol | 11 +++++++++-- contracts/p1/StRSR.sol | 16 ++++++++++++---- test/ZZStRSR.test.ts | 8 ++++---- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index 4a3c0a1da..bd89e0890 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -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 @@ -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(); diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index d251d1c0a..190aeb625 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -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} // ====================== @@ -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(); diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 06fba12cc..ea7e7356c 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -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) => { @@ -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) @@ -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) @@ -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)