Skip to content

Commit 9b07f85

Browse files
committed
fix(contracts): use time for governance
1 parent 2e0f83f commit 9b07f85

File tree

4 files changed

+109
-4
lines changed

4 files changed

+109
-4
lines changed

contracts/.env.example

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,20 @@ AUCTIONEER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
7272

7373
# Governance Variables
7474

75-
### The delay (in blocks) before voting can start on a proposal.
75+
### The delay (in seconds) before voting can start on a proposal.
7676
###
7777
### Allows stakers to see an upcoming proposal and have an opportunity to change their delegation
7878
### via `requestUnstake` before the vote starts.
7979
###
8080
### Mutable after deployment by governance.
81-
VOTING_DELAY=7200 # 1 day assuming a 12s blocktime
81+
VOTING_DELAY=86400 # 1 day
8282

83-
### The duration (in blocks) that a proposal is open for voting.
83+
### The duration (in seconds) that a proposal is open for voting.
8484
###
8585
### Gives an opportunity for provers to `castVote` on a proposal.
8686
###
8787
### Mutable after deployment by governance.
88-
VOTING_PERIOD=100800 # 2 weeks assuming a 12s blocktime
88+
VOTING_PERIOD=1209600 # 2 weeks
8989

9090
### Minimum $iPROVE needed to create a proposal.
9191
###

contracts/script/utils/PostDeployment.s.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ contract PostDeploymentScript is BaseScript, Test {
124124
assertEq(governor.votingPeriod(), VOTE_PERIOD);
125125
assertEq(governor.proposalThreshold(), PROP_THRESH);
126126
assertEq(governor.quorumNumerator(), QUORUM);
127+
assertGt(governor.clock(), 0);
128+
assertEq(governor.CLOCK_MODE(), "mode=timestamp");
127129
}
128130

129131
function _checkIProve(address _iProve, address _staking, address _prove) internal view {

contracts/src/tokens/IntermediateSuccinct.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ contract IntermediateSuccinct is ERC4626, ERC20Permit, ERC20Votes, IIntermediate
9999
super._spendAllowance(_owner, _spender, _amount);
100100
}
101101

102+
// The following functions are for overriding the clock and clock mode for IERC6372. This
103+
// allows governance to be based on time instead of block number.
104+
105+
function clock() public view override returns (uint48) {
106+
return uint48(block.timestamp);
107+
}
108+
109+
function CLOCK_MODE() public pure override returns (string memory) {
110+
return "mode=timestamp";
111+
}
112+
102113
// The following functions are overrides required by Solidity.
103114

104115
function nonces(address _owner) public view override(ERC20Permit, Nonces) returns (uint256) {

contracts/test/SuccinctGovernor.t.sol

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,4 +410,96 @@ contract SuccinctGovernorTest is SuccinctStakingTest {
410410
assertTrue(requests[0].resolved);
411411
assertTrue(requests[1].resolved);
412412
}
413+
414+
// Test governance proposal lifecycle using time-based delays and periods.
415+
function test_Propose_WithTimeBasedGovernance() public {
416+
// The parameter to be updated.
417+
uint256 newDispenseRate = DISPENSE_RATE * 2;
418+
419+
// Staker stakes to Alice's prover.
420+
_stake(STAKER_1, ALICE_PROVER, STAKER_PROVE_AMOUNT);
421+
422+
// It takes a block for voting power to update.
423+
vm.roll(block.number + 1);
424+
425+
// Alice makes a proposal through her prover contract.
426+
address[] memory targets = new address[](1);
427+
targets[0] = STAKING;
428+
uint256[] memory values = new uint256[](1);
429+
values[0] = 0;
430+
bytes[] memory calldatas = new bytes[](1);
431+
calldatas[0] =
432+
abi.encodeWithSelector(SuccinctStaking.updateDispenseRate.selector, newDispenseRate);
433+
string memory description =
434+
string.concat("Time-based: Update dispense rate to ", Strings.toString(newDispenseRate));
435+
bytes32 descriptionHash = keccak256(bytes(description));
436+
437+
// Record the initial timestamp.
438+
uint256 proposalTimestamp = block.timestamp;
439+
440+
// Alice proposes through her prover contract.
441+
vm.prank(ALICE);
442+
uint256 proposalId =
443+
SuccinctProver(ALICE_PROVER).propose(targets, values, calldatas, description);
444+
445+
// Proposal should be in Pending state.
446+
IGovernor.ProposalState state = SuccinctGovernor(payable(GOVERNOR)).state(proposalId);
447+
assertEq(uint8(state), uint8(IGovernor.ProposalState.Pending));
448+
449+
// Get the voting delay in seconds.
450+
uint256 votingDelay = SuccinctGovernor(payable(GOVERNOR)).votingDelay();
451+
452+
// Advance time just before voting delay expires.
453+
vm.warp(proposalTimestamp + votingDelay - 1);
454+
455+
// Proposal should still be in Pending state.
456+
state = SuccinctGovernor(payable(GOVERNOR)).state(proposalId);
457+
assertEq(uint8(state), uint8(IGovernor.ProposalState.Pending));
458+
459+
// Advance time to exactly when voting starts.
460+
vm.warp(proposalTimestamp + votingDelay + 1);
461+
462+
// Proposal should now be in Active state.
463+
state = SuccinctGovernor(payable(GOVERNOR)).state(proposalId);
464+
assertEq(uint8(state), uint8(IGovernor.ProposalState.Active));
465+
466+
// Alice votes FOR the proposal.
467+
vm.prank(ALICE);
468+
SuccinctProver(ALICE_PROVER).castVote(proposalId, 1);
469+
470+
// Check the votes were recorded.
471+
(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) =
472+
SuccinctGovernor(payable(GOVERNOR)).proposalVotes(proposalId);
473+
assertEq(forVotes, IERC20(I_PROVE).balanceOf(ALICE_PROVER));
474+
assertEq(againstVotes, 0);
475+
assertEq(abstainVotes, 0);
476+
477+
// Get the voting period in seconds.
478+
uint256 votingPeriod = SuccinctGovernor(payable(GOVERNOR)).votingPeriod();
479+
480+
// Advance time just before voting period expires.
481+
vm.warp(proposalTimestamp + votingDelay + votingPeriod - 1);
482+
483+
// Proposal should still be in Active state.
484+
state = SuccinctGovernor(payable(GOVERNOR)).state(proposalId);
485+
assertEq(uint8(state), uint8(IGovernor.ProposalState.Active));
486+
487+
// Advance time to after voting period expires.
488+
vm.warp(proposalTimestamp + votingDelay + votingPeriod + 1);
489+
490+
// Proposal should be in Succeeded state.
491+
state = SuccinctGovernor(payable(GOVERNOR)).state(proposalId);
492+
assertEq(uint8(state), uint8(IGovernor.ProposalState.Succeeded));
493+
494+
// Execute the proposal.
495+
vm.prank(STAKER_1);
496+
SuccinctGovernor(payable(GOVERNOR)).execute(targets, values, calldatas, descriptionHash);
497+
498+
// Proposal should be in Executed state.
499+
state = SuccinctGovernor(payable(GOVERNOR)).state(proposalId);
500+
assertEq(uint8(state), uint8(IGovernor.ProposalState.Executed));
501+
502+
// Verify the dispense rate was updated.
503+
assertEq(SuccinctStaking(STAKING).dispenseRate(), newDispenseRate);
504+
}
413505
}

0 commit comments

Comments
 (0)