diff --git a/package.json b/package.json index 973b138..621ae9f 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { - "name": "proposal-22-forge-tests", + "name": "proposal-22-patch", "version": "1.0.0", - "repository": "https://git.tornado.ws/Theo/proposal-22-forge-tests.git", + "repository": "https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch", "author": "Theo", "license": "MIT", "scripts": { - "test": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17336117 --gas-report" + "test": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17336117 --no-match-contract TestRelayerBalance --gas-report", + "relayerBalancesSum": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17304722 --match-contract TestRelayerBalance --gas-report" }, "dependencies": { "@gnosis.pm/ido-contracts": "^0.5.0", diff --git a/src/interfaces/IGovernance.sol b/src/interfaces/IGovernance.sol index 2c2fb45..b6036db 100644 --- a/src/interfaces/IGovernance.sol +++ b/src/interfaces/IGovernance.sol @@ -1,6 +1,29 @@ pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +struct Proposal { + // Creator of the proposal + address proposer; + // target addresses for the call to be made + address target; + // The block at which voting begins + uint256 startTime; + // The block at which voting ends: votes must be cast prior to this block + uint256 endTime; + // Current number of votes in favor of this proposal + uint256 forVotes; + // Current number of votes in opposition to this proposal + uint256 againstVotes; + // Flag marking whether the proposal has been executed + bool executed; + // Flag marking whether the proposal voting time has been extended + // Voting time can be extended once, if the proposal outcome has changed during CLOSING_PERIOD + bool extended; +} interface IGovernance { + function proposals(uint256 index) external view returns (Proposal memory); + function lockedBalance(address account) external view returns (uint256); function propose(address target, string memory description) external returns (uint256); diff --git a/test/Mock.sol b/test/Mock.sol new file mode 100644 index 0000000..9e7a181 --- /dev/null +++ b/test/Mock.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +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) + + 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; + + uint256 public constant ATTACKER_PROPOSAL_ID = 21; // Last attacker proposal (to restore Governance Vault balance) id + + uint256 public constant PROPOSAL_VOTING_DURATION = 5 days; + uint256 public constant PROPOSAL_LOCKED_DURATION = 2 days; + uint256 public constant PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION; + uint256 public constant PROPOSAL_EXECUTION_MAX_DURATION = 3 days; + uint256 public constant PROPOSAL_THRESHOLD = 25_000 ether; + string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}"; + + address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; + + 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); +} diff --git a/test/MockProposal.sol b/test/MockProposal.sol index 0db922e..767a40c 100644 --- a/test/MockProposal.sol +++ b/test/MockProposal.sol @@ -1,103 +1,41 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; - -import {Parameters} from "@proprietary/Parameters.sol"; -import {IGovernance} from "@interfaces/IGovernance.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {Test} from "@forge-std/Test.sol"; - -contract MockProposal is Parameters, Test { - IGovernance internal governance = IGovernance(_governanceAddress); - - 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; - - uint256 public constant PROPOSAL_VOTING_DURATION = 5 days; - uint256 public constant PROPOSAL_LOCKED_DURATION = 2 days; - uint256 public constant PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION; - uint256 public constant PROPOSAL_EXECUTION_MAX_DURATION = 3 days; - uint256 public constant PROPOSAL_THRESHOLD = 25000 ether; - string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}"; - - address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; - - 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 executeProposal(address proposalAddress) public { - uint256 proposalId = voteAndCreateProposal(proposalAddress); - - governance.execute(proposalId); - } - - function waitUntilExecutable() internal { - vm.warp(block.timestamp + PROPOSAL_DURATION); - } - - function voteAndCreateProposal(address proposalAddress) public returns (uint256) { - retrieveAndLockBalance(TEST_PRIVATE_KEY_ONE, TEST_ADDRESS_ONE, PROPOSAL_THRESHOLD); - retrieveAndLockBalance(TEST_PRIVATE_KEY_TWO, TEST_ADDRESS_TWO, 1 ether); - - /* ----------PROPOSER------------ */ - vm.startPrank(TEST_ADDRESS_ONE); - - uint256 proposalId = IGovernance(_governanceAddress).propose(proposalAddress, PROPOSAL_DESCRIPTION); - - // TIME-TRAVEL - vm.warp(block.timestamp + 6 hours); - - IGovernance(_governanceAddress).castVote(proposalId, true); - - vm.stopPrank(); - /* ------------------------------ */ - - /* -------------VOTER-------------*/ - vm.startPrank(TEST_ADDRESS_TWO); - IGovernance(_governanceAddress).castVote(proposalId, true); - vm.stopPrank(); - /* ------------------------------ */ - - return proposalId; - } - - function retrieveAndLockBalance(uint256 privateKey, address voter, uint256 amount) internal { - uint256 lockTimestamp = block.timestamp + PROPOSAL_DURATION; - - bytes32 messageHash = keccak256( - abi.encodePacked( - PERMIT_FUNC_SELECTOR, - EIP712_DOMAIN, - keccak256(abi.encode(PERMIT_TYPEHASH, voter, _governanceAddress, amount, 0, lockTimestamp)) - ) - ); - - /* ----------GOVERNANCE------- */ - vm.startPrank(_governanceAddress); - IERC20(_tokenAddress).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(); - /* ----------------------------*/ - } -} +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +import { Parameters } from "@proprietary/Parameters.sol"; +import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol"; +import { PatchProposal, PatchProposalContractsFactory } from "@root/v4-patch/PatchProposal.sol"; +import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol"; +import { ProposalUtils } from "./ProposalUtils.sol"; + +import { Test } from "@forge-std/Test.sol"; + +contract MockProposal is Parameters, Test, ProposalUtils { + modifier executeCurrentProposalBefore() { + createAndExecuteProposal(); + _; + } + + modifier executeAttackerProposalBefore() { + waitUntilExecutable(ATTACKER_PROPOSAL_ID); + governance.execute(ATTACKER_PROPOSAL_ID); + _; + } + + function createAndExecuteProposal() public { + address patchProposalFactoryAddress = address(new PatchProposalContractsFactory()); + address proposalAddress = address(new PatchProposal(patchProposalFactoryAddress)); + + proposeAndExecute(proposalAddress); + } + + function getRelayerRegistryProxyAddress() internal view returns (address) { + TornadoStakingRewards actualStakingContract = TornadoStakingRewards(getStakingProxyAddress()); + + return actualStakingContract.relayerRegistry(); + } + + function getStakingProxyAddress() internal view returns (address) { + return address(governance.Staking()); + } +} diff --git a/test/ProposalUtils.sol b/test/ProposalUtils.sol new file mode 100644 index 0000000..9e4f7a0 --- /dev/null +++ b/test/ProposalUtils.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +import { Test } from "@forge-std/Test.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { Mock } from "./Mock.sol"; +import { Proposal, IGovernance } from "@interfaces/IGovernance.sol"; +import { Parameters } from "@proprietary/Parameters.sol"; +import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol"; + +contract ProposalUtils is Mock, Parameters, Test { + GovernancePatchUpgrade internal governance = GovernancePatchUpgrade(payable(_governanceAddress)); + + function getProposalExecutableTime(uint256 proposalId) internal view returns (uint256) { + Proposal memory proposal = IGovernance(_governanceAddress).proposals(proposalId); + return proposal.endTime + PROPOSAL_LOCKED_DURATION + 1 hours; + } + + function waitUntilExecutable(uint256 proposalId) internal { + uint256 proposalExecutableTime = getProposalExecutableTime(proposalId); + require(block.timestamp < proposalExecutableTime, "Too late to execute proposal"); + + vm.warp(proposalExecutableTime); + } + + function proposeAndVote(address proposalAddress) public returns (uint256) { + retrieveAndLockBalance(TEST_PRIVATE_KEY_ONE, TEST_ADDRESS_ONE, PROPOSAL_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 retrieveAndLockBalance(uint256 privateKey, address voter, uint256 amount) internal { + uint256 lockTimestamp = block.timestamp + PROPOSAL_DURATION; + + bytes32 messageHash = keccak256( + abi.encodePacked( + PERMIT_FUNC_SELECTOR, + EIP712_DOMAIN, + keccak256(abi.encode(PERMIT_TYPEHASH, voter, _governanceAddress, amount, 0, lockTimestamp)) + ) + ); + + /* ----------GOVERNANCE------- */ + vm.startPrank(_governanceAddress); + IERC20(_tokenAddress).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(); + /* ----------------------------*/ + } + + function proposeAndExecute(address proposalAddress) public { + uint256 proposalId = proposeAndVote(proposalAddress); + + waitUntilExecutable(proposalId); + governance.execute(proposalId); + } +}