Skip to content

Commit

Permalink
Merge pull request lidofinance#12 from lidofinance/feat/gov-state
Browse files Browse the repository at this point in the history
Governance state improvements
  • Loading branch information
Psirex authored Feb 15, 2024
2 parents 545a528 + 14c79c3 commit 9077412
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 40 deletions.
16 changes: 11 additions & 5 deletions contracts/Configuration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@ contract Configuration {
uint256 internal constant PERCENT = 10 ** 16;

address public immutable adminProposer;

uint256 public immutable minProposalExecutionTimelock = 3 * DAY;
uint256 public immutable firstSealThreshold = 3 * PERCENT;
uint256 public immutable secondSealThreshold = 15 * PERCENT;
uint256 public immutable signallingMinDuration = 3 * DAY;
uint256 public immutable signallingMaxDuration = 30 * DAY;

uint256 public immutable signallingDeactivationDuration = 5 * DAY;
uint256 public immutable signallingCooldownDuration = 4 * DAY;
uint256 public immutable rageQuitAccumulationDuration = 15 * DAY;
uint256 public immutable rageQuitEthWithdrawalTimelock = 30 * DAY;

uint256 public immutable firstSealThreshold = 3 * PERCENT;
uint256 public immutable secondSealThreshold = 15 * PERCENT;
uint256 public immutable signallingMinDuration = 3 days;
uint256 public immutable signallingMaxDuration = 30 days;

constructor(address adminProposer_) {
adminProposer = adminProposer_;
}

function getSignallingThresholdData() external view returns (uint256, uint256, uint256, uint256) {
return (firstSealThreshold, secondSealThreshold, signallingMinDuration, signallingMaxDuration);
}
}
55 changes: 21 additions & 34 deletions contracts/GovernanceState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Configuration} from "./Configuration.sol";
import {Escrow} from "./Escrow.sol";

import "forge-std/console.sol";

interface IERC20 {
function totalSupply() external view returns (uint256);
}

contract GovernanceState {
error Unauthorized();

Expand All @@ -17,7 +23,6 @@ contract GovernanceState {
VetoSignalling,
VetoSignallingDeactivation,
VetoCooldown,
RageQuitAccumulation,
RageQuit
}

Expand All @@ -35,6 +40,8 @@ contract GovernanceState {

uint256 internal _proposalsKilledUntil;

uint16 internal constant MAX_BASIS_POINTS = 10_000;

constructor(address config, address governance, address escrowImpl) {
CONFIG = Configuration(config);
GOVERNANCE = governance;
Expand All @@ -43,15 +50,15 @@ contract GovernanceState {
_stateEnteredAt = _getTime();
}

function currentState() external returns (State) {
function currentState() external view returns (State) {
return _state;
}

function signallingEscrow() external returns (address) {
function signallingEscrow() external view returns (address) {
return address(_signallingEscrow);
}

function rageQuitEscrow() external returns (address) {
function rageQuitEscrow() external view returns (address) {
return address(_rageQuitEscrow);
}

Expand Down Expand Up @@ -91,8 +98,6 @@ contract GovernanceState {
_activateNextStateFromVetoSignallingDeactivation();
} else if (state == State.VetoCooldown) {
_activateNextStateFromVetoCooldown();
} else if (state == State.RageQuitAccumulation) {
_activateNextStateFromRageQuitAccumulation();
} else if (state == State.RageQuit) {
_activateNextStateFromRageQuit();
} else {
Expand Down Expand Up @@ -178,7 +183,7 @@ contract GovernanceState {
}

if (rageQuitSupport >= CONFIG.secondSealThreshold()) {
_transitionVetoSignallingToRageQuitAccumulation();
_activateRageQuit();
} else {
_enterVetoSignallingDeactivationSubState();
}
Expand All @@ -199,30 +204,26 @@ contract GovernanceState {

if (currentSignallingDuration >= targetSignallingDuration) {
if (rageQuitSupport >= CONFIG.secondSealThreshold()) {
_transitionVetoSignallingToRageQuitAccumulation();
_activateRageQuit();
}
} else if (totalSupport >= CONFIG.firstSealThreshold()) {
_exitVetoSignallingDeactivationSubState();
}
}

function _calcVetoSignallingTargetDuration(uint256 totalSupport) internal view returns (uint256) {
uint256 firstSealThreshold = CONFIG.firstSealThreshold();
uint256 secondSealThreshold = CONFIG.secondSealThreshold();
function _calcVetoSignallingTargetDuration(uint256 totalSupport) internal view returns (uint256 duration) {
(uint256 firstSealThreshold, uint256 secondSealThreshold, uint256 minDuration, uint256 maxDuration) =
CONFIG.getSignallingThresholdData();

if (totalSupport < firstSealThreshold) {
return 0;
}

uint256 maxDuration = CONFIG.signallingMaxDuration();

if (totalSupport >= secondSealThreshold) {
return maxDuration;
}

uint256 minDuration = CONFIG.signallingMinDuration();

return minDuration
duration = minDuration
+ (totalSupport - firstSealThreshold) * (maxDuration - minDuration) / (secondSealThreshold - firstSealThreshold);
}

Expand Down Expand Up @@ -253,35 +254,21 @@ contract GovernanceState {
}
}

//
// State: RageQuitAccumulation
//
function _transitionVetoSignallingToRageQuitAccumulation() internal {
_setState(State.RageQuitAccumulation);
// _signallingEscrow.startRageQuitAccumulation();
_rageQuitEscrow = _signallingEscrow;
_deployNewSignallingEscrow();
}

function _activateNextStateFromRageQuitAccumulation() internal {
uint256 accumulationDuration = _getTime() - _stateEnteredAt;
if (accumulationDuration >= CONFIG.rageQuitAccumulationDuration()) {
_transitionRageQuitAccumulationToRageQuit();
}
}

//
// State: RageQuit
//
function _transitionRageQuitAccumulationToRageQuit() internal {
function _activateRageQuit() internal {
_setState(State.RageQuit);
_rageQuitEscrow = _signallingEscrow;
_rageQuitEscrow.startRageQuit();
_deployNewSignallingEscrow();
}

function _activateNextStateFromRageQuit() internal {
if (!_rageQuitEscrow.isRageQuitFinalized()) {
return;
}

if (_isFirstThresholdReached()) {
_transitionRageQuitToVetoSignalling();
} else {
Expand Down
190 changes: 190 additions & 0 deletions test/scenario/gov-state-transitions.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "forge-std/Test.sol";
import "forge-std/console.sol";

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

import {DualGovernanceSetup} from "./setup.sol";
import {DualGovernanceUtils} from "./happy-path.t.sol";
import {TestHelpers} from './escrow.t.sol';
import "../utils/utils.sol";

contract GovernanceStateTransitions is TestHelpers {
DualGovernance internal dualGov;

address internal ldoWhale;
address internal stEthWhale;

function setUp() external {
Utils.selectFork();
Utils.removeLidoStakingLimit();

ldoWhale = makeAddr("ldo_whale");
Utils.setupLdoWhale(ldoWhale);

stEthWhale = makeAddr("steth_whale");
Utils.setupStEthWhale(stEthWhale);

uint256 timelockDuration = 0;
address timelockEmergencyMultisig = address(0);
uint256 timelockEmergencyMultisigActiveFor = 0;

DualGovernanceSetup.Deployed memory deployed = deployDG(
ST_ETH,
WST_ETH,
WITHDRAWAL_QUEUE,
BURNER,
DAO_VOTING,
timelockDuration,
timelockEmergencyMultisig,
timelockEmergencyMultisigActiveFor
);

dualGov = deployed.dualGov;
}

function test_signalling_state_min_duration() public {
assertEq(dualGov.currentState(), GovernanceState.State.Normal);

updateVetoSupportInPercent(3 * 10 ** 16);
updateVetoSupport(1);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

uint256 signallingDuration = dualGov.CONFIG().signallingMinDuration();

vm.warp(block.timestamp + (signallingDuration / 2));
updateVetoSupport(1);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

vm.warp(block.timestamp + signallingDuration / 2);
updateVetoSupport(1);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignallingDeactivation);
}

function test_signalling_state_max_duration() public {
assertEq(dualGov.currentState(), GovernanceState.State.Normal);

updateVetoSupportInPercent(15 * 10 ** 16);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

uint256 signallingDuration = dualGov.CONFIG().signallingMaxDuration();

vm.warp(block.timestamp + (signallingDuration / 2));
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

vm.warp(block.timestamp + signallingDuration / 2 + 1000);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.RageQuit);
}

function test_signalling_to_normal() public {
assertEq(dualGov.currentState(), GovernanceState.State.Normal);

updateVetoSupportInPercent(3 * 10 ** 16 + 1);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

uint256 signallingDuration = dualGov.CONFIG().signallingMinDuration();

vm.warp(block.timestamp + signallingDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignallingDeactivation);

uint256 signallingDeactivationDuration = dualGov.CONFIG().signallingDeactivationDuration();

vm.warp(block.timestamp + signallingDeactivationDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.VetoCooldown);

uint256 signallingCooldownDuration = dualGov.CONFIG().signallingCooldownDuration();

Escrow signallingEscrow = Escrow(payable(dualGov.signallingEscrow()));
vm.prank(stEthWhale);
signallingEscrow.unlockStEth();

vm.warp(block.timestamp + signallingCooldownDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.Normal);
}

function test_signalling_non_stop() public {
assertEq(dualGov.currentState(), GovernanceState.State.Normal);

updateVetoSupportInPercent(3 * 10 ** 16 + 1);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

uint256 signallingDuration = dualGov.CONFIG().signallingMinDuration();

vm.warp(block.timestamp + signallingDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignallingDeactivation);

uint256 signallingDeactivationDuration = dualGov.CONFIG().signallingDeactivationDuration();

vm.warp(block.timestamp + signallingDeactivationDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.VetoCooldown);

uint256 signallingCooldownDuration = dualGov.CONFIG().signallingCooldownDuration();

vm.warp(block.timestamp + signallingCooldownDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);
}

function test_signalling_to_rage_quit() public {
assertEq(dualGov.currentState(), GovernanceState.State.Normal);

updateVetoSupportInPercent(15 * 10 ** 16);

assertEq(dualGov.currentState(), GovernanceState.State.VetoSignalling);

uint256 signallingDuration = dualGov.CONFIG().signallingMaxDuration();

vm.warp(block.timestamp + signallingDuration);
dualGov.activateNextState();

assertEq(dualGov.currentState(), GovernanceState.State.RageQuit);
}


function updateVetoSupportInPercent(uint256 supportInPercent) internal {
Escrow signallingEscrow = Escrow(payable(dualGov.signallingEscrow()));
uint256 newVetoSupport = (supportInPercent * IERC20(ST_ETH).totalSupply()) / 10 ** 18;

vm.prank(stEthWhale);
// signallingEscrow.unlockStEth();

updateVetoSupport(newVetoSupport);
vm.stopPrank();

(uint256 totalSupport, uint256 rageQuitSupport) = signallingEscrow.getSignallingState();
// solhint-disable-next-line
console.log("veto totalSupport %d, rageQuitSupport %d", totalSupport, rageQuitSupport);
}

function updateVetoSupport(uint256 amount) internal {
Escrow signallingEscrow = Escrow(payable(dualGov.signallingEscrow()));
vm.startPrank(stEthWhale);
IERC20(ST_ETH).approve(address(signallingEscrow), amount);
signallingEscrow.lockStEth(amount);
vm.stopPrank();
}
}
2 changes: 1 addition & 1 deletion test/utils/utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ library Utils {

function setupStEthWhale(address addr) internal {
// 15% of total stETH supply
setupStEthWhale(addr, 15 * 10 ** 16);
setupStEthWhale(addr, 30 * 10 ** 16);
}

function setupStEthWhale(address addr, uint256 totalSupplyPercentage) internal {
Expand Down

0 comments on commit 9077412

Please sign in to comment.