From a8c9172e8fd916a3e235d5f9f7377ab187238e3b Mon Sep 17 00:00:00 2001 From: Theo Date: Tue, 6 Jun 2023 13:53:08 -0700 Subject: [PATCH] Move all utils for propose/testing to 'test/utils', split functions into different contracts and files by functionality --- test/ExampleProposal.t.sol | 24 ++++++ test/TornadoProposalTest.sol | 149 ----------------------------------- test/utils/Mock.sol | 20 +++++ test/utils/MockProposal.sol | 36 +++++++++ test/utils/ProposalUtils.sol | 62 +++++++++++++++ test/utils/Utils.sol | 57 ++++++++++++++ 6 files changed, 199 insertions(+), 149 deletions(-) create mode 100644 test/ExampleProposal.t.sol delete mode 100644 test/TornadoProposalTest.sol create mode 100644 test/utils/Mock.sol create mode 100644 test/utils/MockProposal.sol create mode 100644 test/utils/ProposalUtils.sol create mode 100644 test/utils/Utils.sol diff --git a/test/ExampleProposal.t.sol b/test/ExampleProposal.t.sol new file mode 100644 index 0000000..9efd7bc --- /dev/null +++ b/test/ExampleProposal.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { ProposalUtils } from "./utils/ProposalUtils.sol"; +import { ExampleProposal } from "@root/ExampleProposal.sol"; + +import { console2 } from "@forge-std/console2.sol"; + +contract TestExampleProposal is ProposalUtils { + modifier executeCurrentProposalBefore() { + createAndExecuteProposal(); + _; + } + + function createAndExecuteProposal() public { + address proposalAddress = address(new ExampleProposal()); /* your proposal initialization */ + + proposeAndExecute(proposalAddress); + } + + /* your tests */ + + function testProposal() public executeCurrentProposalBefore { } +} diff --git a/test/TornadoProposalTest.sol b/test/TornadoProposalTest.sol deleted file mode 100644 index 2a7ad8d..0000000 --- a/test/TornadoProposalTest.sol +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {IGovernance, Proposal} from "common/interfaces/IGovernance.sol"; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; - -import {Test} from "forge-std/Test.sol"; - -import {TornadoAddresses} from "common/TornadoAddresses.sol"; - -contract TornadoProposalTest is Test, TornadoAddresses { - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PERMIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - bytes32 public constant PERMIT_TYPEHASH = - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - - bytes32 public constant EIP712_DOMAIN = keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes("TornadoCash")), - keccak256(bytes("1")), - 1, - VERIFIER_ADDRESS - ) - ); - - uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TEST DUMMIES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58; - - address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth (just for testing) - - uint256 public constant TEST_PRIVATE_KEY_ONE = 0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0; - uint256 public constant TEST_PRIVATE_KEY_TWO = 0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064; - - address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88; - address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20; - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ADDRESSES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GOVERNANCE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}"; - - uint256 public EXECUTION_DELAY; - uint256 public EXECUTION_EXPIRATION; - uint256 public QUORUM_VOTES; - uint256 public PROPOSAL_THRESHOLD; - uint256 public VOTING_DELAY; - uint256 public VOTING_PERIOD; - uint256 public CLOSING_PERIOD; - uint256 public VOTE_EXTEND_TIME; - - IGovernance public immutable governance = IGovernance(getGovernanceProxyAddress()); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TEST UTILS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - function setUp() public virtual { - vm.createSelectFork(vm.envString("MAINNET_RPC_URL")); - _fetchConfiguration(); - } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - function waitUntilExecutable(uint256 proposalId) internal { - uint256 proposalExecutableTime = getProposalExecutableTime(proposalId); - require(block.timestamp < proposalExecutableTime, "Too late to execute proposal"); - vm.warp(proposalExecutableTime); - } - - function easyPropose(address proposalAddress) public returns (uint256) { - retrieveAndLockBalance(TEST_PRIVATE_KEY_ONE, TEST_ADDRESS_ONE, QUORUM_VOTES); - retrieveAndLockBalance(TEST_PRIVATE_KEY_TWO, TEST_ADDRESS_TWO, 1 ether); - - /* ----------PROPOSER------------ */ - vm.startPrank(TEST_ADDRESS_ONE); - - uint256 proposalId = governance.propose(proposalAddress, PROPOSAL_DESCRIPTION); - - // TIME-TRAVEL - vm.warp(block.timestamp + 6 hours); - - governance.castVote(proposalId, true); - - vm.stopPrank(); - /* ------------------------------ */ - - /* -------------VOTER-------------*/ - vm.startPrank(TEST_ADDRESS_TWO); - governance.castVote(proposalId, true); - vm.stopPrank(); - /* ------------------------------ */ - - return proposalId; - } - - function retrieveAndLockBalance(uint256 privateKey, address voter, uint256 amount) internal { - uint256 lockTimestamp = block.timestamp + VOTING_PERIOD + EXECUTION_DELAY + 1 hours; - uint256 accountNonce = ERC20Permit(getTornTokenAddress()).nonces(voter); - - bytes32 messageHash = keccak256( - abi.encodePacked( - PERMIT_FUNC_SELECTOR, - EIP712_DOMAIN, - keccak256( - abi.encode(PERMIT_TYPEHASH, voter, getGovernanceProxyAddress(), amount, accountNonce, lockTimestamp) - ) - ) - ); - - /* ----------GOVERNANCE------- */ - vm.startPrank(getGovernanceProxyAddress()); - IERC20(getTornTokenAddress()).transfer(voter, amount); - vm.stopPrank(); - /* ----------------------------*/ - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, messageHash); - - /* ----------VOTER------------ */ - vm.startPrank(voter); - governance.lock(voter, amount, lockTimestamp, v, r, s); - vm.stopPrank(); - /* ----------------------------*/ - } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - function getProposalExecutableTime(uint256 proposalId) internal view returns (uint256) { - Proposal memory proposal = IGovernance(getGovernanceProxyAddress()).proposals(proposalId); - return proposal.endTime + EXECUTION_DELAY + 1 hours; - } - - function _fetchConfiguration() internal { - EXECUTION_DELAY = governance.EXECUTION_DELAY(); - EXECUTION_EXPIRATION = governance.EXECUTION_EXPIRATION(); - QUORUM_VOTES = governance.QUORUM_VOTES(); - PROPOSAL_THRESHOLD = governance.PROPOSAL_THRESHOLD(); - VOTING_DELAY = governance.VOTING_DELAY(); - VOTING_PERIOD = governance.VOTING_PERIOD(); - CLOSING_PERIOD = governance.CLOSING_PERIOD(); - VOTE_EXTEND_TIME = governance.VOTE_EXTEND_TIME(); - } -} diff --git a/test/utils/Mock.sol b/test/utils/Mock.sol new file mode 100644 index 0000000..4144fa9 --- /dev/null +++ b/test/utils/Mock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IGovernance } from "@interfaces/IGovernance.sol"; + +contract Mock { + // Developer address with 22 staked TORN + address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58; + address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth (just for testing) + + // Address and private key to test staking, Governance lock and rewards accruals + address public constant TEST_STAKER_ADDRESS = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + uint256 public constant TEST_STAKER_PRIVATE_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + + // Two test accounts to create proposal and vote for it + uint256 public constant TEST_PRIVATE_KEY_ONE = 0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0; + uint256 public constant TEST_PRIVATE_KEY_TWO = 0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064; + address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88; + address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20; +} diff --git a/test/utils/MockProposal.sol b/test/utils/MockProposal.sol new file mode 100644 index 0000000..66c06dd --- /dev/null +++ b/test/utils/MockProposal.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IGovernance } from "@interfaces/IGovernance.sol"; +import { TornadoAddresses } from "@proprietary/TornadoAddresses.sol"; + +import { Test } from "@forge-std/Test.sol"; +import { console2 } from "@forge-std/console2.sol"; + +contract MockProposal is TornadoAddresses, Test { + uint256 public PROPOSAL_VOTING_DURATION; + uint256 public PROPOSAL_LOCKED_DURATION; + uint256 public PROPOSAL_DURATION; + uint256 public PROPOSAL_EXECUTION_MAX_DURATION; + uint256 public PROPOSAL_QOURUM_THRESHOLD; + string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}"; + + function _fetchConfiguration() internal { + IGovernance governance = IGovernance(governanceAddress); + + PROPOSAL_LOCKED_DURATION = governance.EXECUTION_DELAY(); + PROPOSAL_EXECUTION_MAX_DURATION = governance.EXECUTION_EXPIRATION(); + PROPOSAL_QOURUM_THRESHOLD = governance.QUORUM_VOTES(); + PROPOSAL_VOTING_DURATION = governance.VOTING_DELAY(); + PROPOSAL_VOTING_DURATION = governance.VOTING_PERIOD(); + PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION; + } + + function setUp() public virtual { + // If fork block number unitialized, then set latest + if (block.number == 1) vm.createSelectFork(vm.rpcUrl("mainnet")); + else vm.createSelectFork(vm.rpcUrl("mainnet"), block.number); + + _fetchConfiguration(); + } +} diff --git a/test/utils/ProposalUtils.sol b/test/utils/ProposalUtils.sol new file mode 100644 index 0000000..a2491a8 --- /dev/null +++ b/test/utils/ProposalUtils.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Utils } from "./Utils.sol"; +import { Proposal, IGovernance } from "@interfaces/IGovernance.sol"; + +contract ProposalUtils is Utils { + IGovernance internal governance = IGovernance(governanceAddress); + + function getProposalExecutableTime(uint256 proposalId) internal view returns (uint256) { + Proposal memory proposal = getProposal(proposalId); + return proposal.endTime + PROPOSAL_LOCKED_DURATION + 1 seconds; + } + + function getProposal(uint256 proposalId) internal view returns (Proposal memory) { + return governance.proposals(proposalId); + } + + function hasProposal(uint256 proposalId) internal view returns (bool) { + return governance.proposalCount() >= proposalId; + } + + function waitUntilExecutable(uint256 proposalId) internal { + uint256 proposalExecutableTime = getProposalExecutableTime(proposalId); + require(block.timestamp < proposalExecutableTime + PROPOSAL_EXECUTION_MAX_DURATION, "Too late to execute proposal"); + + vm.warp(proposalExecutableTime); + } + + function proposeAndVote(address proposalAddress) public returns (uint256) { + retrieveAndLockBalance(TEST_PRIVATE_KEY_ONE, TEST_ADDRESS_ONE, PROPOSAL_QOURUM_THRESHOLD); + retrieveAndLockBalance(TEST_PRIVATE_KEY_TWO, TEST_ADDRESS_TWO, 1 ether); + + /* ----------PROPOSER------------ */ + vm.startPrank(TEST_ADDRESS_ONE); + + uint256 proposalId = governance.propose(proposalAddress, PROPOSAL_DESCRIPTION); + + // TIME-TRAVEL + vm.warp(block.timestamp + 6 hours); + + governance.castVote(proposalId, true); + + vm.stopPrank(); + /* ------------------------------ */ + + /* -------------VOTER-------------*/ + vm.startPrank(TEST_ADDRESS_TWO); + governance.castVote(proposalId, true); + vm.stopPrank(); + /* ------------------------------ */ + + return proposalId; + } + + function proposeAndExecute(address proposalAddress) public { + uint256 proposalId = proposeAndVote(proposalAddress); + + waitUntilExecutable(proposalId); + governance.execute(proposalId); + } +} diff --git a/test/utils/Utils.sol b/test/utils/Utils.sol new file mode 100644 index 0000000..51b1acd --- /dev/null +++ b/test/utils/Utils.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import { Test } from "@forge-std/Test.sol"; + +import { TornadoAddresses } from "@proprietary/TornadoAddresses.sol"; +import { Mock } from "./Mock.sol"; +import { MockProposal } from "./MockProposal.sol"; +import { IGovernance } from "@interfaces/IGovernance.sol"; + +contract Utils is TornadoAddresses, Test, Mock, MockProposal { + address public constant VERIFIER_ADDRESS = tornTokenAddress; + + bytes32 public constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + bytes32 public constant EIP712_DOMAIN = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes("TornadoCash")), + keccak256(bytes("1")), + 1, + VERIFIER_ADDRESS + ) + ); + + uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901); + + function retrieveAndLockBalance(uint256 privateKey, address voter, uint256 amount) public { + uint256 lockTimestamp = block.timestamp + PROPOSAL_DURATION; + uint256 accountNonce = ERC20Permit(tornTokenAddress).nonces(voter); + + bytes32 messageHash = keccak256( + abi.encodePacked( + PERMIT_FUNC_SELECTOR, + EIP712_DOMAIN, + keccak256(abi.encode(PERMIT_TYPEHASH, voter, governanceAddress, amount, accountNonce, lockTimestamp)) + ) + ); + + /* ----------GOVERNANCE------- */ + vm.startPrank(governanceAddress); + IERC20(tornTokenAddress).transfer(voter, amount); + vm.stopPrank(); + /* ----------------------------*/ + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, messageHash); + + /* ----------VOTER------------ */ + vm.startPrank(voter); + IGovernance(governanceAddress).lock(voter, amount, lockTimestamp, v, r, s); + vm.stopPrank(); + /* ----------------------------*/ + } +}