Skip to content

Commit

Permalink
tests: add governor actors and basic mainnet invariant
Browse files Browse the repository at this point in the history
  • Loading branch information
0xtekgrinder committed Dec 18, 2023
1 parent aa10c05 commit e7be47a
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 16 deletions.
18 changes: 10 additions & 8 deletions test/invariant/BasicInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@

pragma solidity ^0.8.19;

import { IERC20 } from "oz/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "oz/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "oz/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "oz/token/ERC20/extensions/IERC20Metadata.sol";
import "oz/utils/Strings.sol";
import { Voter } from "./actors/Voter.t.sol";
import { Fixture, AngleGovernor } from "../Fixture.t.sol";
import {Voter} from "./actors/Voter.t.sol";
import {Fixture, AngleGovernor} from "../Fixture.t.sol";
import {ProposalStore} from "./stores/ProposalStore.sol";

//solhint-disable
import { console } from "forge-std/console.sol";
import {console} from "forge-std/console.sol";

contract BasicInvariants is Fixture {
uint256 internal constant _NUM_VOTER = 10;

Voter internal _voterHandler;
// Keep track of current proposals
uint256[] internal _proposals;
ProposalStore internal _proposalStore;

function setUp() public virtual override {
super.setUp();

_voterHandler = new Voter(angleGovernor, ANGLE, _NUM_VOTER);
_voterHandler = new Voter(angleGovernor, ANGLE, _NUM_VOTER, _proposalStore);

// Label newly created addresses
for (uint256 i; i < _NUM_VOTER; i++)
for (uint256 i; i < _NUM_VOTER; i++) {
vm.label(_voterHandler.actors(i), string.concat("Trader ", Strings.toString(i)));
}

targetContract(address(_voterHandler));

Expand Down
75 changes: 75 additions & 0 deletions test/invariant/MainnetGovernorInvariants.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.19;

import {IERC20} from "oz/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "oz/token/ERC20/extensions/IERC20Metadata.sol";
import "oz/utils/Strings.sol";
import {Voter} from "./actors/Voter.t.sol";
import {Proposer} from "./actors/Proposer.t.sol";
import {BadVoter} from "./actors/BadVoter.t.sol";
import {Fixture, AngleGovernor} from "../Fixture.t.sol";
import {ProposalStore} from "./stores/ProposalStore.sol";

//solhint-disable
import {console} from "forge-std/console.sol";

contract MainnetGovernorInvariants is Fixture {
uint256 internal constant _NUM_VOTER = 10;

Voter internal _voterHandler;
Proposer internal _proposerHandler;
BadVoter internal _badVoterHandler;

// Keep track of current proposals
ProposalStore internal _proposalStore;

function setUp() public virtual override {
super.setUp();

_proposalStore = new ProposalStore();
_voterHandler = new Voter(angleGovernor, ANGLE, _NUM_VOTER, _proposalStore);
_proposerHandler = new Proposer(angleGovernor, ANGLE, 1, _proposalStore, token);
_badVoterHandler = new BadVoter(angleGovernor, ANGLE, _NUM_VOTER, _proposalStore);

// Label newly created addresses
vm.label({account: address(_proposalStore), newLabel: "ProposalStore"});
for (uint256 i; i < _NUM_VOTER; i++) {
vm.label(_voterHandler.actors(i), string.concat("Voter ", Strings.toString(i)));
_setupDealAndLockANGLE(_voterHandler.actors(i), 100000000e18, 4 * 365 days);
}
for (uint256 i; i < _NUM_VOTER; i++) {
vm.label(_badVoterHandler.actors(i), string.concat("BadVoter ", Strings.toString(i)));
_setupDealAndLockANGLE(_badVoterHandler.actors(i), 100000000e18, 4 * 365 days);
}
vm.label(_proposerHandler.actors(0), "Proposer");
_setupDealAndLockANGLE(_proposerHandler.actors(0), angleGovernor.proposalThreshold() * 10, 4 * 365 days);

vm.warp(block.timestamp + 1 weeks);

targetContract(address(_voterHandler));
targetContract(address(_proposerHandler));
targetContract(address(_badVoterHandler));

{
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = Voter.vote.selector;
targetSelector(FuzzSelector({addr: address(_voterHandler), selectors: selectors}));
}
{
bytes4[] memory selectors = new bytes4[](4);
selectors[0] = Proposer.propose.selector;
selectors[1] = Proposer.execute.selector;
selectors[2] = Proposer.skipVotingDelay.selector;
selectors[3] = Proposer.shortCircuit.selector;
targetSelector(FuzzSelector({addr: address(_proposerHandler), selectors: selectors}));
}
{
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = BadVoter.vote.selector;
targetSelector(FuzzSelector({addr: address(_badVoterHandler), selectors: selectors}));
}
}

function invariant_MainnetGovernorSuccess() public {}
}
37 changes: 37 additions & 0 deletions test/invariant/actors/BadVoter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {BaseActor, IERC20, IERC20Metadata, AngleGovernor, TestStorage} from "./BaseActor.t.sol";
import {console} from "forge-std/console.sol";
import {ProposalStore, Proposal} from "../stores/ProposalStore.sol";
import {IGovernor} from "oz/governance/IGovernor.sol";

contract BadVoter is BaseActor {
AngleGovernor internal _angleGovernor;
ProposalStore public proposalStore;

constructor(AngleGovernor angleGovernor, IERC20 _agToken, uint256 nbrVoter, ProposalStore _proposalStore)
BaseActor(nbrVoter, "BadVoter", _agToken)
{
_angleGovernor = angleGovernor;
proposalStore = _proposalStore;
}

function vote(uint256 actorIndexSeed, uint256 proposalId) public useActor(actorIndexSeed) {
if (proposalStore.nbProposals() == 0) {
return;
}
Proposal[] memory proposals = proposalStore.getProposals();
for (uint256 i; i < proposals.length; i++) {
Proposal memory proposal = proposals[i];
uint256 proposalHash =
_angleGovernor.hashProposal(proposal.target, proposal.value, proposal.data, proposal.description);
if (proposalHash != proposalId || proposalStore.doesOldProposalExists(proposalHash)) {
return;
}
}

vm.expectRevert(abi.encodeWithSelector(IGovernor.GovernorNonexistentProposal.selector, proposalId));
_angleGovernor.castVote(proposalId, 1);
}
}
10 changes: 5 additions & 5 deletions test/invariant/actors/BaseActor.t.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import { IERC20 } from "oz/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "oz/token/ERC20/extensions/IERC20Metadata.sol";
import { Test, stdMath, StdStorage, stdStorage } from "forge-std/Test.sol";
import { IVotes } from "oz/governance/utils/IVotes.sol";
import { AngleGovernor } from "contracts/AngleGovernor.sol";
import {IERC20} from "oz/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "oz/token/ERC20/extensions/IERC20Metadata.sol";
import {Test, stdMath, StdStorage, stdStorage} from "forge-std/Test.sol";
import {IVotes} from "oz/governance/utils/IVotes.sol";
import {AngleGovernor} from "contracts/AngleGovernor.sol";
import "contracts/utils/Errors.sol";

struct TestStorage {
Expand Down
109 changes: 109 additions & 0 deletions test/invariant/actors/Proposer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {BaseActor, IERC20, IERC20Metadata, AngleGovernor, TestStorage} from "./BaseActor.t.sol";
import {console} from "forge-std/console.sol";
import {IGovernor} from "oz/governance/IGovernor.sol";
import {ProposalStore, Proposal} from "../stores/ProposalStore.sol";
import {IERC5805} from "oz/interfaces/IERC5805.sol";

contract Proposer is BaseActor {
AngleGovernor internal _angleGovernor;
ProposalStore public proposalStore;
IERC5805 public veANGLEDelegation;

constructor(
AngleGovernor angleGovernor,
IERC20 _agToken,
uint256 nbrVoter,
ProposalStore _proposalStore,
IERC5805 _veANGLEDelegation
) BaseActor(nbrVoter, "Proposer", _agToken) {
_angleGovernor = angleGovernor;
proposalStore = _proposalStore;
veANGLEDelegation = _veANGLEDelegation;
}

function propose(uint256 value) public useActor(1) {
address[] memory targets = new address[](1);
bytes[] memory datas = new bytes[](1);
uint256[] memory values = new uint256[](1);
string memory description = "Test Proposal";
targets[0] = address(_angleGovernor);
datas[0] = "";
values[0] = value;

uint256 proposalId = _angleGovernor.hashProposal(targets, values, datas, keccak256(bytes(description)));
if (_angleGovernor.proposalSnapshot(proposalId) != 0) {
vm.expectRevert(
abi.encodeWithSelector(
IGovernor.GovernorUnexpectedProposalState.selector,
proposalId,
_angleGovernor.state(proposalId),
bytes32(0)
)
);
}
_angleGovernor.propose(targets, values, datas, description);

// Add to the store
proposalStore.addProposal(targets, values, datas, keccak256(bytes(description)));
}

function shortCircuit(uint256 proposalId) public useActor(1) {
if (proposalStore.nbProposals() == 0) {
return;
}
Proposal memory proposal = proposalStore.getRandomProposal(proposalId);
uint256 proposalHash =
_angleGovernor.hashProposal(proposal.target, proposal.value, proposal.data, proposal.description);
IGovernor.ProposalState currentState = _angleGovernor.state(proposalHash);
if (currentState != IGovernor.ProposalState.Active) {
return;
}

uint256 timeElapsed = _angleGovernor.votingDelay() + 1;
uint256 blocksElapsed = (_angleGovernor.votingDelay() + 1) / 12;
vm.warp(block.timestamp + timeElapsed);
vm.roll(block.number + blocksElapsed + 1);

vm.mockCall(
address(veANGLEDelegation),
abi.encodeWithSelector(veANGLEDelegation.getPastVotes.selector, _currentActor),
abi.encode(_angleGovernor.shortCircuitThreshold(_angleGovernor.proposalSnapshot(proposalHash)) + 1)
);
_angleGovernor.castVote(proposalHash, 1);
_angleGovernor.execute(proposal.target, proposal.value, proposal.data, proposal.description);
}

function execute(uint256 proposalId) public useActor(1) {
if (proposalStore.nbProposals() == 0) {
return;
}
Proposal memory proposal = proposalStore.getRandomProposal(proposalId);
uint256 proposalHash =
_angleGovernor.hashProposal(proposal.target, proposal.value, proposal.data, proposal.description);
uint256 proposalSnapshot = _angleGovernor.proposalSnapshot(proposalHash);
vm.warp(_angleGovernor.proposalDeadline(proposalHash) + 1);
vm.roll(_angleGovernor.$snapshotTimestampToSnapshotBlockNumber(proposalSnapshot) + 1);
IGovernor.ProposalState currentState = _angleGovernor.state(proposalHash);
if (currentState != IGovernor.ProposalState.Succeeded) {
vm.expectRevert(
abi.encodeWithSelector(
IGovernor.GovernorUnexpectedProposalState.selector,
proposalHash,
currentState,
bytes32(1 << uint8(IGovernor.ProposalState.Succeeded))
| bytes32(1 << uint8(IGovernor.ProposalState.Queued))
)
);
}
_angleGovernor.execute(proposal.target, proposal.value, proposal.data, proposal.description);
proposalStore.removeProposal(proposalHash);
proposalStore.addOldProposal(proposal.target, proposal.value, proposal.data, proposal.description);
}

function skipVotingDelay() public useActor(1) {
vm.warp(block.timestamp + _angleGovernor.votingDelay() + 1);
}
}
34 changes: 31 additions & 3 deletions test/invariant/actors/Voter.t.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import { BaseActor, IERC20, IERC20Metadata, AngleGovernor, TestStorage } from "./BaseActor.t.sol";
import { console } from "forge-std/console.sol";
import {BaseActor, IERC20, IERC20Metadata, AngleGovernor, TestStorage} from "./BaseActor.t.sol";
import {console} from "forge-std/console.sol";
import {ProposalStore, Proposal} from "../stores/ProposalStore.sol";
import {IGovernor} from "oz/governance/IGovernor.sol";

contract Voter is BaseActor {
AngleGovernor internal _angleGovernor;
ProposalStore public proposalStore;

constructor(AngleGovernor angleGovernor, IERC20 _agToken, uint256 nbrVoter) BaseActor(nbrVoter, "Voter", _agToken) {
constructor(AngleGovernor angleGovernor, IERC20 _agToken, uint256 nbrVoter, ProposalStore _proposalStore)
BaseActor(nbrVoter, "Voter", _agToken)
{
_angleGovernor = angleGovernor;
proposalStore = _proposalStore;
}

function vote(uint256 proposalSeed) public useActor(1) {
if (proposalStore.nbProposals() == 0) {
return;
}
Proposal memory proposal = proposalStore.getRandomProposal(proposalSeed);
uint256 proposalHash =
_angleGovernor.hashProposal(proposal.target, proposal.value, proposal.data, proposal.description);
IGovernor.ProposalState currentState = _angleGovernor.state(proposalHash);
if (currentState != IGovernor.ProposalState.Active) {
vm.expectRevert(
abi.encodeWithSelector(
IGovernor.GovernorUnexpectedProposalState.selector,
proposalHash,
currentState,
bytes32(1 << uint8(IGovernor.ProposalState.Active))
)
);
}
_angleGovernor.castVote(proposalHash, 1);
proposalStore.removeProposal(proposalHash);
}
}
Loading

0 comments on commit e7be47a

Please sign in to comment.