Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: make Proposal a library, and turn Utils in VmExtension #332

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 47 additions & 44 deletions test/governance/IntegrationTests/GovernanceIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity 0.8.18;
// solhint-disable func-name-mixedcase, max-line-length, max-states-count

import { TestSetup } from "../TestSetup.sol";
import { Vm } from "forge-std-next/Vm.sol";
import { VmExtension } from "test/utils/VmExtension.sol";

import { MentoGovernor } from "contracts/governance/MentoGovernor.sol";
import { GovernanceFactory } from "contracts/governance/GovernanceFactory.sol";
Expand All @@ -13,7 +15,6 @@ import { Locking } from "contracts/governance/locking/Locking.sol";
import { TimelockController } from "contracts/governance/TimelockController.sol";

import { Proposals } from "./Proposals.sol";
import { Utils } from "./Utils.sol";
import { Arrays } from "test/utils/Arrays.sol";
import { TestLocking } from "test/utils/TestLocking.sol";

Expand All @@ -24,7 +25,9 @@ import { Enum } from "safe-contracts/contracts/common/Enum.sol";

import { ITransparentUpgradeableProxy } from "openzeppelin-contracts-next/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
contract GovernanceIntegrationTest is TestSetup {
using VmExtension for Vm;

GovernanceFactory public factory;

ProxyAdmin public proxyAdmin;
Expand Down Expand Up @@ -77,7 +80,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
vm.prank(bob);
locking.lock(bob, bob, 1500e18, 1, 103);

Utils._timeTravel(BLOCKS_DAY);
vm.timeTravel(BLOCKS_DAY);
_;
}

Expand Down Expand Up @@ -231,7 +234,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
assertApproxEqAbs(locking.getVotes(alice), 300e18, negligibleAmount);
assertApproxEqAbs(locking.getVotes(bob), 400e18, negligibleAmount);

Utils._timeTravel(13 * BLOCKS_WEEK);
vm.timeTravel(13 * BLOCKS_WEEK);

// Alice withdraws after ~3 months
vm.prank(alice);
Expand All @@ -245,7 +248,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
assertApproxEqAbs(locking.getVotes(bob), 300e18, negligibleAmount);
assertEq(mentoToken.balanceOf(bob), 0);

Utils._timeTravel(13 * BLOCKS_WEEK);
vm.timeTravel(13 * BLOCKS_WEEK);

// Bob relocks and delegates to alice
vm.prank(bob);
Expand All @@ -255,7 +258,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
assertApproxEqAbs(locking.getVotes(alice), 300e18, negligibleAmount);
assertEq(locking.getVotes(bob), 0);

Utils._timeTravel(13 * BLOCKS_WEEK);
vm.timeTravel(13 * BLOCKS_WEEK);

// Bob delegates the lock without relocking
vm.prank(bob);
Expand All @@ -266,7 +269,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
assertApproxEqAbs(locking.getVotes(charlie), 150e18, negligibleAmount);

// End of the locking period
Utils._timeTravel(13 * BLOCKS_WEEK);
vm.timeTravel(13 * BLOCKS_WEEK);

vm.prank(bob);
locking.withdraw();
Expand Down Expand Up @@ -306,7 +309,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
);

// ~10 mins
Utils._timeTravel(120);
vm.timeTravel(120);

// both users cast vote, majority in favor (because alice has more votes than bob)
vm.prank(alice);
Expand All @@ -316,13 +319,13 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
mentoGovernor.castVote(proposalId, 0);

// voting period ends
Utils._timeTravel(BLOCKS_WEEK);
vm.timeTravel(BLOCKS_WEEK);

// proposal can now be queued
mentoGovernor.queue(targets, values, calldatas, keccak256(bytes(description)));

// timelock ends
Utils._timeTravel(2 * BLOCKS_DAY);
vm.timeTravel(2 * BLOCKS_DAY);

// anyone can execute the proposal
mentoGovernor.execute(targets, values, calldatas, keccak256(bytes(description)));
Expand Down Expand Up @@ -362,15 +365,15 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
uint256 validUntil = block.timestamp + 60 days;
uint256 approvedAt = block.timestamp - 10 days;

bytes memory fractalProof0 = Utils._validKycSignature(
bytes memory fractalProof0 = vm.validKycSignature(
fractalSignerPk,
claimer0,
EXPECTED_CREDENTIAL,
validUntil,
approvedAt
);

bytes memory fractalProof1 = Utils._validKycSignature(
bytes memory fractalProof1 = vm.validKycSignature(
fractalSignerPk,
claimer1,
EXPECTED_CREDENTIAL,
Expand All @@ -390,7 +393,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
assertEq(locking.getVotes(claimer1), 0);
assertEq(locking.getVotes(alice), 12_000e18);

Utils._timeTravel(BLOCKS_DAY);
vm.timeTravel(BLOCKS_DAY);

address newEmissionTarget = makeAddr("NewEmissionTarget");

Expand All @@ -410,7 +413,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
) = Proposals._proposeChangeEmissionTarget(mentoGovernor, emission, newEmissionTarget);

// ~10 mins
Utils._timeTravel(120);
vm.timeTravel(120);

// both claimers and delegate cast vote
vm.prank(claimer0);
Expand All @@ -425,15 +428,15 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
mentoGovernor.queue(targets, values, calldatas, keccak256(bytes(description)));

// voting period ends
Utils._timeTravel(BLOCKS_WEEK);
vm.timeTravel(BLOCKS_WEEK);

mentoGovernor.queue(targets, values, calldatas, keccak256(bytes(description)));

// timelock blocks for the lock delay
vm.expectRevert("TimelockController: operation is not ready");
mentoGovernor.execute(targets, values, calldatas, keccak256(bytes(description)));

Utils._timeTravel(2 * BLOCKS_DAY);
vm.timeTravel(2 * BLOCKS_DAY);

assertEq(emission.emissionTarget(), governanceTimelockAddress);

Expand All @@ -449,7 +452,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
assertEq(mentoToken.balanceOf(governanceTimelockAddress), 100_000_000e18);

// emit tokens after a year
Utils._timeTravel(365 * BLOCKS_DAY);
vm.timeTravel(365 * BLOCKS_DAY);
uint256 amount = emission.emitTokens();

assertEq(emission.totalEmittedAmount(), amount);
Expand Down Expand Up @@ -477,7 +480,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
vm.prank(charlie);
locking.lock(charlie, charlie, 5000e18, 30, 10);

Utils._timeTravel(1);
vm.timeTravel(1);

address newEmissionTarget = makeAddr("NewEmissionTarget");

Expand All @@ -492,7 +495,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
) = Proposals._proposeChangeEmissionTarget(mentoGovernor, emission, newEmissionTarget);

// ~10 mins
Utils._timeTravel(120);
vm.timeTravel(120);

// majority votes in favor of the proposal
vm.prank(alice);
Expand All @@ -504,12 +507,12 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
vm.prank(charlie);
mentoGovernor.castVote(proposalId, 1);

Utils._timeTravel(BLOCKS_WEEK);
vm.timeTravel(BLOCKS_WEEK);

mentoGovernor.queue(targets, values, calldatas, keccak256(bytes(description)));

// still time locked
Utils._timeTravel(BLOCKS_DAY);
vm.timeTravel(BLOCKS_DAY);

bytes32 timelockId = governanceTimelock.hashOperationBatch(
targets,
Expand All @@ -524,7 +527,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
governanceTimelock.cancel(timelockId);

// timelock delay is over
Utils._timeTravel(BLOCKS_DAY);
vm.timeTravel(BLOCKS_DAY);

// proposal can not be executed since it was cancelled
vm.expectRevert("Governor: proposal not successful");
Expand Down Expand Up @@ -565,7 +568,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
);

// ~10 mins
Utils._timeTravel(120);
vm.timeTravel(120);

// both claimers cast vote
vm.prank(alice);
Expand All @@ -575,11 +578,11 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
vm.prank(bob);
mentoGovernor.castVote(proposalId, 1);

Utils._timeTravel(7 * BLOCKS_DAY);
vm.timeTravel(7 * BLOCKS_DAY);

mentoGovernor.queue(targets, values, calldatas, keccak256(bytes(description)));

Utils._timeTravel(2 * BLOCKS_DAY);
vm.timeTravel(2 * BLOCKS_DAY);

// the old implementation has no such method
vm.expectRevert();
Expand Down Expand Up @@ -617,11 +620,11 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {

// sign the transfer tx
(uint8 v, bytes32 r, bytes32 s) = vm.sign(mentoPK0, txHash);
bytes memory signature0 = Utils._constructSignature(v, r, s);
bytes memory signature0 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK1, txHash);
bytes memory signature1 = Utils._constructSignature(v, r, s);
bytes memory signature1 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK2, txHash);
bytes memory signature2 = Utils._constructSignature(v, r, s);
bytes memory signature2 = vm.constructSignature(v, r, s);

bytes memory signatures = abi.encodePacked(signature2);

Expand Down Expand Up @@ -696,11 +699,11 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {

// sign the schedule tx
(uint8 v, bytes32 r, bytes32 s) = vm.sign(mentoPK0, txHash);
bytes memory signature0 = Utils._constructSignature(v, r, s);
bytes memory signature0 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK1, txHash);
bytes memory signature1 = Utils._constructSignature(v, r, s);
bytes memory signature1 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK2, txHash);
bytes memory signature2 = Utils._constructSignature(v, r, s);
bytes memory signature2 = vm.constructSignature(v, r, s);

bytes memory signatures = abi.encodePacked(signature2, signature1, signature0);

Expand All @@ -718,7 +721,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
signatures
);

Utils._timeTravel(12 * BLOCKS_DAY);
vm.timeTravel(12 * BLOCKS_DAY);

// the tx is not ready to be executed
vm.expectRevert("TimelockController: operation is not ready");
Expand All @@ -730,7 +733,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
keccak256(bytes("Transfer tokens to governanceTimelockAddress"))
);

Utils._timeTravel(2 * BLOCKS_DAY);
vm.timeTravel(2 * BLOCKS_DAY);

// after 13 days, timelock expires
mentoLabsTreasury.execute(
Expand Down Expand Up @@ -781,11 +784,11 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {

// sign the schedule tx
(uint8 v, bytes32 r, bytes32 s) = vm.sign(mentoPK0, txHash);
bytes memory signature0 = Utils._constructSignature(v, r, s);
bytes memory signature0 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK1, txHash);
bytes memory signature1 = Utils._constructSignature(v, r, s);
bytes memory signature1 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK2, txHash);
bytes memory signature2 = Utils._constructSignature(v, r, s);
bytes memory signature2 = vm.constructSignature(v, r, s);
bytes memory signatures = abi.encodePacked(signature2, signature1, signature0);

// schedule the transfer by calling schedule from the multisig on the timelock
Expand Down Expand Up @@ -822,7 +825,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
) = Proposals._proposeCancelQueuedTx(mentoGovernor, mentoLabsTreasury, id);

// ~10 mins
Utils._timeTravel(120);
vm.timeTravel(120);

// both claimers cast vote
vm.prank(alice);
Expand All @@ -832,17 +835,17 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
vm.prank(bob);
mentoGovernor.castVote(proposalId, 1);

Utils._timeTravel(7 * BLOCKS_DAY);
vm.timeTravel(7 * BLOCKS_DAY);

// queue the cancelling proposal
mentoGovernor.queue(targets, values, calldatas, keccak256(bytes(description)));

Utils._timeTravel(2 * BLOCKS_DAY);
vm.timeTravel(2 * BLOCKS_DAY);

// governance cancels the queued transfer
mentoGovernor.execute(targets, values, calldatas, keccak256(bytes(description)));

Utils._timeTravel(5 * BLOCKS_DAY);
vm.timeTravel(5 * BLOCKS_DAY);

// the transfer can not be executed
vm.expectRevert("TimelockController: operation is not ready");
Expand Down Expand Up @@ -896,11 +899,11 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {

// sign the grantRole tx
(uint8 v, bytes32 r, bytes32 s) = vm.sign(mentoPK0, txHash);
bytes memory signature0 = Utils._constructSignature(v, r, s);
bytes memory signature0 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK1, txHash);
bytes memory signature1 = Utils._constructSignature(v, r, s);
bytes memory signature1 = vm.constructSignature(v, r, s);
(v, r, s) = vm.sign(mentoPK2, txHash);
bytes memory signature2 = Utils._constructSignature(v, r, s);
bytes memory signature2 = vm.constructSignature(v, r, s);

bytes memory signatures = abi.encodePacked(signature2, signature1, signature0);

Expand All @@ -918,7 +921,7 @@ contract GovernanceIntegrationTest is TestSetup, Proposals, Utils {
signatures
);

Utils._timeTravel(13 * BLOCKS_DAY);
vm.timeTravel(13 * BLOCKS_DAY);

assertFalse(mentoLabsTreasury.hasRole(proposerRole, alice));

Expand Down
4 changes: 1 addition & 3 deletions test/governance/IntegrationTests/Proposals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { Emission } from "contracts/governance/Emission.sol";
import { ProxyAdmin } from "openzeppelin-contracts-next/contracts/proxy/transparent/ProxyAdmin.sol";
import { ITransparentUpgradeableProxy } from "openzeppelin-contracts-next/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import { Test } from "forge-std-next/Test.sol";

contract Proposals is Test {
library Proposals {
function _proposeChangeEmissionTarget(
MentoGovernor mentoGovernor,
Emission emission,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@
pragma solidity 0.8.18;
// solhint-disable func-name-mixedcase, max-line-length, no-inline-assembly

import { Test } from "forge-std-next/Test.sol";
import { Vm } from "forge-std-next/Vm.sol";
import { ECDSA } from "openzeppelin-contracts-next/contracts/utils/cryptography/ECDSA.sol";
import { Strings } from "openzeppelin-contracts-next/contracts/utils/Strings.sol";

contract Utils is Test {
library VmExtension {
/// @dev moves `block.number` and `block.timestamp` in sync
/// @param vm The forge Vm
/// @param blocks The number of blocks that will be moved
function _timeTravel(uint256 blocks) internal {
function timeTravel(Vm vm, uint256 blocks) internal {
uint256 time = blocks * 5;
vm.roll(block.number + blocks);
skip(time);
vm.warp(block.timestamp + time);
}

/// @dev build the KYC message hash and sign it with the provided pk
/// @param vm The forge Vm
/// @param signer The PK to sign the message with
/// @param account The account to sign the message for
/// @param credential KYC credentials
/// @param validUntil KYC valid until this timestamp
/// @param approvedAt KYC approved at this timestamp
function _validKycSignature(
function validKycSignature(
Vm vm,
uint256 signer,
address account,
string memory credential,
Expand Down Expand Up @@ -51,7 +54,8 @@ contract Utils is Test {
/// @param r The first 32 bytes of the signature, representing the R value in ECDSA.
/// @param s The next 32 bytes of the signature, representing the S value in ECDSA.
/// @return signature A 65-byte long digital signature composed of r, s, and v.
function _constructSignature(
function constructSignature(
Vm,
uint8 v,
bytes32 r,
bytes32 s
Expand Down
Loading