Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

7 changed files with 43 additions and 122 deletions

3
.gitignore vendored

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

@ -5,18 +5,24 @@
## Installation ## Installation
```bash ```
git clone --recurse-submodules https://git.tornado.ws/AlienTornadosaurusHex/proposal-21-test.git git clone --recurse-submodules https://git.tornado.ws/Theo/proposal-21-test.git
``` ```
## Testing ## Testing
```bash ```
yarn # or npm npm run test
# To test slots ```
yarn test:slots # or npm
# To test execution of the proposal and balance changes ## Test logic
yarn test:execution
# To test both Hacker added 1 200 000 TORN tokens to staking directly, 10 000 to 100 accounts and 200 000 to one account.
yarn test He then withdrew 483,000 TORN from the Governance Vault, however, 717,000 remained.
```
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 -vv --fork-url https://rpc.mevblocker.io --block-number 17315182 --match-contract ExecutionBased" "test:execution": "forge test -vvv --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 & AlienTornadosaurusHex", "author": "Theo",
"license": "MIT" "license": "MIT"
} }

@ -21,20 +21,6 @@ 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,7 +24,6 @@ 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);
} }
@ -38,88 +37,26 @@ 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 {
intro(); console2.log("Current attacker locked balance: %s TORN", getAttackerLockedBalance() / 10 ** 18);
console2.log("Governance balance before execution: %s TORN", getGovernanceBalance() / 10 ** 18); console2.log("Current governance Vault balance: %s TORN", getGovernanceVaultBalance() / 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 governanceBalanceAfterProposalExecution = getGovernanceBalance();
uint256 governanceVaultBalanceAfterProposalExecution = getGovernanceVaultBalance();
uint256 attackerBalanceAfterProposalExecution = getAttackerLockedBalance(); uint256 attackerBalanceAfterProposalExecution = getAttackerLockedBalance();
uint256 governanceVaultBalanceAfterProposalExecution = getGovernanceVaultBalance();
console2.log("");
console2.log( console2.log(
"Governance balance after the execution of Proposal #21: %s TORN", "Attacker locked balance after proposal 21 execution: %s TORN",
governanceBalanceAfterProposalExecution / 10 ** 18
);
console2.log(
"Governance Vault balance after the execution of Proposal #21: %s TORN",
governanceVaultBalanceAfterProposalExecution / 10 ** 18
);
console2.log(
"Attacker locked balance after the execution of Proposal #21: %s TORN",
attackerBalanceAfterProposalExecution / 10 ** 18 attackerBalanceAfterProposalExecution / 10 ** 18
); );
console2.log(
"Governance Vault balance after proposal 21 execution: %s TORN",
governanceVaultBalanceAfterProposalExecution / 10 ** 18
);
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(attackerBalanceAfterProposalExecution == 0 ether); require(attackerBalanceAfterProposalExecution == 0 ether);
require(governanceVaultBalanceAfterProposalExecution == currentGovernanceVaultBalance + attackerWithdrawnAmount);
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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -196,10 +133,6 @@ 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);
} }

14
test/ForkBased.sol Normal file

@ -0,0 +1,14 @@
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,8 +13,6 @@ 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"
); );
@ -36,29 +34,14 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~