generated from AngleProtocol/boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tests: add governor actors and basic mainnet invariant
- Loading branch information
1 parent
aa10c05
commit e7be47a
Showing
7 changed files
with
342 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.