Compare commits

...

3 Commits
main ... main

Author SHA1 Message Date
AlienTornadosaurusHex
15f06e5e8c delimit before / after
Signed-off-by: AlienTornadosaurusHex <>
2023-05-27 22:35:19 +00:00
AlienTornadosaurusHex
f165ff7be4 improve tests with better descriptions and more state assertions
Signed-off-by: AlienTornadosaurusHex <>
2023-05-26 00:11:47 +00:00
AlienTornadosaurusHex
ed28aa6eff Remove ForkBased - for now
Signed-off-by: AlienTornadosaurusHex <>
2023-05-23 21:52:17 +00:00
7 changed files with 120 additions and 41 deletions

3
.gitignore vendored

@ -1,2 +1,3 @@
out out
cache cache
FORUM_POST.md

@ -5,24 +5,18 @@
## Installation ## Installation
``` ```bash
git clone --recurse-submodules https://git.tornado.ws/Theo/proposal-21-test.git git clone --recurse-submodules https://git.tornado.ws/AlienTornadosaurusHex/proposal-21-test.git
``` ```
## Testing ## Testing
``` ```bash
npm run test yarn # or npm
``` # To test slots
yarn test:slots # or npm
## Test logic # To test execution of the proposal and balance changes
yarn test:execution
Hacker added 1 200 000 TORN tokens to staking directly, 10 000 to 100 accounts and 200 000 to one account. # To test both
He then withdrew 483,000 TORN from the Governance Vault, however, 717,000 remained. yarn test
```
We need to check, that:
1. All attacker staked TORNs after proposal 21 will be nullified;
2. Governance Vault will be replenished from Governance for 483 000 TORN - the entire amount that he withdrew, to set the ratio of funds locked in staking contract to real tokens in Vault 1: 1
If all tests passed, all good.

@ -9,12 +9,12 @@
"test": "npm run test:slots & npm run test:execution", "test": "npm run test:slots & npm run test:execution",
"build": "forge build --optimize", "build": "forge build --optimize",
"test:slots": "forge test -vv --match-contract StorageBased", "test:slots": "forge test -vv --match-contract StorageBased",
"test:execution": "forge test -vvv --fork-url https://rpc.mevblocker.io --block-number 17315182 --match-contract ExecutionBased" "test:execution": "forge test -vv --fork-url https://rpc.mevblocker.io --block-number 17315182 --match-contract ExecutionBased"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.tornado.ws/Theo/proposal-21-test" "url": "https://git.tornado.ws/Theo/proposal-21-test"
}, },
"author": "Theo", "author": "Theo & AlienTornadosaurusHex",
"license": "MIT" "license": "MIT"
} }

@ -21,6 +21,20 @@ struct Proposal {
} }
interface IGovernance { interface IGovernance {
function initialized() external view returns (bool);
function initializing() external view returns (bool);
function EXECUTION_DELAY() external view returns (uint256);
function EXECUTION_EXPIRATION() external view returns (uint256);
function QUORUM_VOTES() external view returns (uint256);
function PROPOSAL_THRESHOLD() external view returns (uint256);
function VOTING_DELAY() external view returns (uint256);
function VOTING_PERIOD() external view returns (uint256);
function CLOSING_PERIOD() external view returns (uint256);
function VOTE_EXTEND_TIME() external view returns (uint256);
function torn() external view returns (address);
function proposals(uint256 index) external view returns (Proposal memory); function proposals(uint256 index) external view returns (Proposal memory);
function lockedBalance(address account) external view returns (uint256); function lockedBalance(address account) external view returns (uint256);

@ -24,6 +24,7 @@ contract ExecutionBased is Test, Parameters, Mock {
} }
function testExistentHackerProposal() public conditionStateChecks { function testExistentHackerProposal() public conditionStateChecks {
storeStateBefore();
waitUntilExecutable(_attackerProposalId); waitUntilExecutable(_attackerProposalId);
governance.execute(_attackerProposalId); governance.execute(_attackerProposalId);
} }
@ -37,26 +38,88 @@ contract ExecutionBased is Test, Parameters, Mock {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PREDICATES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PREDICATES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function intro() internal {
console2.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EXECUTION TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
console2.log("");
console2.log(
"It is expected that the attackers balances are nullified and that the Vault receives 483,000 TORN,"
);
console2.log("and that the Governance TORN balance does not otherwise change, or any other state.");
console2.log("");
}
function outro() internal {
console2.log("");
console2.log("All state other state checks completed successfully.");
console2.log("");
}
function checkCurrentState() internal { function checkCurrentState() internal {
console2.log("Current attacker locked balance: %s TORN", getAttackerLockedBalance() / 10 ** 18); intro();
console2.log("Current governance Vault balance: %s TORN", getGovernanceVaultBalance() / 10 ** 18); console2.log("Governance balance before execution: %s TORN", getGovernanceBalance() / 10 ** 18);
console2.log("Governance Vault balance before execution: %s TORN", getGovernanceVaultBalance() / 10 ** 18);
console2.log("Attacker locked balance before execution: %s TORN", getAttackerLockedBalance() / 10 ** 18);
} }
function checkResults() internal { function checkResults() internal {
uint256 attackerBalanceAfterProposalExecution = getAttackerLockedBalance(); uint256 governanceBalanceAfterProposalExecution = getGovernanceBalance();
uint256 governanceVaultBalanceAfterProposalExecution = getGovernanceVaultBalance(); uint256 governanceVaultBalanceAfterProposalExecution = getGovernanceVaultBalance();
uint256 attackerBalanceAfterProposalExecution = getAttackerLockedBalance();
console2.log("");
console2.log( console2.log(
"Attacker locked balance after proposal 21 execution: %s TORN", "Governance balance after the execution of Proposal #21: %s TORN",
attackerBalanceAfterProposalExecution / 10 ** 18 governanceBalanceAfterProposalExecution / 10 ** 18
); );
console2.log( console2.log(
"Governance Vault balance after proposal 21 execution: %s TORN", "Governance Vault balance after the execution of Proposal #21: %s TORN",
governanceVaultBalanceAfterProposalExecution / 10 ** 18 governanceVaultBalanceAfterProposalExecution / 10 ** 18
); );
console2.log(
"Attacker locked balance after the execution of Proposal #21: %s TORN",
attackerBalanceAfterProposalExecution / 10 ** 18
);
require(attackerBalanceAfterProposalExecution == 0 ether); require(uint8(uint256(bytes32(vm.load(_governanceAddress, bytes32(0))))) == 1);
require(_EXECUTION_DELAY == governance.EXECUTION_DELAY());
require(_EXECUTION_EXPIRATION == governance.EXECUTION_EXPIRATION());
require(_QUORUM_VOTES == governance.QUORUM_VOTES());
require(_PROPOSAL_THRESHOLD == governance.PROPOSAL_THRESHOLD());
require(_VOTING_DELAY == governance.VOTING_DELAY());
require(_VOTING_PERIOD == governance.VOTING_PERIOD());
require(_CLOSING_PERIOD == governance.CLOSING_PERIOD());
require(_VOTE_EXTEND_TIME == governance.VOTE_EXTEND_TIME());
require(governanceBalanceAfterProposalExecution == _governanceBalanceBefore - attackerWithdrawnAmount);
require(governanceVaultBalanceAfterProposalExecution == currentGovernanceVaultBalance + attackerWithdrawnAmount); require(governanceVaultBalanceAfterProposalExecution == currentGovernanceVaultBalance + attackerWithdrawnAmount);
require(attackerBalanceAfterProposalExecution == 0 ether);
require(address(governance.torn()) == _tokenAddress);
outro();
}
uint256 internal _governanceBalanceBefore;
uint256 internal _EXECUTION_DELAY;
uint256 internal _EXECUTION_EXPIRATION;
uint256 internal _QUORUM_VOTES;
uint256 internal _PROPOSAL_THRESHOLD;
uint256 internal _VOTING_DELAY;
uint256 internal _VOTING_PERIOD;
uint256 internal _CLOSING_PERIOD;
uint256 internal _VOTE_EXTEND_TIME;
function storeStateBefore() internal {
_governanceBalanceBefore = getGovernanceBalance();
_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();
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -133,6 +196,10 @@ contract ExecutionBased is Test, Parameters, Mock {
return attackerLockedBalance; return attackerLockedBalance;
} }
function getGovernanceBalance() internal returns (uint256) {
return IERC20(_tokenAddress).balanceOf(_governanceAddress);
}
function getGovernanceVaultBalance() internal returns (uint256) { function getGovernanceVaultBalance() internal returns (uint256) {
return IERC20(_tokenAddress).balanceOf(_governanceVaultAddress); return IERC20(_tokenAddress).balanceOf(_governanceVaultAddress);
} }

@ -1,14 +0,0 @@
pragma solidity 0.8.17;
import "@interfaces/IGovernance.sol";
import "@interfaces/IERC20.sol";
import "@proprietary/Parameters.sol";
import "@proprietary/Mock.sol";
import "@forge-std/Test.sol";
import "@forge-std/console2.sol";
contract ForkBased is Test {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}

@ -13,6 +13,8 @@ contract StorageBased is Test, Parameters {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function testSlotsMustBeEqualToAddresses() public delimit { function testSlotsMustBeEqualToAddresses() public delimit {
console2.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SLOT TO ADDRESS EQUALITY TEST ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
console2.log("");
console2.log( console2.log(
"Follow this link to find the storage slots modified during the attack (out of order) => https://etherscan.io/tx/0x3274b6090685b842aca80b304a4dcee0f61ef8b6afee10b7c7533c32fb75486d#statechange\n" "Follow this link to find the storage slots modified during the attack (out of order) => https://etherscan.io/tx/0x3274b6090685b842aca80b304a4dcee0f61ef8b6afee10b7c7533c32fb75486d#statechange\n"
); );
@ -34,14 +36,29 @@ contract StorageBased is Test, Parameters {
console2.log("Should equal storage address =>"); console2.log("Should equal storage address =>");
console2.logBytes32(expectedStorageAddress); console2.logBytes32(expectedStorageAddress);
console2.log("Is it? => ", storageAddress == expectedStorageAddress); console2.log("Is it? => ", storageAddress == expectedStorageAddress);
require(storageAddress == expectedStorageAddress);
} }
console2.log("");
console2.log("All 101 slots have been determined to belong to an address being nullified.");
console2.log("");
} }
function testComputeProposalExecutedSlot() public delimit { function testComputeProposalExecutedSlot() public delimit {
console2.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PROPOSAL EXECUTED SLOT TEST ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
console2.log("");
console2.log("The former test does not account for 1 more storage slot modified in governance.\n"); console2.log("The former test does not account for 1 more storage slot modified in governance.\n");
console2.log("This is storage slot 0xece66cfdbd22e3f37d348a3d8e19074452862cd65fd4b9a11f0336d1ac6d1e69."); console2.log("This is storage slot 0xece66cfdbd22e3f37d348a3d8e19074452862cd65fd4b9a11f0336d1ac6d1e69.");
console2.log("Computed:"); console2.log("Computed:");
console2.logBytes32(bytes32(uint256(keccak256(abi.encode(20 | 61))) + 166)); console2.logBytes32(bytes32(uint256(keccak256(abi.encode(20 | 61))) + 166));
require(
(bytes32(uint256(keccak256(abi.encode(20 | 61))) + 166))
== bytes32(0xece66cfdbd22e3f37d348a3d8e19074452862cd65fd4b9a11f0336d1ac6d1e69)
);
console2.log("");
console2.log("The slot belongs to proposal.executed = true, this is a successful test.");
console2.log("");
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~