Compare commits

..

15 Commits

34 changed files with 5877 additions and 6529 deletions

3
.gitignore vendored
View File

@@ -4,7 +4,7 @@ out/
node_modules/ node_modules/
# Ignores development broadcast logs # Ignores development broadcast logs
!/broadcast /broadcast
/broadcast/*/31337/ /broadcast/*/31337/
/broadcast/**/dry-run/ /broadcast/**/dry-run/
@@ -16,3 +16,4 @@ docs/
# yarn # yarn
yarn-error* yarn-error*
yarn.lock

View File

@@ -1,3 +1,46 @@
# proposal-22-forge-tests # Governance upgrade to patch exploit
Please do not format the contracts. Besides [this repository](https://git.tornado.ws/AlienTornadosaurusHex/tornado-governance), this is another test suite. ### Major changes
1. Adding protection from metamorphic contracts to Governance voting process;
2. Redeploying Governance Staking proxy contract to nullify bugged rewards;
3. Return of tokens lost due to a bug in Governance Staking;
4. Redeploying Governance Staking logic contract and Relayer Registry logic contract to change the staking address to the current one.
### Requirements
- Rust ([Any system](https://doc.rust-lang.org/cargo/getting-started/installation.html))
- Foundryup ([Windows](https://github.com/altugbakan/foundryup-windows), [Linux](https://book.getfoundry.sh/getting-started/installation))
- Node 14 or higher ([Windows](https://github.com/coreybutler/nvm-windows), [Linux](https://github.com/nvm-sh/nvm))
### Installation
```text
git clone --recurse-submodules https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch
cd proposal-22-governance-and-rewards-patch
npm install
```
### Testing
```text
npm run test
```
### Contracts info
The contracts can be currently found here:
- [GovernancePatchUpgrade](https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch/src/branch/main/src/v4-patch/GovernancePatchUpgrade.sol)
- [PatchProposal](https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch/src/branch/main/src/v4-patch/PatchProposal.sol)
Inlined version of the `RelayerRegistry` and `TornadoStakingRewards` are also used. Check the `diffs` folder to see how much they deviate from the deployed contract implementations.
For testing resistance against metamorphic contracts, we use the contracts provided by: https://github.com/0age/metamorphic.git
##### Deployed contracts
- [Governance logic (implementation) contract](https://etherscan.io/address/0xba178126c28f50ee60322a82f5ebcd6b3711e101#code)
- [Staking proxy contract](https://etherscan.io/address/0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29#code)
- [Staking logic (implementation) contract](https://etherscan.io/address/0xefbea4ec481c2467a1a94d94bc54f111f6a7345f#code)
- [Relayer Registry logic (implementation) contract](https://etherscan.io/address/0xe27b91724c55e950f68b394f33fa3b86693179c0#code)

View File

@@ -9,7 +9,7 @@ optimizer-runs = 10_000_000
[fmt] [fmt]
line_length = 140 line_length = 110
bracket_spacing = true bracket_spacing = true
multiline_func_header = 'attributes_first' multiline_func_header = 'attributes_first'
number_underscore = 'thousands' number_underscore = 'thousands'

4963
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,12 @@
"author": "Theo", "author": "Theo",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17336117 --no-match-contract TestRelayerBalance --gas-report", "test": "npm run test:all",
"relayerBalancesSum": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17304425 --match-contract TestRelayerBalance --gas-report" "test:all": "npm run test:beforeProposed && npm run test:afterProposed",
"test:beforeProposed": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17336117 --no-match-contract TestRelayerBalance",
"test:afterProposed": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17387853 --no-match-contract TestRelayerBalance",
"test:gas": "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": { "dependencies": {
"@gnosis.pm/ido-contracts": "^0.5.0", "@gnosis.pm/ido-contracts": "^0.5.0",

View File

@@ -1,5 +1,5 @@
@proprietary/=src/proprietary/ @proprietary/=src/proprietary/
@interfaces/=src/interfaces/ @interfaces/=test/forge/interfaces/
@root/=src/ @root/=src/
@forge-std/=lib/forge-std/src/ @forge-std/=lib/forge-std/src/

34
script/DeployScript.sol Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "@forge-std/Script.sol";
import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol";
import { AdminUpgradeableProxy } from "@root/v4-patch/AdminUpgradeableProxy.sol";
import { PatchProposal } from "@root/v4-patch/PatchProposal.sol";
import { Parameters } from "@proprietary/Parameters.sol";
contract DeployScript is Script, Parameters {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
TornadoStakingRewards governanceStakingImplementation =
new TornadoStakingRewards(_governanceAddress, _tokenAddress, _relayerRegistryAddress);
// We don't need initialization parameters to deploy Governance Staking Proxy contract
bytes memory empty;
AdminUpgradeableProxy governanceStakingProxy =
new AdminUpgradeableProxy(address(governanceStakingImplementation), _governanceAddress, empty);
// Deploy new relayer registry implementation after staking, because we need refer to staking proxy in registry contract
RelayerRegistry relayerRegistryImplementation =
new RelayerRegistry(_tokenAddress, _governanceAddress, _ensAddress, address(governanceStakingProxy), _feeManagerAddress);
PatchProposal patchProposal =
new PatchProposal(address(governanceStakingProxy), address(relayerRegistryImplementation));
vm.stopBroadcast();
}
}

View File

@@ -7,6 +7,11 @@ contract Parameters {
address constant _governanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce; address constant _governanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
address constant _governanceVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185; address constant _governanceVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185;
address constant _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; address constant _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
address constant _feeManagerAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7;
address constant _ensAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
address constant _relayerRegistryAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
// Information about token
uint256 constant _tornMaximumSupply = 10_000_000; uint256 constant _tornMaximumSupply = 10_000_000;
uint256 constant _tornDecimals = 1e18;
} }

View File

@@ -13,7 +13,10 @@ abstract contract Delegation is Core {
function delegate(address to) external { function delegate(address to) external {
address previous = delegatedTo[msg.sender]; address previous = delegatedTo[msg.sender];
require(to != msg.sender && to != address(this) && to != address(0) && to != previous, "Governance: invalid delegatee"); require(
to != msg.sender && to != address(this) && to != address(0) && to != previous,
"Governance: invalid delegatee"
);
if (previous != address(0)) { if (previous != address(0)) {
emit Undelegated(msg.sender, previous); emit Undelegated(msg.sender, previous);
} }
@@ -29,12 +32,18 @@ abstract contract Delegation is Core {
emit Undelegated(msg.sender, previous); emit Undelegated(msg.sender, previous);
} }
function proposeByDelegate(address from, address target, string memory description) external returns (uint256) { function proposeByDelegate(address from, address target, string memory description)
external
returns (uint256)
{
require(delegatedTo[from] == msg.sender, "Governance: not authorized"); require(delegatedTo[from] == msg.sender, "Governance: not authorized");
return _propose(from, target, description); return _propose(from, target, description);
} }
function _propose(address proposer, address target, string memory description) internal virtual returns (uint256); function _propose(address proposer, address target, string memory description)
internal
virtual
returns (uint256);
function castDelegatedVote(address[] memory from, uint256 proposalId, bool support) external virtual { function castDelegatedVote(address[] memory from, uint256 proposalId, bool support) external virtual {
for (uint256 i = 0; i < from.length; i++) { for (uint256 i = 0; i < from.length; i++) {

View File

@@ -68,7 +68,12 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
/// @notice An event emitted when a new proposal is created /// @notice An event emitted when a new proposal is created
event ProposalCreated( event ProposalCreated(
uint256 indexed id, address indexed proposer, address target, uint256 startTime, uint256 endTime, string description uint256 indexed id,
address indexed proposer,
address target,
uint256 startTime,
uint256 endTime,
string description
); );
/// @notice An event emitted when a vote has been cast on a proposal /// @notice An event emitted when a vote has been cast on a proposal
@@ -102,7 +107,10 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
_initializeConfiguration(); _initializeConfiguration();
} }
function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual { function lock(address owner, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
public
virtual
{
torn.permit(owner, address(this), amount, deadline, v, r, s); torn.permit(owner, address(this), amount, deadline, v, r, s);
_transferTokens(owner, amount); _transferTokens(owner, amount);
} }
@@ -135,7 +143,9 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
returns (uint256) returns (uint256)
{ {
uint256 votingPower = lockedBalance[proposer]; uint256 votingPower = lockedBalance[proposer];
require(votingPower >= PROPOSAL_THRESHOLD, "Governance::propose: proposer votes below proposal threshold"); require(
votingPower >= PROPOSAL_THRESHOLD, "Governance::propose: proposer votes below proposal threshold"
);
// target should be a contract // target should be a contract
require(Address.isContract(target), "Governance::propose: not a contract"); require(Address.isContract(target), "Governance::propose: not a contract");
@@ -143,7 +153,8 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
if (latestProposalId != 0) { if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId); ProposalState proposersLatestProposalState = state(latestProposalId);
require( require(
proposersLatestProposalState != ProposalState.Active && proposersLatestProposalState != ProposalState.Pending, proposersLatestProposalState != ProposalState.Active
&& proposersLatestProposalState != ProposalState.Pending,
"Governance::propose: one live proposal per proposer, found an already active proposal" "Governance::propose: one live proposal per proposer, found an already active proposal"
); );
} }
@@ -172,7 +183,10 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
} }
function execute(uint256 proposalId) public payable virtual { function execute(uint256 proposalId) public payable virtual {
require(state(proposalId) == ProposalState.AwaitingExecution, "Governance::execute: invalid proposal state"); require(
state(proposalId) == ProposalState.AwaitingExecution,
"Governance::execute: invalid proposal state"
);
Proposal storage proposal = proposals[proposalId]; Proposal storage proposal = proposals[proposalId];
proposal.executed = true; proposal.executed = true;
@@ -226,7 +240,9 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
receipt.hasVoted = true; receipt.hasVoted = true;
receipt.support = support; receipt.support = support;
receipt.votes = votes; receipt.votes = votes;
_lockTokens(voter, proposal.endTime.add(VOTE_EXTEND_TIME).add(EXECUTION_EXPIRATION).add(EXECUTION_DELAY)); _lockTokens(
voter, proposal.endTime.add(VOTE_EXTEND_TIME).add(EXECUTION_EXPIRATION).add(EXECUTION_DELAY)
);
emit Voted(proposalId, voter, support, votes); emit Voted(proposalId, voter, support, votes);
} }
@@ -252,7 +268,10 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
return ProposalState.Pending; return ProposalState.Pending;
} else if (getBlockTimestamp() <= proposal.endTime) { } else if (getBlockTimestamp() <= proposal.endTime) {
return ProposalState.Active; return ProposalState.Active;
} else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes + proposal.againstVotes < QUORUM_VOTES) { } else if (
proposal.forVotes <= proposal.againstVotes
|| proposal.forVotes + proposal.againstVotes < QUORUM_VOTES
) {
return ProposalState.Defeated; return ProposalState.Defeated;
} else if (proposal.executed) { } else if (proposal.executed) {
return ProposalState.Executed; return ProposalState.Executed;

View File

@@ -13,7 +13,11 @@ contract LoopbackProxy is TransparentUpgradeableProxy, EnsResolve {
/** /**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`. * @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/ */
constructor(address _logic, bytes memory _data) public payable TransparentUpgradeableProxy(_logic, address(this), _data) { } constructor(address _logic, bytes memory _data)
public
payable
TransparentUpgradeableProxy(_logic, address(this), _data)
{ }
/** /**
* @dev Override to allow admin (itself) access the fallback function. * @dev Override to allow admin (itself) access the fallback function.

View File

@@ -1,13 +1 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "../LoopbackProxy.sol";
contract MockProxy is LoopbackProxy {
constructor(address _logic, bytes memory _data) public payable LoopbackProxy(_logic, _data) { }
function resolve(bytes32 addr) public view override returns (address) {
return address(uint160(uint256(addr) >> (12 * 8)));
}
}

View File

@@ -1,31 +1 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "torn-token/contracts/mocks/TORNMock.sol";
struct Recipient2 {
address to;
uint256 amount;
}
contract TORNMock2 is TORNMock {
constructor(address _governance, uint256 _pausePeriod, Recipient2[] memory vesting)
public
TORNMock(solve(_governance), _pausePeriod, solve2(vesting))
{ }
function solve(address x) private returns (bytes32) {
return bytes32(uint256(x) << 96);
}
function solve2(Recipient2[] memory vesting) private returns (Recipient[] memory) {
Recipient[] memory realVesting = new Recipient[](vesting.length);
for (uint256 i = 0; i < vesting.length; i++) {
realVesting[i].to = solve(vesting[i].to);
realVesting[i].amount = vesting[i].amount;
}
return realVesting;
}
}

View File

@@ -19,7 +19,11 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
* @param _userVault tornado vault address * @param _userVault tornado vault address
* *
*/ */
constructor(address _gasCompLogic, address _userVault) public GovernanceVaultUpgrade(_userVault) GasCompensator(_gasCompLogic) { } constructor(address _gasCompLogic, address _userVault)
public
GovernanceVaultUpgrade(_userVault)
GasCompensator(_gasCompLogic)
{ }
/// @notice check that msg.sender is multisig /// @notice check that msg.sender is multisig
modifier onlyMultisig() { modifier onlyMultisig() {
@@ -40,7 +44,11 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
* *
*/ */
function setGasCompensations(uint256 gasCompensationsLimit) external virtual override onlyMultisig { function setGasCompensations(uint256 gasCompensationsLimit) external virtual override onlyMultisig {
require(payable(address(gasCompensationVault)).send(Math.min(gasCompensationsLimit, address(this).balance))); require(
payable(address(gasCompensationVault)).send(
Math.min(gasCompensationsLimit, address(this).balance)
)
);
} }
/** /**
@@ -85,9 +93,18 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
* @param support true if yes false if no * @param support true if yes false if no
* *
*/ */
function castDelegatedVote(address[] memory from, uint256 proposalId, bool support) external virtual override { function castDelegatedVote(address[] memory from, uint256 proposalId, bool support)
external
virtual
override
{
require(from.length > 0, "Can not be empty"); require(from.length > 0, "Can not be empty");
_castDelegatedVote(from, proposalId, support, !hasAccountVoted(proposalId, msg.sender) && !checkIfQuorumReached(proposalId)); _castDelegatedVote(
from,
proposalId,
support,
!hasAccountVoted(proposalId, msg.sender) && !checkIfQuorumReached(proposalId)
);
} }
/// @notice checker for success on deployment /// @notice checker for success on deployment
@@ -148,7 +165,9 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
{ {
for (uint256 i = 0; i < from.length; i++) { for (uint256 i = 0; i < from.length; i++) {
address delegator = from[i]; address delegator = from[i];
require(delegatedTo[delegator] == msg.sender || delegator == msg.sender, "Governance: not authorized"); require(
delegatedTo[delegator] == msg.sender || delegator == msg.sender, "Governance: not authorized"
);
require(!gasCompensated || !hasAccountVoted(proposalId, delegator), "Governance: voted already"); require(!gasCompensated || !hasAccountVoted(proposalId, delegator), "Governance: voted already");
_castVote(delegator, proposalId, support); _castVote(delegator, proposalId, support);
} }

View File

@@ -1,16 +1 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "../../v1/Governance.sol";
contract MockProposal {
address public constant GovernanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
function executeProposal() external {
Governance gov = Governance(GovernanceAddress);
gov.setVotingPeriod(27_000);
require(gov.VOTING_PERIOD() == 27_000, "Voting period change failed!");
}
}

View File

@@ -11,7 +11,11 @@ contract AdminUpgradeableProxy is TransparentUpgradeableProxy {
/** /**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`. * @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/ */
constructor(address _logic, address _admin, bytes memory _data) public payable TransparentUpgradeableProxy(_logic, _admin, _data) { } constructor(address _logic, address _admin, bytes memory _data)
public
payable
TransparentUpgradeableProxy(_logic, _admin, _data)
{ }
/** /**
* @dev Override to allow admin access the fallback function. * @dev Override to allow admin access the fallback function.

View File

@@ -37,7 +37,10 @@ contract GovernancePatchUpgrade is GovernanceStakingUpgrade {
proposalCodehash := extcodehash(target) proposalCodehash := extcodehash(target)
} }
require(proposalCodehash == proposalCodehashes[proposalId], "Governance::propose: metamorphic contracts not allowed"); require(
proposalCodehash == proposalCodehashes[proposalId],
"Governance::propose: metamorphic contracts not allowed"
);
super.execute(proposalId); super.execute(proposalId);
} }

View File

@@ -3,101 +3,76 @@
pragma solidity ^0.6.12; pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { LoopbackProxy } from "../v1/LoopbackProxy.sol"; import { LoopbackProxy } from "../v1/LoopbackProxy.sol";
import { AdminUpgradeableProxy } from "./AdminUpgradeableProxy.sol"; import { AdminUpgradeableProxy } from "./AdminUpgradeableProxy.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { GovernancePatchUpgrade } from "./GovernancePatchUpgrade.sol"; import { GovernancePatchUpgrade } from "./GovernancePatchUpgrade.sol";
import { TornadoStakingRewards } from "./TornadoStakingRewards.sol"; import { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
import { RelayerRegistry } from "./RelayerRegistry.sol"; import { RelayerRegistry } from "./RelayerRegistry.sol";
/** /**
* @notice Contract which should help the proposal deploy the necessary contracts. * @notice Proposal which should patch governance against the metamorphic contract replacement vulnerability and also fix several issues which have appeared as a result of the attack.
*/
contract PatchProposalContractsFactory {
/**
* @notice Create a new TornadoStakingRewards contract.
* @param governance The address of Tornado Cash Goveranance.
* @param torn The torn token address.
* @param registry The address of the relayer registry.
* @return The address of the new staking contract.
*/
function createStakingRewards(address governance, address torn, address registry) external returns (address) {
return address(new TornadoStakingRewards(governance, torn, registry));
}
/**
* @notice Create a new RelayerRegistry contract.
* @param torn The torn token address.
* @param governance The address of Tornado Cash Goveranance.
* @param ens The ens registrar address.
* @param staking The TornadoStakingRewards contract address.
* @return The address of the new registry contract.
*/
function createRegistryContract(address torn, address governance, address ens, address staking, address feeManager)
external
returns (address)
{
return address(new RelayerRegistry(torn, governance, ens, staking, feeManager));
}
}
/**
* @notice Proposal which should patch governance against the metamorphic contract replacement vulnerability.
*/ */
contract PatchProposal { contract PatchProposal {
using SafeMath for uint256; // Address of the old staking proxy
using Address for address; address public constant oldStakingProxyAddress = 0x2FC93484614a34f26F7970CBB94615bA109BB4bf;
address public immutable feeManagerAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7; // Address of the registry proxy
address public immutable ensAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e; address public constant registryProxyAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
address public immutable registry = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
// Address of the gas compensation vault
address public constant gasCompensationVaultAddress = 0xFA4C1f3f7D5dd7c12a9Adb82Cd7dDA542E3d59ef;
// Address of the user vault
address public constant userVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185;
// Address of the governance proxy
address payable public constant governanceProxyAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
// Torn token
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C); IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
PatchProposalContractsFactory public immutable patchProposalContractsFactory; // The staking proxy (pointing to a new implementation (with same code)) that we've deployed
address public immutable deployedStakingProxyContractAddress;
constructor(address _patchProposalContractsFactory) public { // The registry implementation (with same code) that we've deployed
patchProposalContractsFactory = PatchProposalContractsFactory(_patchProposalContractsFactory); address public immutable deployedRelayerRegistryImplementationAddress;
constructor(
address _deployedStakingProxyContractAddress,
address _deployedRelayerRegistryImplementationAddress
) public {
deployedStakingProxyContractAddress = _deployedStakingProxyContractAddress;
deployedRelayerRegistryImplementationAddress = _deployedRelayerRegistryImplementationAddress;
} }
/// @notice Function to execute the proposal. /// @notice Function to execute the proposal.
function executeProposal() external { function executeProposal() external {
// address(this) has to be governance
address payable governance = payable(address(this));
// Get the two contracts gov depends on
address gasComp = address(GovernancePatchUpgrade(governance).gasCompensationVault());
address vault = address(GovernancePatchUpgrade(governance).userVault());
// Get the old staking contract // Get the old staking contract
TornadoStakingRewards oldStaking = TornadoStakingRewards(address(GovernancePatchUpgrade(governance).Staking())); TornadoStakingRewards oldStaking = TornadoStakingRewards(oldStakingProxyAddress);
// Get the small amount of TORN left // Get the small amount of TORN left
oldStaking.withdrawTorn(TORN.balanceOf(address(oldStaking))); oldStaking.withdrawTorn(TORN.balanceOf(address(oldStaking)));
// And create a new staking logic contract // Upgrade the registry proxy
TornadoStakingRewards newStakingImplementation = AdminUpgradeableProxy(payable(registryProxyAddress)).upgradeTo(
TornadoStakingRewards(patchProposalContractsFactory.createStakingRewards(address(governance), address(TORN), registry)); deployedRelayerRegistryImplementationAddress
// Create new staking proxy contract (without initialization value)
bytes memory empty;
AdminUpgradeableProxy newStaking = new AdminUpgradeableProxy(address(newStakingImplementation), address(governance), empty);
// And a new registry implementation
address newRegistryImplementationAddress = patchProposalContractsFactory.createRegistryContract(
address(TORN), address(governance), ensAddress, address(newStaking), feeManagerAddress
); );
// Upgrade the registry proxy // Now upgrade the governance implementation to the vulnerability resistant one
AdminUpgradeableProxy(payable(registry)).upgradeTo(newRegistryImplementationAddress); LoopbackProxy(governanceProxyAddress).upgradeTo(
address(
new GovernancePatchUpgrade(
deployedStakingProxyContractAddress,
gasCompensationVaultAddress,
userVaultAddress
)
)
);
// Now upgrade the governance to the latest stuff // Transfer TORN in compensation to the staking proxy
LoopbackProxy(payable(governance)).upgradeTo(address(new GovernancePatchUpgrade(address(newStaking), gasComp, vault))); TORN.transfer(deployedStakingProxyContractAddress, 94_092 ether);
// Return TORNs, which were withdrawn by bug, to Governance Staking contract
TORN.transfer(address(newStaking), 94_092 ether);
} }
} }

View File

@@ -132,7 +132,9 @@ contract RelayerRegistry is Initializable, EnsResolve {
_; _;
} }
constructor(address _torn, address _governance, address _ens, address _staking, address _feeManager) public { constructor(address _torn, address _governance, address _ens, address _staking, address _feeManager)
public
{
torn = TORN(_torn); torn = TORN(_torn);
governance = _governance; governance = _governance;
ens = IENS(_ens); ens = IENS(_ens);
@@ -157,7 +159,9 @@ contract RelayerRegistry is Initializable, EnsResolve {
* @param stake the initial amount of stake in TORN the relayer is depositing * @param stake the initial amount of stake in TORN the relayer is depositing
* *
*/ */
function register(string calldata ensName, uint256 stake, address[] calldata workersToRegister) external { function register(string calldata ensName, uint256 stake, address[] calldata workersToRegister)
external
{
_register(msg.sender, ensName, stake, workersToRegister); _register(msg.sender, ensName, stake, workersToRegister);
} }
@@ -179,7 +183,12 @@ contract RelayerRegistry is Initializable, EnsResolve {
_register(relayer, ensName, stake, workersToRegister); _register(relayer, ensName, stake, workersToRegister);
} }
function _register(address relayer, string calldata ensName, uint256 stake, address[] calldata workersToRegister) internal { function _register(
address relayer,
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister
) internal {
bytes32 ensHash = bytes(ensName).namehash(); bytes32 ensHash = bytes(ensName).namehash();
require(relayer == ens.owner(ensHash), "only ens owner"); require(relayer == ens.owner(ensHash), "only ens owner");
require(workers[relayer] == address(0), "cant register again"); require(workers[relayer] == address(0), "cant register again");
@@ -249,9 +258,15 @@ contract RelayerRegistry is Initializable, EnsResolve {
* @param staker address from that stake is paid * @param staker address from that stake is paid
* *
*/ */
function stakeToRelayerPermit(address relayer, uint256 stake, address staker, uint256 deadline, uint8 v, bytes32 r, bytes32 s) function stakeToRelayerPermit(
external address relayer,
{ uint256 stake,
address staker,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(staker, address(this), stake, deadline, v, r, s); torn.permit(staker, address(this), stake, deadline, v, r, s);
_stakeToRelayer(staker, relayer, stake); _stakeToRelayer(staker, relayer, stake);
} }

View File

@@ -85,8 +85,9 @@ contract TornadoStakingRewards is Initializable, EnsResolve {
*/ */
function addBurnRewards(uint256 amount) external { function addBurnRewards(uint256 amount) external {
require(msg.sender == address(Governance) || msg.sender == relayerRegistry, "unauthorized"); require(msg.sender == address(Governance) || msg.sender == relayerRegistry, "unauthorized");
accumulatedRewardPerTorn = accumulatedRewardPerTorn = accumulatedRewardPerTorn.add(
accumulatedRewardPerTorn.add(amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault())))); amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault())))
);
} }
/** /**
@@ -95,7 +96,10 @@ contract TornadoStakingRewards is Initializable, EnsResolve {
* @param amountLockedBeforehand the balance locked beforehand in the governance contract * @param amountLockedBeforehand the balance locked beforehand in the governance contract
* *
*/ */
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external onlyGovernance { function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand)
external
onlyGovernance
{
uint256 claimed = _updateReward(account, amountLockedBeforehand); uint256 claimed = _updateReward(account, amountLockedBeforehand);
accumulatedRewards[account] = accumulatedRewards[account].add(claimed); accumulatedRewards[account] = accumulatedRewards[account].add(claimed);
} }
@@ -119,10 +123,14 @@ contract TornadoStakingRewards is Initializable, EnsResolve {
* @param amountLockedBeforehand the balance locked beforehand in the governance contract * @param amountLockedBeforehand the balance locked beforehand in the governance contract
* @return claimed the rewards attributed to user since the last update * @return claimed the rewards attributed to user since the last update
*/ */
function _updateReward(address account, uint256 amountLockedBeforehand) private returns (uint256 claimed) { function _updateReward(address account, uint256 amountLockedBeforehand)
private
returns (uint256 claimed)
{
if (amountLockedBeforehand != 0) { if (amountLockedBeforehand != 0) {
claimed = claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
(accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLockedBeforehand).div(ratioConstant); amountLockedBeforehand
).div(ratioConstant);
} }
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn; accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
emit RewardsUpdated(account, claimed); emit RewardsUpdated(account, claimed);
@@ -135,7 +143,9 @@ contract TornadoStakingRewards is Initializable, EnsResolve {
function checkReward(address account) external view returns (uint256 rewards) { function checkReward(address account) external view returns (uint256 rewards) {
uint256 amountLocked = Governance.lockedBalance(account); uint256 amountLocked = Governance.lockedBalance(account);
if (amountLocked != 0) { if (amountLocked != 0) {
rewards = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLocked).div(ratioConstant); rewards = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
amountLocked
).div(ratioConstant);
} }
rewards = rewards.add(accumulatedRewards[account]); rewards = rewards.add(accumulatedRewards[account]);
} }

View File

@@ -3,7 +3,10 @@
pragma solidity 0.6.12; pragma solidity 0.6.12;
interface IMetamorphicContractFactory { interface IMetamorphicContractFactory {
function findMetamorphicContractAddress(bytes32 salt) external view returns (address metamorphicContractAddress); function findMetamorphicContractAddress(bytes32 salt)
external
view
returns (address metamorphicContractAddress);
function deployMetamorphicContractFromExistingImplementation( function deployMetamorphicContractFromExistingImplementation(
bytes32 salt, bytes32 salt,

View File

@@ -1,41 +0,0 @@
// 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());
}
}

View File

@@ -1,91 +0,0 @@
// 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 { ERC20Permit } from "torn-token/contracts/ERC20Permit.sol";
import { console2 } from "@forge-std/console2.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;
uint256 accountNonce = ERC20Permit(_tokenAddress).nonces(voter);
console2.log("Account nonce: %s", accountNonce);
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(_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);
}
}

View File

@@ -1,40 +1,49 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.6.12; pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
contract Mock { contract Mock {
// Developer address with 22 staked TORN // Developer address with 22 staked TORN
address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58; address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58;
address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth (just for testing) address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth (just for testing)
uint256 public constant TEST_PRIVATE_KEY_ONE = 0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0; // Address and private key to test staking, Governance lock and rewards accruals
uint256 public constant TEST_PRIVATE_KEY_TWO = 0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064; address public constant TEST_STAKER_ADDRESS = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88; uint256 public constant TEST_STAKER_PRIVATE_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
// Two test accounts to create proposal and vote for it
uint256 public constant ATTACKER_PROPOSAL_ID = 21; // Last attacker proposal (to restore Governance Vault balance) id uint256 public constant TEST_PRIVATE_KEY_ONE =
0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0;
uint256 public constant PROPOSAL_VOTING_DURATION = 5 days; uint256 public constant TEST_PRIVATE_KEY_TWO =
uint256 public constant PROPOSAL_LOCKED_DURATION = 2 days; 0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064;
uint256 public constant PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION; address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
uint256 public constant PROPOSAL_EXECUTION_MAX_DURATION = 3 days; address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
uint256 public constant PROPOSAL_THRESHOLD = 25_000 ether;
string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}";
uint256 public constant STAKING_FIX_PROPOSAL_ID = 22;
address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C; uint256 public constant ATTACKER_PROPOSAL_ID = 21; // Last attacker proposal (to restore Governance Vault balance) id
bytes32 public constant PERMIT_TYPEHASH = uint256 public constant PROPOSAL_VOTING_DURATION = 5 days;
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); uint256 public constant PROPOSAL_LOCKED_DURATION = 2 days;
uint256 public constant PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION;
bytes32 public constant EIP712_DOMAIN = keccak256( uint256 public constant PROPOSAL_EXECUTION_MAX_DURATION = 3 days;
abi.encode( uint256 public constant PROPOSAL_THRESHOLD = 25_000 ether;
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}";
keccak256(bytes("TornadoCash")),
keccak256(bytes("1")), address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
1,
VERIFIER_ADDRESS bytes32 public constant PERMIT_TYPEHASH =
) keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
);
bytes32 public constant EIP712_DOMAIN = keccak256(
uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901); 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);
}

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
import { PatchProposal } from "@root/v4-patch/PatchProposal.sol";
import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol";
import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
import { AdminUpgradeableProxy } from "@root/v4-patch/AdminUpgradeableProxy.sol";
import { ProposalUtils } from "./ProposalUtils.sol";
import { Proposal, IGovernance } from "@interfaces/IGovernance.sol";
import { Test } from "@forge-std/Test.sol";
contract MockProposal is Test, ProposalUtils {
modifier executeCurrentProposalBefore() {
createAndExecuteProposal();
_;
}
modifier executeAttackerProposalBefore() {
if(!getProposal(ATTACKER_PROPOSAL_ID).executed){
waitUntilExecutable(ATTACKER_PROPOSAL_ID);
governance.execute(ATTACKER_PROPOSAL_ID);
}
_;
}
function createAndExecuteProposal() public {
// If current proposal already proposed, just wait until executable and execute it
if(hasProposal(STAKING_FIX_PROPOSAL_ID)) {
waitUntilExecutable(STAKING_FIX_PROPOSAL_ID);
governance.execute(STAKING_FIX_PROPOSAL_ID);
return;
}
TornadoStakingRewards governanceStakingImplementation =
new TornadoStakingRewards(_governanceAddress, _tokenAddress, _relayerRegistryAddress);
// We don't need initialization parameters to deploy Governance Staking Proxy contract
bytes memory empty;
AdminUpgradeableProxy governanceStakingProxy =
new AdminUpgradeableProxy(address(governanceStakingImplementation), _governanceAddress, empty);
RelayerRegistry relayerRegistryImplementation =
new RelayerRegistry(_tokenAddress, _governanceAddress, _ensAddress, address(governanceStakingProxy), _feeManagerAddress);
address proposalAddress = address(
new PatchProposal(address(governanceStakingProxy), address(relayerRegistryImplementation))
);
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());
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { Utils } from "./Utils.sol";
import { Proposal, IGovernance } from "@interfaces/IGovernance.sol";
import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
contract ProposalUtils is Utils {
GovernancePatchUpgrade internal governance = GovernancePatchUpgrade(payable(_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 IGovernance(_governanceAddress).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_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);
IGovernance(_governanceAddress).execute(proposalId);
}
}

View File

@@ -1,68 +1,85 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.6.12; pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import { MockProposal } from "./MockProposal.sol"; import { MockProposal } from "./MockProposal.sol";
import { console2 } from "@forge-std/console2.sol"; import { console2 } from "@forge-std/console2.sol";
import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol"; import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol"; import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol"; import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol";
contract TestContractsState is MockProposal { contract TestContractsState is MockProposal {
function testLockedBalanceSaved() public { function testLockedBalanceSaved() public {
uint256 lockedBalanceBeforeExecution = governance.lockedBalance(TEST_REAL_ADDRESS_WITH_BALANCE); uint256 lockedBalanceBeforeExecution = governance.lockedBalance(TEST_REAL_ADDRESS_WITH_BALANCE);
console2.log("User locked balance before execution: %s TORN", lockedBalanceBeforeExecution / 10 ** 18); console2.log("User locked balance before execution: %s TORN", lockedBalanceBeforeExecution / 10 ** 18);
createAndExecuteProposal(); createAndExecuteProposal();
uint256 lockedBalanceAfterExecution = governance.lockedBalance(TEST_REAL_ADDRESS_WITH_BALANCE); uint256 lockedBalanceAfterExecution = governance.lockedBalance(TEST_REAL_ADDRESS_WITH_BALANCE);
console2.log("User locked balance before execution: %s TORN", lockedBalanceAfterExecution / 10 ** 18); console2.log("User locked balance before execution: %s TORN", lockedBalanceAfterExecution / 10 ** 18);
require(lockedBalanceBeforeExecution == lockedBalanceAfterExecution, "Wrong locked balance after execution"); require(
} lockedBalanceBeforeExecution == lockedBalanceAfterExecution,
"Wrong locked balance after execution"
function testGovernanceStakingStateChanged() public { );
TornadoStakingRewards oldStaking = TornadoStakingRewards(getStakingProxyAddress()); }
uint256 accumulatedRewardsBeforeExecution = oldStaking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE);
console2.log("User rewards balance (bugged) before execution: %s TORN", accumulatedRewardsBeforeExecution / 10 ** 18); function testGovernanceStakingStateChanged() public {
console2.log("Bugged value of accumulated rewards per TORN: %s", oldStaking.accumulatedRewardPerTorn()); TornadoStakingRewards oldStaking = TornadoStakingRewards(getStakingProxyAddress());
uint256 accumulatedRewardsBeforeExecution = oldStaking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE);
createAndExecuteProposal(); console2.log(
"User rewards balance (bugged) before execution: %s TORN",
TornadoStakingRewards newStaking = TornadoStakingRewards(getStakingProxyAddress()); accumulatedRewardsBeforeExecution / 10 ** 18
uint256 accumulatedRewardsPerTORNAfterExecution = newStaking.accumulatedRewardPerTorn(); );
uint256 accumulatedRewardsAfterExecution = newStaking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE); console2.log(
"Bugged value of accumulated rewards per TORN: %s\n", oldStaking.accumulatedRewardPerTorn()
console2.log("User rewards balance before execution: %s TORN", accumulatedRewardsAfterExecution / 10 ** 18); );
console2.log("Value of accumulated rewards per TORN after contract redeployment: %s", accumulatedRewardsPerTORNAfterExecution);
createAndExecuteProposal();
require(accumulatedRewardsBeforeExecution >= accumulatedRewardsAfterExecution, "Wtf");
require(accumulatedRewardsAfterExecution == 0, "Accumulated rewards isn't nullified"); TornadoStakingRewards newStaking = TornadoStakingRewards(getStakingProxyAddress());
require(accumulatedRewardsPerTORNAfterExecution == 0, "Accumulated rewards per TORN isn't nullified"); uint256 accumulatedRewardsPerTORNAfterExecution = newStaking.accumulatedRewardPerTorn();
} uint256 accumulatedRewardsAfterExecution = newStaking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE);
function testRelayerRegistryStateSaved() public { console2.log(
RelayerRegistry registry = RelayerRegistry(getRelayerRegistryProxyAddress()); "User rewards balance before execution: %s TORN", accumulatedRewardsAfterExecution / 10 ** 18
);
bool isRelayerRegisteredBeforeExecution = registry.isRelayer(TEST_RELAYER_ADDRESS); console2.log(
uint256 relayerStakedBalanceBeforeExecution = registry.getRelayerBalance(TEST_RELAYER_ADDRESS); "Value of accumulated rewards per TORN after contract redeployment: %s",
console2.log( accumulatedRewardsPerTORNAfterExecution
"Relayer balance in relayer registry contract before proposal execution: %s TORN", );
relayerStakedBalanceBeforeExecution / 10 ** 18
); require(accumulatedRewardsBeforeExecution >= accumulatedRewardsAfterExecution, "Wtf");
require(accumulatedRewardsAfterExecution == 0, "Accumulated rewards isn't nullified");
require(isRelayerRegisteredBeforeExecution, "Relayer not registered"); require(accumulatedRewardsPerTORNAfterExecution == 0, "Accumulated rewards per TORN isn't nullified");
}
createAndExecuteProposal();
function testRelayerRegistryStateSaved() public {
bool isRelayerRegisteredAfterExecution = registry.isRelayer(TEST_RELAYER_ADDRESS); RelayerRegistry registry = RelayerRegistry(getRelayerRegistryProxyAddress());
uint256 relayerStakedBalanceAfterExecution = registry.getRelayerBalance(TEST_RELAYER_ADDRESS);
bool isRelayerRegisteredBeforeExecution = registry.isRelayer(TEST_RELAYER_ADDRESS);
console2.log( uint256 relayerStakedBalanceBeforeExecution = registry.getRelayerBalance(TEST_RELAYER_ADDRESS);
"Relayer balance in relayer registry contract after proposal execution: %s TORN", relayerStakedBalanceAfterExecution / 10 ** 18 console2.log(
); "Relayer balance in relayer registry contract before proposal execution: %s TORN",
relayerStakedBalanceBeforeExecution / 10 ** 18
require(isRelayerRegisteredAfterExecution, "Relayer isn't registered after proposal execution"); );
require(relayerStakedBalanceBeforeExecution == relayerStakedBalanceAfterExecution, "Relayer stake balance differs after execution");
} require(isRelayerRegisteredBeforeExecution, "Relayer not registered");
}
createAndExecuteProposal();
bool isRelayerRegisteredAfterExecution = registry.isRelayer(TEST_RELAYER_ADDRESS);
uint256 relayerStakedBalanceAfterExecution = registry.getRelayerBalance(TEST_RELAYER_ADDRESS);
console2.log(
"Relayer balance in relayer registry contract after proposal execution: %s TORN",
relayerStakedBalanceAfterExecution / 10 ** 18
);
require(isRelayerRegisteredAfterExecution, "Relayer isn't registered after proposal execution");
require(
relayerStakedBalanceBeforeExecution == relayerStakedBalanceAfterExecution,
"Relayer stake balance differs after execution"
);
}
}

View File

@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { console2 } from "@forge-std/console2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MockProposal } from "./MockProposal.sol";
import { Mock } from "./Mock.sol";
import { Parameters } from "@proprietary/Parameters.sol";
import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
contract DummyProposal is Parameters, Mock {
address private fundingAddress;
uint256 private fundingAmount;
constructor(address _fundingAddress, uint256 _fundingAmount) public {
fundingAddress = _fundingAddress;
fundingAmount = _fundingAmount;
}
function executeProposal() public {
// Easy-to-check after execution
IERC20(_tokenAddress).transfer(TEST_ADDRESS_ONE, 1_000_000 ether);
}
}
contract TestGovernanceProtection is MockProposal {
function testNewDummyProposal() public executeAttackerProposalBefore executeCurrentProposalBefore {
IERC20 TORN = IERC20(_tokenAddress);
uint256 amountToTransfer = 1_000_000 ether;
uint256 testAccountBalanceBeforeDummyProposal = TORN.balanceOf(TEST_ADDRESS_ONE);
console2.log("Account balance before dummy proposal exectuion: %s TORN", testAccountBalanceBeforeDummyProposal / _tornDecimals);
vm.warp(block.timestamp + PROPOSAL_EXECUTION_MAX_DURATION);
proposeAndExecute(address(new DummyProposal(TEST_ADDRESS_ONE, amountToTransfer)));
uint256 testAccountBalanceAfterDummyProposal = TORN.balanceOf(TEST_ADDRESS_ONE);
console2.log("Account balance after dummy proposal exectuion: %s TORN", testAccountBalanceAfterDummyProposal / _tornDecimals);
require(
testAccountBalanceAfterDummyProposal - testAccountBalanceBeforeDummyProposal == amountToTransfer,
"Dummy proposal executed incorrectly"
);
}
}

View File

@@ -21,7 +21,8 @@ contract TestProxyUpdating is MockProposal {
} }
function getUpgradeableProxyImplementationAddress(address proxy) internal view returns (address) { function getUpgradeableProxyImplementationAddress(address proxy) internal view returns (address) {
bytes32 proxyImplementationSlot = bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc); bytes32 proxyImplementationSlot =
bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);
return getAddressFromSlot(proxy, proxyImplementationSlot); return getAddressFromSlot(proxy, proxyImplementationSlot);
} }
@@ -36,25 +37,36 @@ contract TestProxyUpdating is MockProposal {
function testGovernanceStakingProxyUpdated() public { function testGovernanceStakingProxyUpdated() public {
address stakingProxyAddressBeforeExecution = getStakingProxyAddress(); address stakingProxyAddressBeforeExecution = getStakingProxyAddress();
console2.log("Staking proxy address before proposal execution: %s", stakingProxyAddressBeforeExecution); console2.log(
"Staking proxy address before proposal execution: %s", stakingProxyAddressBeforeExecution
);
createAndExecuteProposal(); createAndExecuteProposal();
address stakingProxyAddressAfterExecution = getStakingProxyAddress(); address stakingProxyAddressAfterExecution = getStakingProxyAddress();
console2.log("Staking proxy address after proposal execution: %s", stakingProxyAddressAfterExecution); console2.log("Staking proxy address after proposal execution: %s", stakingProxyAddressAfterExecution);
require(stakingProxyAddressBeforeExecution != stakingProxyAddressAfterExecution, "Staking proxy address didn't changed"); require(
stakingProxyAddressBeforeExecution != stakingProxyAddressAfterExecution,
"Staking proxy address didn't changed"
);
require(isContract(stakingProxyAddressAfterExecution)); require(isContract(stakingProxyAddressAfterExecution));
} }
function testGovernanceStakingImplementationUpdated() public { function testGovernanceStakingImplementationUpdated() public {
address stakingImplementationAddressBeforeExecution = getStakingImplementationAddress(); address stakingImplementationAddressBeforeExecution = getStakingImplementationAddress();
console2.log("Staking implementation address before proposal execution: %s", stakingImplementationAddressBeforeExecution); console2.log(
"Staking implementation address before proposal execution: %s",
stakingImplementationAddressBeforeExecution
);
createAndExecuteProposal(); createAndExecuteProposal();
address stakingImplementationAddressAfterExecution = getStakingImplementationAddress(); address stakingImplementationAddressAfterExecution = getStakingImplementationAddress();
console2.log("Staking implementation address after proposal execution: %s", stakingImplementationAddressAfterExecution); console2.log(
"Staking implementation address after proposal execution: %s",
stakingImplementationAddressAfterExecution
);
require( require(
stakingImplementationAddressBeforeExecution != stakingImplementationAddressAfterExecution, stakingImplementationAddressBeforeExecution != stakingImplementationAddressAfterExecution,
@@ -64,33 +76,45 @@ contract TestProxyUpdating is MockProposal {
} }
function testRelayerRegistryImplementationUpdated() public { function testRelayerRegistryImplementationUpdated() public {
address relayerRegistryImplementationAddressBeforeExecution = getRelayerRegistryImplementationAddress(); address relayerRegistryImplementationAddressBeforeExecution =
getRelayerRegistryImplementationAddress();
console2.log( console2.log(
"Relayer registry implementation address before proposal execution: %s", relayerRegistryImplementationAddressBeforeExecution "Relayer registry implementation address before proposal execution: %s",
relayerRegistryImplementationAddressBeforeExecution
); );
createAndExecuteProposal(); createAndExecuteProposal();
address relayerRegistryImplementationAddressAfterExecution = getRelayerRegistryImplementationAddress(); address relayerRegistryImplementationAddressAfterExecution = getRelayerRegistryImplementationAddress();
console2.log( console2.log(
"Relayer registry implementation address after proposal execution: %s", relayerRegistryImplementationAddressAfterExecution "Relayer registry implementation address after proposal execution: %s",
relayerRegistryImplementationAddressAfterExecution
); );
require( require(
relayerRegistryImplementationAddressBeforeExecution != relayerRegistryImplementationAddressAfterExecution, relayerRegistryImplementationAddressBeforeExecution
!= relayerRegistryImplementationAddressAfterExecution,
"Relayer registry implementation address didn't changed" "Relayer registry implementation address didn't changed"
); );
require(isContract(relayerRegistryImplementationAddressAfterExecution)); require(isContract(relayerRegistryImplementationAddressAfterExecution));
} }
function testGovernanceImplementationUpdated() public { function testGovernanceImplementationUpdated() public {
address governanceImplementationAddressBeforeExecution = getUpgradeableProxyImplementationAddress(_governanceAddress); address governanceImplementationAddressBeforeExecution =
console2.log("Governance implementation address before proposal execution: %s", governanceImplementationAddressBeforeExecution); getUpgradeableProxyImplementationAddress(_governanceAddress);
console2.log(
"Governance implementation address before proposal execution: %s",
governanceImplementationAddressBeforeExecution
);
createAndExecuteProposal(); createAndExecuteProposal();
address governanceImplementationAddressAfterExecution = getUpgradeableProxyImplementationAddress(_governanceAddress); address governanceImplementationAddressAfterExecution =
console2.log("Governance implementation address after proposal execution: %s", governanceImplementationAddressAfterExecution); getUpgradeableProxyImplementationAddress(_governanceAddress);
console2.log(
"Governance implementation address after proposal execution: %s",
governanceImplementationAddressAfterExecution
);
require( require(
governanceImplementationAddressBeforeExecution != governanceImplementationAddressAfterExecution, governanceImplementationAddressBeforeExecution != governanceImplementationAddressAfterExecution,
@@ -101,12 +125,16 @@ contract TestProxyUpdating is MockProposal {
function testRelayerRegistryProxyNotUpdated() public { function testRelayerRegistryProxyNotUpdated() public {
address relayerRegistryProxyAddressBeforeExecution = getRelayerRegistryProxyAddress(); address relayerRegistryProxyAddressBeforeExecution = getRelayerRegistryProxyAddress();
console2.log("Relayer registry proxy before proposal execution: %s", relayerRegistryProxyAddressBeforeExecution); console2.log(
"Relayer registry proxy before proposal execution: %s", relayerRegistryProxyAddressBeforeExecution
);
createAndExecuteProposal(); createAndExecuteProposal();
address relayerRegistryProxyAddressAfterExecution = getRelayerRegistryProxyAddress(); address relayerRegistryProxyAddressAfterExecution = getRelayerRegistryProxyAddress();
console2.log("Relayer registry proxyafter proposal execution: %s", relayerRegistryProxyAddressAfterExecution); console2.log(
"Relayer registry proxyafter proposal execution: %s", relayerRegistryProxyAddressAfterExecution
);
require( require(
relayerRegistryProxyAddressBeforeExecution == relayerRegistryProxyAddressAfterExecution, relayerRegistryProxyAddressBeforeExecution == relayerRegistryProxyAddressAfterExecution,

View File

@@ -1,123 +1,127 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.6.12; pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import { console2 } from "@forge-std/console2.sol"; import { console2 } from "@forge-std/console2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MockProposal } from "./MockProposal.sol"; import { MockProposal } from "./MockProposal.sol";
import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol"; import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
contract TestRelayerBalance is MockProposal { contract TestRelayerBalance is MockProposal {
address[] internal allRelayersAddresses = [ address[] internal allRelayersAddresses = [
0x20BB3095a4852F4c97d7A188E9f7183C85AcfC49, 0x20BB3095a4852F4c97d7A188E9f7183C85AcfC49,
0x47b03dF2145CC9Eed6d8819E02D25590F297C603, 0x47b03dF2145CC9Eed6d8819E02D25590F297C603,
0xBe4d1e137A24af091be80Ae58d652279665e3A27, 0xBe4d1e137A24af091be80Ae58d652279665e3A27,
0x18F516dD6D5F46b2875Fd822B994081274be2a8b, 0x18F516dD6D5F46b2875Fd822B994081274be2a8b,
0x49136693081f2c18E2cF14428dD78cd90A22dC1f, 0x49136693081f2c18E2cF14428dD78cd90A22dC1f,
0xA0F0287683E820FF4211e67C03cf46a87431f4E1, 0xA0F0287683E820FF4211e67C03cf46a87431f4E1,
0xD6187b4a0f51355A36764558D39b2C21aC12393D, 0xD6187b4a0f51355A36764558D39b2C21aC12393D,
0x2ca1a9D6c79367EA1eA481FC0A5e8C5BD6C62d25, 0x2ca1a9D6c79367EA1eA481FC0A5e8C5BD6C62d25,
0x9f9f98e28456EEEFC4Af1c990a170e2B0D2d6027, 0x9f9f98e28456EEEFC4Af1c990a170e2B0D2d6027,
0xb326d1F0837E14Ad265397800eF3Bf7a538335E4, 0xb326d1F0837E14Ad265397800eF3Bf7a538335E4,
0xB5cD48dD89C063B5a3Fe1BCC325364be62fc0f00, 0xB5cD48dD89C063B5a3Fe1BCC325364be62fc0f00,
0x0F75C6BFAF436Eba0cB977Dcdfb0F30b57ff9D05, 0x0F75C6BFAF436Eba0cB977Dcdfb0F30b57ff9D05,
0x6a2D058890ccA15BEaEe5050caaAd56B2aB54DD4, 0x6a2D058890ccA15BEaEe5050caaAd56B2aB54DD4,
0x62E142F218585827436f59997C301F7040396AD4, 0x62E142F218585827436f59997C301F7040396AD4,
0x85972458dfBf9269567b2a27C4ffC958A4f24761, 0x85972458dfBf9269567b2a27C4ffC958A4f24761,
0x078AD5DB2151083Ec16eCA1b26e2C98f79034DA8, 0x078AD5DB2151083Ec16eCA1b26e2C98f79034DA8,
0x3514Cfd42E4DDe9E65e283EbdBfa2888117823A6, 0x3514Cfd42E4DDe9E65e283EbdBfa2888117823A6,
0x550c9288310482F593602dD3e426603ae00BC352, 0x550c9288310482F593602dD3e426603ae00BC352,
0x3884e9b1E2b0f8D00666c9767B5602B709EeEE06, 0x3884e9b1E2b0f8D00666c9767B5602B709EeEE06,
0xc6C86Aa348Eaa0ef1b6F8Da90C279b670e67A55D, 0xc6C86Aa348Eaa0ef1b6F8Da90C279b670e67A55D,
0x9f340Bf3791809293DC50321Bd7F4c19120a98B6, 0x9f340Bf3791809293DC50321Bd7F4c19120a98B6,
0xF6CB46F9c2E34cD4B7f374D225e0aE5F474DdB32, 0xF6CB46F9c2E34cD4B7f374D225e0aE5F474DdB32,
0x076D4E32C6A5D888fC4658281539c94E778C796d, 0x076D4E32C6A5D888fC4658281539c94E778C796d,
0x28907F21F43B419F34226d6f10aCbCf1832b1D4d, 0x28907F21F43B419F34226d6f10aCbCf1832b1D4d,
0x6289C8a70EE2Ed6914834CaEa431F9a82c7eAf70, 0x6289C8a70EE2Ed6914834CaEa431F9a82c7eAf70,
0xE6B23CBae6a62f4b52A021B76E7811522eb82055, 0xE6B23CBae6a62f4b52A021B76E7811522eb82055,
0xAF02873D7dF5f3e6D5fF42F622F4e138A68208e7, 0xAF02873D7dF5f3e6D5fF42F622F4e138A68208e7,
0xb9C612760dC5456e5979393Cfe4AB1fF270AE9e5, 0xb9C612760dC5456e5979393Cfe4AB1fF270AE9e5,
0xa56963fe9F46C758B2D0616A754346A8F9eba30b, 0xa56963fe9F46C758B2D0616A754346A8F9eba30b,
0x56be1F8196cC4AefCe3348E679a2008496D14473, 0x56be1F8196cC4AefCe3348E679a2008496D14473,
0x63606C4011e97a73BCd844Cde6a38D45a728BC0E, 0x63606C4011e97a73BCd844Cde6a38D45a728BC0E,
0xE939c61Acd8bD30366435C6B1033251117851b03, 0xE939c61Acd8bD30366435C6B1033251117851b03,
0x3e9979106DA74AFB64b866218AeB47F224A312bb, 0x3e9979106DA74AFB64b866218AeB47F224A312bb,
0x28f1a9b8e3941C0909059eB84E5834154A99E0fC, 0x28f1a9b8e3941C0909059eB84E5834154A99E0fC,
0x9c8C81f3F5C19DFfeE7257Dd7477b8ef6E405e82, 0x9c8C81f3F5C19DFfeE7257Dd7477b8ef6E405e82,
0x644D4f3b293a7fc86eB4EFB6Bd2439f7603C991D, 0x644D4f3b293a7fc86eB4EFB6Bd2439f7603C991D,
0x25De357c61c9f2711A605b66E83887BA5Fd22ac1, 0x25De357c61c9f2711A605b66E83887BA5Fd22ac1,
0x78c88fF43cd503316e8A15B6d92b2EBFa73802B2, 0x78c88fF43cd503316e8A15B6d92b2EBFa73802B2,
0x2C42550Ff1Bdc139b54C5042a9a86A56398E9d83, 0x2C42550Ff1Bdc139b54C5042a9a86A56398E9d83,
0xf18673Ab6Eb72937607aA8388b8f7aa0AC3a0D32, 0xf18673Ab6Eb72937607aA8388b8f7aa0AC3a0D32,
0x3a1d526D09b7E59Fd88De4726f68A8246dDC2742, 0x3a1d526D09b7E59Fd88De4726f68A8246dDC2742,
0x7Ba6781620c91676B070D319E7E894BFd4A9eC81, 0x7Ba6781620c91676B070D319E7E894BFd4A9eC81,
0x9Ffbd3f9eE795A4fDa880ED553A2A4BD6D45CE5B, 0x9Ffbd3f9eE795A4fDa880ED553A2A4BD6D45CE5B,
0xe6184DA55174Cc0263a17eA2fc24E48511766505, 0xe6184DA55174Cc0263a17eA2fc24E48511766505,
0x36989535F0290eaC96692675cbf15a3BD2f42E46, 0x36989535F0290eaC96692675cbf15a3BD2f42E46,
0x12D92FeD171F16B3a05ACB1542B40648E7CEd384, 0x12D92FeD171F16B3a05ACB1542B40648E7CEd384,
0x08657a1f4C1F06d657F31767831421EE7FaDf549, 0x08657a1f4C1F06d657F31767831421EE7FaDf549,
0x42FecB4137aFF76E0E85702ff4F339DbFe6D859E, 0x42FecB4137aFF76E0E85702ff4F339DbFe6D859E,
0xc6e531CF18afE3a64bE19e40ac410f39FC9738da, 0xc6e531CF18afE3a64bE19e40ac410f39FC9738da,
0x9Ee26a4bFd731E8e742B65bF955814EADdd7F151, 0x9Ee26a4bFd731E8e742B65bF955814EADdd7F151,
0x7E3893725d4e238B4c8c83375bBAd024a66Ffa42, 0x7E3893725d4e238B4c8c83375bBAd024a66Ffa42,
0x465403d43f48Dfaa3F9385B60F0fEa36c360C18A, 0x465403d43f48Dfaa3F9385B60F0fEa36c360C18A,
0xc7285e85a6D11C762A7D9C57aC38E31A671E9777, 0xc7285e85a6D11C762A7D9C57aC38E31A671E9777,
0x74b6ea6B2EeFd3eF4da5E8c4C0480776035029c2, 0x74b6ea6B2EeFd3eF4da5E8c4C0480776035029c2,
0x14812AE927e2BA5aA0c0f3C0eA016b3039574242, 0x14812AE927e2BA5aA0c0f3C0eA016b3039574242,
0xdc957b6a3F630bEf2E6104C1a22dAeF9650b5349, 0xdc957b6a3F630bEf2E6104C1a22dAeF9650b5349,
0x1247749d7E28D357B4279110af0802603AC526cE, 0x1247749d7E28D357B4279110af0802603AC526cE,
0x1036AF02bCDb2e3A4db2d3D40b29e5054EDc79BA, 0x1036AF02bCDb2e3A4db2d3D40b29e5054EDc79BA,
0x3665B1E938Ce90c48502303ACB5049Fb065D3a85, 0x3665B1E938Ce90c48502303ACB5049Fb065D3a85,
0x87BeDf6AD81A2907633Ab68D02c44f0415bc68C1, 0x87BeDf6AD81A2907633Ab68D02c44f0415bc68C1,
0x0B45840cCEE39aeEfFDF621633d24AA8930B834c, 0x0B45840cCEE39aeEfFDF621633d24AA8930B834c,
0xcBD78860218160F4b463612f30806807Fe6E804C, 0xcBD78860218160F4b463612f30806807Fe6E804C,
0xa42303EE9B2eC1DB7E2a86Ed6C24AF7E49E9e8B9, 0xa42303EE9B2eC1DB7E2a86Ed6C24AF7E49E9e8B9,
0xa0109274F53609f6Be97ec5f3052C659AB80f012, 0xa0109274F53609f6Be97ec5f3052C659AB80f012,
0xb578603D3fB9216158c29488c1A902Dd0300c115, 0xb578603D3fB9216158c29488c1A902Dd0300c115,
0x7b81b8680b1abd1e2E983a1589DeB5468B50A544, 0x7b81b8680b1abd1e2E983a1589DeB5468B50A544,
0x4750BCfcC340AA4B31be7e71fa072716d28c29C5, 0x4750BCfcC340AA4B31be7e71fa072716d28c29C5,
0x36DD7b862746fdD3eDd3577c8411f1B76FDC2Af5, 0x36DD7b862746fdD3eDd3577c8411f1B76FDC2Af5,
0x0D13F55BA1509352F9e36190d948D7c45B854Be2, 0x0D13F55BA1509352F9e36190d948D7c45B854Be2,
0x4803c6ec3E61cD1bb1735bBDdB21732100AA13cc, 0x4803c6ec3E61cD1bb1735bBDdB21732100AA13cc,
0x1ee815AD4a914c2C2f4650b3ED34978F8Fe2fcC4, 0x1ee815AD4a914c2C2f4650b3ED34978F8Fe2fcC4,
0x04843E2C74018c8d94f1834a7ccB94c16691E451, 0x04843E2C74018c8d94f1834a7ccB94c16691E451,
0x0000208a6cC0299dA631C08fE8c2EDe435Ea83B8, 0x0000208a6cC0299dA631C08fE8c2EDe435Ea83B8,
0x853281B7676DFB66B87e2f26c9cB9D10Ce883F37, 0x853281B7676DFB66B87e2f26c9cB9D10Ce883F37,
0xaaaaD0b504B4CD22348C4Db1071736646Aa314C6, 0xaaaaD0b504B4CD22348C4Db1071736646Aa314C6,
0x5a0cB6505B3b99dD4035bb1Ac43cC51202d4e29F, 0x5a0cB6505B3b99dD4035bb1Ac43cC51202d4e29F,
0x7171717171866B60cc1A76A058ae20C8F703AE05, 0x7171717171866B60cc1A76A058ae20C8F703AE05,
0x30F96AEF199B399B722F8819c9b0723016CEAe6C, 0x30F96AEF199B399B722F8819c9b0723016CEAe6C,
0xEFa22d23de9f293B11e0c4aC865d7b440647587a, 0xEFa22d23de9f293B11e0c4aC865d7b440647587a,
0xC0F12799B8D3FA8810DfE1616095170C72117F8F, 0xC0F12799B8D3FA8810DfE1616095170C72117F8F,
0x996ad81FD83eD7A87FD3D03694115dff19db0B3b, 0x996ad81FD83eD7A87FD3D03694115dff19db0B3b,
0x000000Cd6521Ed1a65FAe0678eA15aF4EEAD74fe, 0x000000Cd6521Ed1a65FAe0678eA15aF4EEAD74fe,
0x15980A3Bd6ed317f42d2eD0DCf3d3D730b6Bc0C5, 0x15980A3Bd6ed317f42d2eD0DCf3d3D730b6Bc0C5,
0x7853E027F37830790685622cdd8685fF0c8255A2, 0x7853E027F37830790685622cdd8685fF0c8255A2,
0xf0D9b969925116074eF43e7887Bcf035Ff1e7B19, 0xf0D9b969925116074eF43e7887Bcf035Ff1e7B19,
0x97096F56B09F6aaA4230eec3BA33249995690B0E, 0x97096F56B09F6aaA4230eec3BA33249995690B0E,
0x5555555731006f71f121144534Ca7C8799F66AA3, 0x5555555731006f71f121144534Ca7C8799F66AA3,
0x5007565e69E5c23C278c2e976beff38eF4D27B3d, 0x5007565e69E5c23C278c2e976beff38eF4D27B3d,
0x2ffAc4D796261ba8964d859867592B952b9FC158, 0x2ffAc4D796261ba8964d859867592B952b9FC158,
0xCEdac436cEA98E93F471331eCC693fF41D730921, 0xCEdac436cEA98E93F471331eCC693fF41D730921,
0x94596B6A626392F5D972D6CC4D929a42c2f0008c, 0x94596B6A626392F5D972D6CC4D929a42c2f0008c,
0x065f2A0eF62878e8951af3c387E4ddC944f1B8F4, 0x065f2A0eF62878e8951af3c387E4ddC944f1B8F4,
0xe7c490986FC34248F77b813eD6C8971e76e0384C, 0xe7c490986FC34248F77b813eD6C8971e76e0384C,
0xC49415493eB3Ec64a0F13D8AA5056f1CfC4ce35c 0xC49415493eB3Ec64a0F13D8AA5056f1CfC4ce35c
]; ];
function testRelayerBalancesSum() public view { function testRelayerBalancesSum() public view {
RelayerRegistry registry = RelayerRegistry(getRelayerRegistryProxyAddress()); RelayerRegistry registry = RelayerRegistry(getRelayerRegistryProxyAddress());
uint256 relayersBalancesSum; uint256 relayersBalancesSum;
for (uint256 i = 0; i < allRelayersAddresses.length; i++) { for (uint256 i = 0; i < allRelayersAddresses.length; i++) {
uint256 currentRelayerBalance = registry.getRelayerBalance(allRelayersAddresses[i]); uint256 currentRelayerBalance = registry.getRelayerBalance(allRelayersAddresses[i]);
relayersBalancesSum += currentRelayerBalance; relayersBalancesSum += currentRelayerBalance;
console2.log("Relayer %s, relayer balance: %s TORN", allRelayersAddresses[i], currentRelayerBalance / 10e17); console2.log(
} "Relayer %s, relayer balance: %s TORN", allRelayersAddresses[i], currentRelayerBalance / 10e17
);
console2.log("\nSum of relayer balances on block %s: %s TORN", block.number, relayersBalancesSum / 10e17); }
require(relayersBalancesSum > 0); console2.log(
} "\nSum of relayer balances on block %s: %s TORN", block.number, relayersBalancesSum / 10e17
} );
require(relayersBalancesSum > 0);
}
}

View File

@@ -1,92 +1,132 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.6.12; pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import { console2 } from "@forge-std/console2.sol"; import { console2 } from "@forge-std/console2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MockProposal } from "./MockProposal.sol"; import { MockProposal } from "./MockProposal.sol";
import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol"; import { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol"; import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol"; import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol";
contract TestGovernanceStakingRewards is MockProposal { contract TestGovernanceStakingRewards is MockProposal {
function burnTokens(address caller, uint256 amount, TornadoStakingRewards staking) internal { modifier replenishGovernanceStakingBalanceBefore() {
vm.startPrank(caller); vm.startPrank(_governanceAddress);
staking.addBurnRewards(amount); IERC20(_tokenAddress).transfer(getStakingProxyAddress(), 100_000 ether);
vm.stopPrank(); vm.stopPrank();
} _;
}
function testAccumulatedRewardCanBeUpdated() public executeAttackerProposalBefore executeCurrentProposalBefore {
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress()); function burnTokens(address caller, uint256 amount, TornadoStakingRewards staking) internal {
vm.startPrank(caller);
uint256 accumulatedRewardPerTornBeforeBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply; staking.addBurnRewards(amount);
vm.stopPrank();
console2.log("Accumulated reward per TORN right after proposal execution: %s TORN", accumulatedRewardPerTornBeforeBurning / 10e17); }
burnTokens(_governanceAddress, 10_000_000 ether, staking); function testAccumulatedRewardCanBeUpdated()
public
uint256 accumulatedRewardPerTornAfterBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply; executeAttackerProposalBefore
executeCurrentProposalBefore
console2.log("Accumulated reward per TORN after burning 10 000 000 TORN: ~ %s TORN", accumulatedRewardPerTornAfterBurning / 10e17); {
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
require(accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning, "Staking rewards isn't updated");
} uint256 accumulatedRewardPerTornBeforeBurning =
staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
function testStakerCanGetRewards() public executeAttackerProposalBefore executeCurrentProposalBefore {
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress()); console2.log(
IERC20 TORN = IERC20(_tokenAddress); "Accumulated reward per TORN right after proposal execution: %s TORN",
accumulatedRewardPerTornBeforeBurning / _tornDecimals
uint256 toBurn = 10_000 ether; );
// Remind that we have locked in Governance 25 000 TORN for TEST_ADDRESS_ONE while voting burnTokens(_governanceAddress, 10_000_000 ether, staking);
uint256 stakerLockedBalance = governance.lockedBalance(TEST_ADDRESS_ONE);
require(stakerLockedBalance == 25_000 ether, "Invalid test staker locked balance"); uint256 accumulatedRewardPerTornAfterBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_ADDRESS_ONE); console2.log(
console2.log("Staking rewards before burning: %s TORN", stakerRewardsBeforeBurning / 10e17); "Accumulated reward per TORN after burning 10 000 000 TORN: ~ %s TORN",
accumulatedRewardPerTornAfterBurning / _tornDecimals
burnTokens(_governanceAddress, toBurn, staking); );
uint256 stakerRewardsAfterBurning = staking.checkReward(TEST_ADDRESS_ONE); require(
console2.log("Staking rewards after burning 10 000 TORN: %s TORN\n", stakerRewardsAfterBurning / 10e17); accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning,
require(stakerRewardsAfterBurning > stakerRewardsBeforeBurning, "Rewards isn't changed after burning"); "Staking rewards isn't updated"
);
// All TORN, locked by users in Governance, is on the userVault contract balance }
uint256 governanceLockedAmount = TORN.balanceOf(address(governance.userVault()));
uint256 receivedReward = stakerRewardsAfterBurning - stakerRewardsBeforeBurning; function testStakerCanGetRewards()
uint256 expectedRewards = stakerLockedBalance * toBurn / governanceLockedAmount; public
executeAttackerProposalBefore
console2.log("Expected staking rewards: %s TORN", expectedRewards / 10e17); executeCurrentProposalBefore
console2.log("Staker received rewards: %s TORN\n", receivedReward / 10e17); replenishGovernanceStakingBalanceBefore
{
require(receivedReward == expectedRewards, "Expected and received rewards don't match"); TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
IERC20 TORN = IERC20(_tokenAddress);
uint256 stakerTORNbalanceBeforeGettingRewards = TORN.balanceOf(TEST_ADDRESS_ONE);
console2.log("Staker balance before getting (withdrawal) collected rewards: %s TORN", stakerTORNbalanceBeforeGettingRewards / 10e17); uint256 toBurn = 10_000 ether;
vm.startPrank(TEST_ADDRESS_ONE); retrieveAndLockBalance(TEST_STAKER_PRIVATE_KEY, TEST_STAKER_ADDRESS, PROPOSAL_THRESHOLD);
staking.getReward(); uint256 stakerLockedBalance = governance.lockedBalance(TEST_STAKER_ADDRESS);
vm.stopPrank(); require(stakerLockedBalance == PROPOSAL_THRESHOLD, "Invalid test staker locked balance");
uint256 stakerTORNbalanceAfterGettingRewards = TORN.balanceOf(TEST_ADDRESS_ONE); uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_STAKER_ADDRESS);
console2.log("Staker balance after getting (withdrawal) collected rewards: %s TORN", stakerTORNbalanceAfterGettingRewards / 10e17); console2.log("Staking rewards before burning: %s TORN", stakerRewardsBeforeBurning / _tornDecimals);
require(stakerTORNbalanceAfterGettingRewards > stakerTORNbalanceBeforeGettingRewards, "Rewards isn't withdrawed"); burnTokens(_governanceAddress, toBurn, staking);
require(
stakerTORNbalanceAfterGettingRewards - stakerTORNbalanceBeforeGettingRewards == receivedReward, uint256 stakerRewardsAfterBurning = staking.checkReward(TEST_STAKER_ADDRESS);
"Incorrect rewards amount withdrawed" console2.log(
); "Staking rewards after burning 10 000 TORN: %s TORN\n", stakerRewardsAfterBurning / _tornDecimals
} );
require(stakerRewardsAfterBurning > stakerRewardsBeforeBurning, "Rewards isn't changed after burning");
function testCanBurnFromRelayerRegistry() public executeAttackerProposalBefore executeCurrentProposalBefore {
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress()); // All TORN, locked by users in Governance, is on the userVault contract balance
address relayerRegistryAddress = getRelayerRegistryProxyAddress(); uint256 governanceLockedAmount = TORN.balanceOf(address(governance.userVault()));
uint256 receivedReward = stakerRewardsAfterBurning - stakerRewardsBeforeBurning;
uint256 accumulatedRewardPerTornBeforeBurning = staking.accumulatedRewardPerTorn(); uint256 expectedRewards = stakerLockedBalance * toBurn / governanceLockedAmount;
burnTokens(relayerRegistryAddress, 10_000 ether, staking); console2.log("Expected staking rewards: %s TORN", expectedRewards / _tornDecimals);
console2.log("Staker received rewards: %s TORN\n", receivedReward / _tornDecimals);
require(staking.accumulatedRewardPerTorn() > accumulatedRewardPerTornBeforeBurning, "Burn from relayer registry failed");
} require(receivedReward == expectedRewards, "Expected and received rewards don't match");
}
uint256 stakerTORNbalanceBeforeGettingRewards = TORN.balanceOf(TEST_STAKER_ADDRESS);
console2.log(
"Staker balance before getting (withdrawal) collected rewards: %s TORN",
stakerTORNbalanceBeforeGettingRewards / _tornDecimals
);
vm.startPrank(TEST_STAKER_ADDRESS);
staking.getReward();
vm.stopPrank();
uint256 stakerTORNbalanceAfterGettingRewards = TORN.balanceOf(TEST_STAKER_ADDRESS);
console2.log(
"Staker balance after getting (withdrawal) collected rewards: %s TORN",
stakerTORNbalanceAfterGettingRewards / _tornDecimals
);
require(
stakerTORNbalanceAfterGettingRewards > stakerTORNbalanceBeforeGettingRewards,
"Rewards isn't withdrawed"
);
require(
stakerTORNbalanceAfterGettingRewards - stakerTORNbalanceBeforeGettingRewards == receivedReward,
"Incorrect rewards amount withdrawed"
);
}
function testCanBurnFromRelayerRegistry() public {
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
address relayerRegistryAddress = getRelayerRegistryProxyAddress();
uint256 accumulatedRewardPerTornBeforeBurning = staking.accumulatedRewardPerTorn();
burnTokens(relayerRegistryAddress, 10_000 ether, staking);
require(
staking.accumulatedRewardPerTorn() > accumulatedRewardPerTornBeforeBurning,
"Burn from relayer registry failed"
);
}
}

44
test/forge/Utils.sol Normal file
View File

@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20Permit } from "torn-token/contracts/ERC20Permit.sol";
import { Test } from "@forge-std/Test.sol";
import { Parameters } from "@proprietary/Parameters.sol";
import { Mock } from "./Mock.sol";
import { IGovernance } from "@interfaces/IGovernance.sol";
contract Utils is Parameters, Mock, Test {
function retrieveAndLockBalance(uint256 privateKey, address voter, uint256 amount) public {
uint256 lockTimestamp = block.timestamp + PROPOSAL_DURATION;
uint256 accountNonce = ERC20Permit(_tokenAddress).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(_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();
/* ----------------------------*/
}
}

View File

@@ -24,6 +24,8 @@ struct Proposal {
interface IGovernance { interface IGovernance {
function proposals(uint256 index) external view returns (Proposal memory); function proposals(uint256 index) external view returns (Proposal memory);
function proposalCount() external view returns (uint256);
function lockedBalance(address account) external view returns (uint256); function lockedBalance(address account) external view returns (uint256);
function propose(address target, string memory description) external returns (uint256); function propose(address target, string memory description) external returns (uint256);

5893
yarn.lock

File diff suppressed because it is too large Load Diff