Skip to content

Commit

Permalink
Merge pull request #115 from lidofinance/feature/dg-config-sanity-checks
Browse files Browse the repository at this point in the history
DG config sanity checks
  • Loading branch information
rkolpakov authored Sep 11, 2024
2 parents 1b12b42 + 2414cfc commit ba63b4a
Show file tree
Hide file tree
Showing 21 changed files with 693 additions and 324 deletions.
7 changes: 5 additions & 2 deletions contracts/DualGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ import {ITiebreaker} from "./interfaces/ITiebreaker.sol";
import {IWithdrawalQueue} from "./interfaces/IWithdrawalQueue.sol";
import {IDualGovernance} from "./interfaces/IDualGovernance.sol";
import {IResealManager} from "./interfaces/IResealManager.sol";
import {IDualGovernanceConfigProvider} from "./interfaces/IDualGovernanceConfigProvider.sol";

import {Proposers} from "./libraries/Proposers.sol";
import {Tiebreaker} from "./libraries/Tiebreaker.sol";
import {ExternalCall} from "./libraries/ExternalCalls.sol";
import {DualGovernanceConfig} from "./libraries/DualGovernanceConfig.sol";
import {State, DualGovernanceStateMachine} from "./libraries/DualGovernanceStateMachine.sol";
import {IDualGovernanceConfigProvider} from "./DualGovernanceConfigProvider.sol";

import {Escrow} from "./Escrow.sol";

contract DualGovernance is IDualGovernance {
using Proposers for Proposers.Context;
using Tiebreaker for Tiebreaker.Context;
using DualGovernanceConfig for DualGovernanceConfig.Context;
using DualGovernanceStateMachine for DualGovernanceStateMachine.Context;

// ---
Expand Down Expand Up @@ -324,7 +326,8 @@ contract DualGovernance is IDualGovernance {
revert InvalidConfigProvider(newConfigProvider);
}

_configProvider = IDualGovernanceConfigProvider(newConfigProvider);
newConfigProvider.getDualGovernanceConfig().validate();
_configProvider = newConfigProvider;
emit ConfigProviderSet(newConfigProvider);
}

Expand Down
57 changes: 24 additions & 33 deletions contracts/DualGovernanceConfigProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,61 @@ import {IDualGovernanceConfigProvider} from "./interfaces/IDualGovernanceConfigP
import {DualGovernanceConfig} from "./libraries/DualGovernanceConfig.sol";

contract ImmutableDualGovernanceConfigProvider is IDualGovernanceConfigProvider {
using DualGovernanceConfig for DualGovernanceConfig.Context;

PercentD16 public immutable FIRST_SEAL_RAGE_QUIT_SUPPORT;
PercentD16 public immutable SECOND_SEAL_RAGE_QUIT_SUPPORT;

Duration public immutable MIN_ASSETS_LOCK_DURATION;
Duration public immutable DYNAMIC_TIMELOCK_MIN_DURATION;
Duration public immutable DYNAMIC_TIMELOCK_MAX_DURATION;

Duration public immutable VETO_SIGNALLING_MIN_DURATION;
Duration public immutable VETO_SIGNALLING_MAX_DURATION;
Duration public immutable VETO_SIGNALLING_MIN_ACTIVE_DURATION;
Duration public immutable VETO_SIGNALLING_DEACTIVATION_MAX_DURATION;

Duration public immutable VETO_COOLDOWN_DURATION;

Duration public immutable RAGE_QUIT_EXTENSION_DELAY;
Duration public immutable RAGE_QUIT_ETH_WITHDRAWALS_MIN_TIMELOCK;
uint256 public immutable RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_START_SEQ_NUMBER;

uint256 public immutable RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_A;
uint256 public immutable RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_B;
uint256 public immutable RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_C;
Duration public immutable RAGE_QUIT_EXTENSION_PERIOD_DURATION;
Duration public immutable RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY;
Duration public immutable RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY;
Duration public immutable RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH;

constructor(DualGovernanceConfig.Context memory dualGovernanceConfig) {
dualGovernanceConfig.validate();

FIRST_SEAL_RAGE_QUIT_SUPPORT = dualGovernanceConfig.firstSealRageQuitSupport;
SECOND_SEAL_RAGE_QUIT_SUPPORT = dualGovernanceConfig.secondSealRageQuitSupport;

MIN_ASSETS_LOCK_DURATION = dualGovernanceConfig.minAssetsLockDuration;
DYNAMIC_TIMELOCK_MIN_DURATION = dualGovernanceConfig.dynamicTimelockMinDuration;
DYNAMIC_TIMELOCK_MAX_DURATION = dualGovernanceConfig.dynamicTimelockMaxDuration;
VETO_SIGNALLING_MIN_DURATION = dualGovernanceConfig.vetoSignallingMinDuration;
VETO_SIGNALLING_MAX_DURATION = dualGovernanceConfig.vetoSignallingMaxDuration;

VETO_SIGNALLING_MIN_ACTIVE_DURATION = dualGovernanceConfig.vetoSignallingMinActiveDuration;
VETO_SIGNALLING_DEACTIVATION_MAX_DURATION = dualGovernanceConfig.vetoSignallingDeactivationMaxDuration;

VETO_COOLDOWN_DURATION = dualGovernanceConfig.vetoCooldownDuration;

RAGE_QUIT_EXTENSION_DELAY = dualGovernanceConfig.rageQuitExtensionDelay;
RAGE_QUIT_ETH_WITHDRAWALS_MIN_TIMELOCK = dualGovernanceConfig.rageQuitEthWithdrawalsMinTimelock;
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_START_SEQ_NUMBER =
dualGovernanceConfig.rageQuitEthWithdrawalsTimelockGrowthStartSeqNumber;

RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_A =
dualGovernanceConfig.rageQuitEthWithdrawalsTimelockGrowthCoeffs[0];
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_B =
dualGovernanceConfig.rageQuitEthWithdrawalsTimelockGrowthCoeffs[1];
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_C =
dualGovernanceConfig.rageQuitEthWithdrawalsTimelockGrowthCoeffs[2];
RAGE_QUIT_EXTENSION_PERIOD_DURATION = dualGovernanceConfig.rageQuitExtensionPeriodDuration;
RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY = dualGovernanceConfig.rageQuitEthWithdrawalsMinDelay;
RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY = dualGovernanceConfig.rageQuitEthWithdrawalsMaxDelay;
RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH = dualGovernanceConfig.rageQuitEthWithdrawalsDelayGrowth;
}

function getDualGovernanceConfig() external view returns (DualGovernanceConfig.Context memory config) {
config.firstSealRageQuitSupport = FIRST_SEAL_RAGE_QUIT_SUPPORT;
config.secondSealRageQuitSupport = SECOND_SEAL_RAGE_QUIT_SUPPORT;

config.minAssetsLockDuration = MIN_ASSETS_LOCK_DURATION;
config.dynamicTimelockMinDuration = DYNAMIC_TIMELOCK_MIN_DURATION;
config.dynamicTimelockMaxDuration = DYNAMIC_TIMELOCK_MAX_DURATION;
config.vetoSignallingMinDuration = VETO_SIGNALLING_MIN_DURATION;
config.vetoSignallingMaxDuration = VETO_SIGNALLING_MAX_DURATION;
config.vetoSignallingMinActiveDuration = VETO_SIGNALLING_MIN_ACTIVE_DURATION;
config.vetoSignallingDeactivationMaxDuration = VETO_SIGNALLING_DEACTIVATION_MAX_DURATION;

config.vetoCooldownDuration = VETO_COOLDOWN_DURATION;
config.rageQuitExtensionDelay = RAGE_QUIT_EXTENSION_DELAY;
config.rageQuitEthWithdrawalsMinTimelock = RAGE_QUIT_ETH_WITHDRAWALS_MIN_TIMELOCK;
config.rageQuitEthWithdrawalsTimelockGrowthStartSeqNumber =
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_START_SEQ_NUMBER;
config.rageQuitEthWithdrawalsTimelockGrowthCoeffs = [
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_A,
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_B,
RAGE_QUIT_ETH_WITHDRAWALS_TIMELOCK_GROWTH_COEFF_C
];

config.rageQuitExtensionPeriodDuration = RAGE_QUIT_EXTENSION_PERIOD_DURATION;
config.rageQuitEthWithdrawalsMinDelay = RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY;
config.rageQuitEthWithdrawalsMaxDelay = RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY;
config.rageQuitEthWithdrawalsDelayGrowth = RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH;
}
}
24 changes: 12 additions & 12 deletions contracts/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ contract Escrow is IEscrow {
// Start rage quit
// ---

function startRageQuit(Duration rageQuitExtensionDelay, Duration rageQuitWithdrawalsTimelock) external {
function startRageQuit(Duration rageQuitExtensionPeriodDuration, Duration rageQuitEthWithdrawalsDelay) external {
_checkCallerIsDualGovernance();
_escrowState.startRageQuit(rageQuitExtensionDelay, rageQuitWithdrawalsTimelock);
_escrowState.startRageQuit(rageQuitExtensionPeriodDuration, rageQuitEthWithdrawalsDelay);
_batchesQueue.open(WITHDRAWAL_QUEUE.getLastRequestId());
}

Expand Down Expand Up @@ -319,13 +319,13 @@ contract Escrow is IEscrow {
// Start rage quit extension delay
// ---

function startRageQuitExtensionDelay() external {
function startRageQuitExtensionPeriod() external {
if (!_batchesQueue.isClosed()) {
revert BatchesQueueIsNotClosed();
}

/// @dev This check is primarily required when only unstETH NFTs are locked in the Escrow
/// and there are no WithdrawalBatches. In this scenario, the RageQuitExtensionDelay can only begin
/// and there are no WithdrawalBatches. In this scenario, the RageQuitExtensionPeriod can only begin
/// when the last locked unstETH id is finalized in the WithdrawalQueue.
/// When the WithdrawalBatchesQueue is not empty, this invariant is maintained by the following:
/// - Any locked unstETH during the VetoSignalling phase has an id less than any unstETH NFT created
Expand All @@ -340,7 +340,7 @@ contract Escrow is IEscrow {
revert UnclaimedBatches();
}

_escrowState.startRageQuitExtensionDelay();
_escrowState.startRageQuitExtensionPeriod();
}

// ---
Expand Down Expand Up @@ -374,7 +374,7 @@ contract Escrow is IEscrow {

function withdrawETH() external {
_escrowState.checkRageQuitEscrow();
_escrowState.checkWithdrawalsTimelockPassed();
_escrowState.checkEthWithdrawalsDelayPassed();
ETHValue ethToWithdraw = _accounting.accountStETHSharesWithdraw(msg.sender);
ethToWithdraw.sendTo(payable(msg.sender));
}
Expand All @@ -384,7 +384,7 @@ contract Escrow is IEscrow {
revert EmptyUnstETHIds();
}
_escrowState.checkRageQuitEscrow();
_escrowState.checkWithdrawalsTimelockPassed();
_escrowState.checkEthWithdrawalsDelayPassed();
ETHValue ethToWithdraw = _accounting.accountUnstETHWithdraw(msg.sender, unstETHIds);
ethToWithdraw.sendTo(payable(msg.sender));
}
Expand Down Expand Up @@ -424,12 +424,12 @@ contract Escrow is IEscrow {
return _batchesQueue.isClosed();
}

function isRageQuitExtensionDelayStarted() external view returns (bool) {
return _escrowState.isRageQuitExtensionDelayStarted();
function isRageQuitExtensionPeriodStarted() external view returns (bool) {
return _escrowState.isRageQuitExtensionPeriodStarted();
}

function getRageQuitExtensionDelayStartedAt() external view returns (Timestamp) {
return _escrowState.rageQuitExtensionDelayStartedAt;
function getRageQuitExtensionPeriodStartedAt() external view returns (Timestamp) {
return _escrowState.rageQuitExtensionPeriodStartedAt;
}

function getRageQuitSupport() external view returns (PercentD16) {
Expand All @@ -446,7 +446,7 @@ contract Escrow is IEscrow {
}

function isRageQuitFinalized() external view returns (bool) {
return _escrowState.isRageQuitEscrow() && _escrowState.isRageQuitExtensionDelayPassed();
return _escrowState.isRageQuitEscrow() && _escrowState.isRageQuitExtensionPeriodPassed();
}

// ---
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IDualGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface IDualGovernance is IGovernance, ITiebreaker {
Timestamp vetoSignallingReactivationTime;
Timestamp normalOrVetoCooldownExitedAt;
uint256 rageQuitRound;
Duration dynamicDelay;
Duration vetoSignallingDuration;
}

function activateNextState() external;
Expand Down
2 changes: 1 addition & 1 deletion contracts/libraries/AssetsAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ library AssetsAccounting {
error InvalidSharesValue(SharesValue value);
error InvalidUnstETHStatus(uint256 unstETHId, UnstETHRecordStatus status);
error InvalidUnstETHHolder(uint256 unstETHId, address actual, address expected);
error MinAssetsLockDurationNotPassed(Timestamp unlockTimelockExpiresAt);
error MinAssetsLockDurationNotPassed(Timestamp lockDurationExpiresAt);
error InvalidClaimableAmount(uint256 unstETHId, ETHValue expected, ETHValue actual);

// ---
Expand Down
95 changes: 59 additions & 36 deletions contracts/libraries/DualGovernanceConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,54 @@ import {Duration, Durations} from "../types/Duration.sol";
import {Timestamp, Timestamps} from "../types/Timestamp.sol";

library DualGovernanceConfig {
// ---
// Errors
// ---

error InvalidSecondSealRageSupport(PercentD16 secondSealRageQuitSupport);
error InvalidRageQuitSupportRange(PercentD16 firstSealRageQuitSupport, PercentD16 secondSealRageQuitSupport);
error InvalidRageQuitEthWithdrawalsDelayRange(
Duration rageQuitEthWithdrawalsMinDelay, Duration rageQuitEthWithdrawalsMaxDelay
);
error InvalidVetoSignallingDurationRange(Duration vetoSignallingMinDuration, Duration vetoSignallingMaxDuration);

// ---
// Data Types
// ---

struct Context {
PercentD16 firstSealRageQuitSupport;
PercentD16 secondSealRageQuitSupport;
//
Duration minAssetsLockDuration;
Duration dynamicTimelockMinDuration;
Duration dynamicTimelockMaxDuration;
//
Duration vetoSignallingMinDuration;
Duration vetoSignallingMaxDuration;
Duration vetoSignallingMinActiveDuration;
Duration vetoSignallingDeactivationMaxDuration;
//
Duration vetoCooldownDuration;
Duration rageQuitExtensionDelay;
Duration rageQuitEthWithdrawalsMinTimelock;
uint256 rageQuitEthWithdrawalsTimelockGrowthStartSeqNumber;
uint256[3] rageQuitEthWithdrawalsTimelockGrowthCoeffs;
//
Duration rageQuitExtensionPeriodDuration;
Duration rageQuitEthWithdrawalsMinDelay;
Duration rageQuitEthWithdrawalsMaxDelay;
Duration rageQuitEthWithdrawalsDelayGrowth;
}

function validate(Context memory self) internal pure {
if (self.firstSealRageQuitSupport >= self.secondSealRageQuitSupport) {
revert InvalidRageQuitSupportRange(self.firstSealRageQuitSupport, self.secondSealRageQuitSupport);
}

if (self.vetoSignallingMinDuration >= self.vetoSignallingMaxDuration) {
revert InvalidVetoSignallingDurationRange(self.vetoSignallingMinDuration, self.vetoSignallingMaxDuration);
}

if (self.rageQuitEthWithdrawalsMinDelay > self.rageQuitEthWithdrawalsMaxDelay) {
revert InvalidRageQuitEthWithdrawalsDelayRange(
self.rageQuitEthWithdrawalsMinDelay, self.rageQuitEthWithdrawalsMaxDelay
);
}
}

function isFirstSealRageQuitSupportCrossed(
Expand All @@ -35,26 +70,19 @@ library DualGovernanceConfig {
return rageQuitSupport > self.secondSealRageQuitSupport;
}

function isDynamicTimelockMaxDurationPassed(
Context memory self,
Timestamp vetoSignallingActivatedAt
) internal view returns (bool) {
return Timestamps.now() > self.dynamicTimelockMaxDuration.addTo(vetoSignallingActivatedAt);
}

function isDynamicTimelockDurationPassed(
function isVetoSignallingDurationPassed(
Context memory self,
Timestamp vetoSignallingActivatedAt,
PercentD16 rageQuitSupport
) internal view returns (bool) {
return Timestamps.now() > calcDynamicDelayDuration(self, rageQuitSupport).addTo(vetoSignallingActivatedAt);
return Timestamps.now() > calcVetoSignallingDuration(self, rageQuitSupport).addTo(vetoSignallingActivatedAt);
}

function isVetoSignallingReactivationDurationPassed(
Context memory self,
Timestamp vetoSignallingReactivationTime
Timestamp vetoSignallingReactivatedAt
) internal view returns (bool) {
return Timestamps.now() > self.vetoSignallingMinActiveDuration.addTo(vetoSignallingReactivationTime);
return Timestamps.now() > self.vetoSignallingMinActiveDuration.addTo(vetoSignallingReactivatedAt);
}

function isVetoSignallingDeactivationMaxDurationPassed(
Expand All @@ -71,46 +99,41 @@ library DualGovernanceConfig {
return Timestamps.now() > self.vetoCooldownDuration.addTo(vetoCooldownEnteredAt);
}

function calcDynamicDelayDuration(
function calcVetoSignallingDuration(
Context memory self,
PercentD16 rageQuitSupport
) internal pure returns (Duration duration_) {
) internal pure returns (Duration) {
PercentD16 firstSealRageQuitSupport = self.firstSealRageQuitSupport;
PercentD16 secondSealRageQuitSupport = self.secondSealRageQuitSupport;

Duration dynamicTimelockMinDuration = self.dynamicTimelockMinDuration;
Duration dynamicTimelockMaxDuration = self.dynamicTimelockMaxDuration;
Duration vetoSignallingMinDuration = self.vetoSignallingMinDuration;
Duration vetoSignallingMaxDuration = self.vetoSignallingMaxDuration;

if (rageQuitSupport <= firstSealRageQuitSupport) {
return Durations.ZERO;
}

if (rageQuitSupport >= secondSealRageQuitSupport) {
return dynamicTimelockMaxDuration;
return vetoSignallingMaxDuration;
}

duration_ = dynamicTimelockMinDuration
return vetoSignallingMinDuration
+ Durations.from(
PercentD16.unwrap(rageQuitSupport - firstSealRageQuitSupport)
* (dynamicTimelockMaxDuration - dynamicTimelockMinDuration).toSeconds()
* (vetoSignallingMaxDuration - vetoSignallingMinDuration).toSeconds()
/ PercentD16.unwrap(secondSealRageQuitSupport - firstSealRageQuitSupport)
);
}

function calcRageQuitWithdrawalsTimelock(
function calcRageQuitWithdrawalsDelay(
Context memory self,
uint256 rageQuitRound
) internal pure returns (Duration) {
if (rageQuitRound < self.rageQuitEthWithdrawalsTimelockGrowthStartSeqNumber) {
return self.rageQuitEthWithdrawalsMinTimelock;
}
return self.rageQuitEthWithdrawalsMinTimelock
+ Durations.from(
(
self.rageQuitEthWithdrawalsTimelockGrowthCoeffs[0] * rageQuitRound * rageQuitRound
+ self.rageQuitEthWithdrawalsTimelockGrowthCoeffs[1] * rageQuitRound
+ self.rageQuitEthWithdrawalsTimelockGrowthCoeffs[2]
) / 10 ** 18
); // TODO: rewrite in a prettier way
return Durations.min(
self.rageQuitEthWithdrawalsMinDelay.plusSeconds(
rageQuitRound * self.rageQuitEthWithdrawalsDelayGrowth.toSeconds()
),
self.rageQuitEthWithdrawalsMaxDelay
);
}
}
Loading

0 comments on commit ba63b4a

Please sign in to comment.