Skip to content

Commit

Permalink
Merge pull request #87 from liquity/cs-044
Browse files Browse the repository at this point in the history
chore: Add functions to IGovernance interface
  • Loading branch information
bingen authored Dec 4, 2024
2 parents 9d1bd2a + 567ba08 commit 078a766
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 84 deletions.
30 changes: 4 additions & 26 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -285,17 +285,14 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG
return calculateVotingThreshold(snapshotVotes);
}

/// @dev Returns the most up to date voting threshold
/// In contrast to `getLatestVotingThreshold` this function updates the snapshot
/// This ensures that the value returned is always the latest
/// @inheritdoc IGovernance
function calculateVotingThreshold() public returns (uint256) {
(VoteSnapshot memory snapshot,) = _snapshotVotes();

return calculateVotingThreshold(snapshot.votes);
}

/// @dev Utility function to compute the threshold votes without recomputing the snapshot
/// Note that `boldAccrued` is a cached value, this function works correctly only when called after an accrual
/// @inheritdoc IGovernance
function calculateVotingThreshold(uint256 _votes) public view returns (uint256) {
if (_votes == 0) return 0;

Expand All @@ -321,8 +318,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG
}
}

/// @notice Return the most up to date global snapshot and state as well as a flag to notify whether the state can be updated
/// This is a convenience function to always retrieve the most up to date state values
/// @inheritdoc IGovernance
function getTotalVotesAndState()
public
view
Expand Down Expand Up @@ -360,8 +356,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG
}
}

/// @dev Given an initiative address, return it's most up to date snapshot and state as well as a flag to notify whether the state can be updated
/// This is a convenience function to always retrieve the most up to date state values
/// @inheritdoc IGovernance
function getInitiativeSnapshotAndState(address _initiative)
public
view
Expand Down Expand Up @@ -406,23 +401,6 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG
FSM
//////////////////////////////////////////////////////////////*/

enum InitiativeStatus {
NONEXISTENT,
/// This Initiative Doesn't exist | This is never returned
WARM_UP,
/// This epoch was just registered
SKIP,
/// This epoch will result in no rewards and no unregistering
CLAIMABLE,
/// This epoch will result in claiming rewards
CLAIMED,
/// The rewards for this epoch have been claimed
UNREGISTERABLE,
/// Can be unregistered
DISABLED // It was already Unregistered

}

/// @notice Given an inititive address, updates all snapshots and return the initiative state
/// See the view version of `getInitiativeState` for the underlying logic on Initatives FSM
function getInitiativeState(address _initiative)
Expand Down
59 changes: 59 additions & 0 deletions src/interfaces/IGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,33 @@ interface IGovernance {
pure
returns (uint208);

/// @dev Returns the most up to date voting threshold
/// In contrast to `getLatestVotingThreshold` this function updates the snapshot
/// This ensures that the value returned is always the latest
function calculateVotingThreshold() external returns (uint256);

/// @dev Utility function to compute the threshold votes without recomputing the snapshot
/// Note that `boldAccrued` is a cached value, this function works correctly only when called after an accrual
function calculateVotingThreshold(uint256 _votes) external view returns (uint256);

/// @notice Return the most up to date global snapshot and state as well as a flag to notify whether the state can be updated
/// This is a convenience function to always retrieve the most up to date state values
function getTotalVotesAndState()
external
view
returns (VoteSnapshot memory snapshot, GlobalState memory state, bool shouldUpdate);

/// @dev Given an initiative address, return it's most up to date snapshot and state as well as a flag to notify whether the state can be updated
/// This is a convenience function to always retrieve the most up to date state values
function getInitiativeSnapshotAndState(address _initiative)
external
view
returns (
InitiativeVoteSnapshot memory initiativeSnapshot,
InitiativeState memory initiativeState,
bool shouldUpdate
);

/// @notice Voting threshold is the max. of either:
/// - 4% of the total voting LQTY in the previous epoch
/// - or the minimum number of votes necessary to claim at least MIN_CLAIM BOLD
Expand All @@ -240,6 +267,38 @@ interface IGovernance {
external
returns (VoteSnapshot memory voteSnapshot, InitiativeVoteSnapshot memory initiativeVoteSnapshot);

/*//////////////////////////////////////////////////////////////
FSM
//////////////////////////////////////////////////////////////*/

enum InitiativeStatus {
NONEXISTENT,
/// This Initiative Doesn't exist | This is never returned
WARM_UP,
/// This epoch was just registered
SKIP,
/// This epoch will result in no rewards and no unregistering
CLAIMABLE,
/// This epoch will result in claiming rewards
CLAIMED,
/// The rewards for this epoch have been claimed
UNREGISTERABLE,
/// Can be unregistered
DISABLED // It was already Unregistered

}

function getInitiativeState(address _initiative)
external
returns (InitiativeStatus status, uint16 lastEpochClaim, uint256 claimableAmount);

function getInitiativeState(
address _initiative,
VoteSnapshot memory _votesSnapshot,
InitiativeVoteSnapshot memory _votesForInitiativeSnapshot,
InitiativeState memory _initiativeState
) external view returns (InitiativeStatus status, uint16 lastEpochClaim, uint256 claimableAmount);

/// @notice Registers a new initiative
/// @param _initiative Address of the initiative
function registerInitiative(address _initiative) external;
Expand Down
42 changes: 21 additions & 21 deletions test/E2E.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ contract ForkedE2ETests is Test {

address newInitiative = address(0x123123);
governance.registerInitiative(newInitiative);
assertEq(uint256(Governance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative), "Cooldown");
assertEq(uint256(IGovernance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative), "Cooldown");

uint256 skipCount;

Expand All @@ -165,25 +165,25 @@ contract ForkedE2ETests is Test {
// Whereas in next week it will work
vm.warp(block.timestamp + EPOCH_DURATION); // 1
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

// Cooldown on epoch Staert
vm.warp(block.timestamp + EPOCH_DURATION); // 2
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

vm.warp(block.timestamp + EPOCH_DURATION); // 3
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

vm.warp(block.timestamp + EPOCH_DURATION); // 3
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

vm.warp(block.timestamp + EPOCH_DURATION); // 4
++skipCount;
assertEq(
uint256(Governance.InitiativeStatus.UNREGISTERABLE), _getInitiativeStatus(newInitiative), "UNREGISTERABLE"
uint256(IGovernance.InitiativeStatus.UNREGISTERABLE), _getInitiativeStatus(newInitiative), "UNREGISTERABLE"
);

/// 4 + 1 ??
Expand All @@ -205,8 +205,8 @@ contract ForkedE2ETests is Test {
address newInitiative2 = address(0x1231234);
governance.registerInitiative(newInitiative);
governance.registerInitiative(newInitiative2);
assertEq(uint256(Governance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative), "Cooldown");
assertEq(uint256(Governance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative2), "Cooldown");
assertEq(uint256(IGovernance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative), "Cooldown");
assertEq(uint256(IGovernance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative2), "Cooldown");

uint256 skipCount;

Expand All @@ -217,7 +217,7 @@ contract ForkedE2ETests is Test {

vm.warp(block.timestamp + EPOCH_DURATION); // 1
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

_allocate(newInitiative2, 1e18, 0);

Expand All @@ -226,24 +226,24 @@ contract ForkedE2ETests is Test {
// Cooldown on epoch Staert
vm.warp(block.timestamp + EPOCH_DURATION); // 2
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

// 3rd Week of SKIP

vm.warp(block.timestamp + EPOCH_DURATION); // 3
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

// 4th Week of SKIP | If it doesn't get any rewards it will be UNREGISTERABLE

vm.warp(block.timestamp + EPOCH_DURATION); // 3
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

vm.warp(block.timestamp + EPOCH_DURATION); // 4
++skipCount;
assertEq(
uint256(Governance.InitiativeStatus.UNREGISTERABLE), _getInitiativeStatus(newInitiative), "UNREGISTERABLE"
uint256(IGovernance.InitiativeStatus.UNREGISTERABLE), _getInitiativeStatus(newInitiative), "UNREGISTERABLE"
);

/// It was SKIP for 4 EPOCHS, it is now UNREGISTERABLE
Expand All @@ -264,8 +264,8 @@ contract ForkedE2ETests is Test {
address newInitiative2 = address(0x1231234);
governance.registerInitiative(newInitiative);
governance.registerInitiative(newInitiative2);
assertEq(uint256(Governance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative), "Cooldown");
assertEq(uint256(Governance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative2), "Cooldown");
assertEq(uint256(IGovernance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative), "Cooldown");
assertEq(uint256(IGovernance.InitiativeStatus.WARM_UP), _getInitiativeStatus(newInitiative2), "Cooldown");

uint256 skipCount;

Expand All @@ -276,7 +276,7 @@ contract ForkedE2ETests is Test {

vm.warp(block.timestamp + EPOCH_DURATION); // 1
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

_allocate(newInitiative2, 1e18, 0);

Expand All @@ -285,27 +285,27 @@ contract ForkedE2ETests is Test {
// Cooldown on epoch Staert
vm.warp(block.timestamp + EPOCH_DURATION); // 2
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

// 3rd Week of SKIP

vm.warp(block.timestamp + EPOCH_DURATION); // 3
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

// 4th Week of SKIP | If it doesn't get any rewards it will be UNREGISTERABLE

vm.warp(block.timestamp + EPOCH_DURATION); // 3
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");
assertEq(uint256(IGovernance.InitiativeStatus.SKIP), _getInitiativeStatus(newInitiative), "SKIP");

// Allocating to it, saves it
_reset(newInitiative2);
_allocate(newInitiative, 1e18, 0);

vm.warp(block.timestamp + EPOCH_DURATION); // 4
++skipCount;
assertEq(uint256(Governance.InitiativeStatus.CLAIMABLE), _getInitiativeStatus(newInitiative), "UNREGISTERABLE");
assertEq(uint256(IGovernance.InitiativeStatus.CLAIMABLE), _getInitiativeStatus(newInitiative), "UNREGISTERABLE");
}

function _deposit(uint88 amt) internal {
Expand Down Expand Up @@ -339,7 +339,7 @@ contract ForkedE2ETests is Test {
}

function _getInitiativeStatus(address _initiative) internal returns (uint256) {
(Governance.InitiativeStatus status,,) = governance.getInitiativeState(_initiative);
(IGovernance.InitiativeStatus status,,) = governance.getInitiativeState(_initiative);
return uint256(status);
}
}
2 changes: 1 addition & 1 deletion test/Governance.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ abstract contract GovernanceTest is Test {

assertEq(lusd.balanceOf(baseInitiative1), 15000e18);

(Governance.InitiativeStatus status,, uint256 claimable) = governance.getInitiativeState(baseInitiative2);
(IGovernance.InitiativeStatus status,, uint256 claimable) = governance.getInitiativeState(baseInitiative2);
console.log("res", uint8(status));
console.log("claimable", claimable);
(uint224 votes,,, uint224 vetos) = governance.votesForInitiativeSnapshot(baseInitiative2);
Expand Down
6 changes: 3 additions & 3 deletions test/recon/BeforeAfter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {Governance} from "src/Governance.sol";
abstract contract BeforeAfter is Setup, Asserts {
struct Vars {
uint16 epoch;
mapping(address => Governance.InitiativeStatus) initiativeStatus;
mapping(address => IGovernance.InitiativeStatus) initiativeStatus;
// initiative => user => epoch => claimed
mapping(address => mapping(address => mapping(uint16 => bool))) claimedBribeForInitiativeAtEpoch;
mapping(address user => uint128 lqtyBalance) userLqtyBalance;
Expand All @@ -31,7 +31,7 @@ abstract contract BeforeAfter is Setup, Asserts {
_before.epoch = currentEpoch;
for (uint8 i; i < deployedInitiatives.length; i++) {
address initiative = deployedInitiatives[i];
(Governance.InitiativeStatus status,,) = governance.getInitiativeState(initiative);
(IGovernance.InitiativeStatus status,,) = governance.getInitiativeState(initiative);
_before.initiativeStatus[initiative] = status;
_before.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch] =
IBribeInitiative(initiative).claimedBribeAtEpoch(user, currentEpoch);
Expand All @@ -48,7 +48,7 @@ abstract contract BeforeAfter is Setup, Asserts {
_after.epoch = currentEpoch;
for (uint8 i; i < deployedInitiatives.length; i++) {
address initiative = deployedInitiatives[i];
(Governance.InitiativeStatus status,,) = governance.getInitiativeState(initiative);
(IGovernance.InitiativeStatus status,,) = governance.getInitiativeState(initiative);
_after.initiativeStatus[initiative] = status;
_after.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch] =
IBribeInitiative(initiative).claimedBribeAtEpoch(user, currentEpoch);
Expand Down
2 changes: 1 addition & 1 deletion test/recon/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ abstract contract Setup is BaseSetup, MockStakingV1Deployer {
}

function _getInitiativeStatus(address) internal returns (uint256) {
(Governance.InitiativeStatus status,,) = governance.getInitiativeState(_getDeployedInitiative(0));
(IGovernance.InitiativeStatus status,,) = governance.getInitiativeState(_getDeployedInitiative(0));
return uint256(status);
}
}
Loading

0 comments on commit 078a766

Please sign in to comment.