Compare commits
15 Commits
ff9f10c5b3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 053da5391f | |||
| 04ba45b2c1 | |||
| f0f5424edf | |||
| bca8932686 | |||
| 5fbeaa7a72 | |||
| baf416acaf | |||
| 7f0433bd33 | |||
| 2031afdbe8 | |||
| 195840d678 | |||
| 77b361d8a5 | |||
| 9525b3dae4 | |||
|
|
2aabdc4bbe | ||
|
|
7cc789f48f | ||
| f3df5c0b14 | |||
| fd57f13e63 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,7 +4,7 @@ out/
|
||||
node_modules/
|
||||
|
||||
# Ignores development broadcast logs
|
||||
!/broadcast
|
||||
/broadcast
|
||||
/broadcast/*/31337/
|
||||
/broadcast/**/dry-run/
|
||||
|
||||
@@ -16,3 +16,4 @@ docs/
|
||||
|
||||
# yarn
|
||||
yarn-error*
|
||||
yarn.lock
|
||||
47
README.md
47
README.md
@@ -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)
|
||||
|
||||
@@ -9,7 +9,7 @@ optimizer-runs = 10_000_000
|
||||
|
||||
[fmt]
|
||||
|
||||
line_length = 140
|
||||
line_length = 110
|
||||
bracket_spacing = true
|
||||
multiline_func_header = 'attributes_first'
|
||||
number_underscore = 'thousands'
|
||||
4963
package-lock.json
generated
Normal file
4963
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,12 @@
|
||||
"author": "Theo",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17336117 --no-match-contract TestRelayerBalance --gas-report",
|
||||
"relayerBalancesSum": "forge test -vvv --fork-url https://rpc.mevblocker.io --fork-block-number 17304425 --match-contract TestRelayerBalance --gas-report"
|
||||
"test": "npm run test:all",
|
||||
"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": {
|
||||
"@gnosis.pm/ido-contracts": "^0.5.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@proprietary/=src/proprietary/
|
||||
@interfaces/=src/interfaces/
|
||||
@interfaces/=test/forge/interfaces/
|
||||
@root/=src/
|
||||
@forge-std/=lib/forge-std/src/
|
||||
|
||||
|
||||
34
script/DeployScript.sol
Normal file
34
script/DeployScript.sol
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,11 @@ contract Parameters {
|
||||
address constant _governanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||
address constant _governanceVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185;
|
||||
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 _tornDecimals = 1e18;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ abstract contract Delegation is Core {
|
||||
|
||||
function delegate(address to) external {
|
||||
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)) {
|
||||
emit Undelegated(msg.sender, previous);
|
||||
}
|
||||
@@ -29,12 +32,18 @@ abstract contract Delegation is Core {
|
||||
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");
|
||||
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 {
|
||||
for (uint256 i = 0; i < from.length; i++) {
|
||||
|
||||
@@ -68,7 +68,12 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
|
||||
/// @notice An event emitted when a new proposal is created
|
||||
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
|
||||
@@ -102,7 +107,10 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
_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);
|
||||
_transferTokens(owner, amount);
|
||||
}
|
||||
@@ -135,7 +143,9 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
returns (uint256)
|
||||
{
|
||||
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
|
||||
require(Address.isContract(target), "Governance::propose: not a contract");
|
||||
|
||||
@@ -143,7 +153,8 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
if (latestProposalId != 0) {
|
||||
ProposalState proposersLatestProposalState = state(latestProposalId);
|
||||
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"
|
||||
);
|
||||
}
|
||||
@@ -172,7 +183,10 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
}
|
||||
|
||||
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.executed = true;
|
||||
|
||||
@@ -226,7 +240,9 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
receipt.hasVoted = true;
|
||||
receipt.support = support;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -252,7 +268,10 @@ contract Governance is Initializable, Configuration, Delegation, EnsResolve {
|
||||
return ProposalState.Pending;
|
||||
} else if (getBlockTimestamp() <= proposal.endTime) {
|
||||
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;
|
||||
} else if (proposal.executed) {
|
||||
return ProposalState.Executed;
|
||||
|
||||
@@ -13,7 +13,11 @@ contract LoopbackProxy is TransparentUpgradeableProxy, EnsResolve {
|
||||
/**
|
||||
* @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.
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
|
||||
* @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
|
||||
modifier onlyMultisig() {
|
||||
@@ -40,7 +44,11 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
|
||||
*
|
||||
*/
|
||||
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
|
||||
*
|
||||
*/
|
||||
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");
|
||||
_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
|
||||
@@ -148,7 +165,9 @@ contract GovernanceGasUpgrade is GovernanceVaultUpgrade, GasCompensator {
|
||||
{
|
||||
for (uint256 i = 0; i < from.length; 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");
|
||||
_castVote(delegator, proposalId, support);
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ contract AdminUpgradeableProxy is TransparentUpgradeableProxy {
|
||||
/**
|
||||
* @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.
|
||||
|
||||
@@ -37,7 +37,10 @@ contract GovernancePatchUpgrade is GovernanceStakingUpgrade {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -3,101 +3,76 @@
|
||||
pragma solidity ^0.6.12;
|
||||
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 { 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 { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
|
||||
import { RelayerRegistry } from "./RelayerRegistry.sol";
|
||||
|
||||
/**
|
||||
* @notice Contract which should help the proposal deploy the necessary contracts.
|
||||
*/
|
||||
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.
|
||||
* @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 PatchProposal {
|
||||
using SafeMath for uint256;
|
||||
using Address for address;
|
||||
// Address of the old staking proxy
|
||||
address public constant oldStakingProxyAddress = 0x2FC93484614a34f26F7970CBB94615bA109BB4bf;
|
||||
|
||||
address public immutable feeManagerAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7;
|
||||
address public immutable ensAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
|
||||
address public immutable registry = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
|
||||
// Address of the registry proxy
|
||||
address public constant registryProxyAddress = 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);
|
||||
|
||||
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 {
|
||||
patchProposalContractsFactory = PatchProposalContractsFactory(_patchProposalContractsFactory);
|
||||
// The registry implementation (with same code) that we've deployed
|
||||
address public immutable deployedRelayerRegistryImplementationAddress;
|
||||
|
||||
constructor(
|
||||
address _deployedStakingProxyContractAddress,
|
||||
address _deployedRelayerRegistryImplementationAddress
|
||||
) public {
|
||||
deployedStakingProxyContractAddress = _deployedStakingProxyContractAddress;
|
||||
deployedRelayerRegistryImplementationAddress = _deployedRelayerRegistryImplementationAddress;
|
||||
}
|
||||
|
||||
/// @notice Function to execute the proposal.
|
||||
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
|
||||
TornadoStakingRewards oldStaking = TornadoStakingRewards(address(GovernancePatchUpgrade(governance).Staking()));
|
||||
TornadoStakingRewards oldStaking = TornadoStakingRewards(oldStakingProxyAddress);
|
||||
|
||||
// Get the small amount of TORN left
|
||||
oldStaking.withdrawTorn(TORN.balanceOf(address(oldStaking)));
|
||||
|
||||
// And create a new staking logic contract
|
||||
TornadoStakingRewards newStakingImplementation =
|
||||
TornadoStakingRewards(patchProposalContractsFactory.createStakingRewards(address(governance), address(TORN), registry));
|
||||
|
||||
// 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
|
||||
AdminUpgradeableProxy(payable(registryProxyAddress)).upgradeTo(
|
||||
deployedRelayerRegistryImplementationAddress
|
||||
);
|
||||
|
||||
// Upgrade the registry proxy
|
||||
AdminUpgradeableProxy(payable(registry)).upgradeTo(newRegistryImplementationAddress);
|
||||
// Now upgrade the governance implementation to the vulnerability resistant one
|
||||
LoopbackProxy(governanceProxyAddress).upgradeTo(
|
||||
address(
|
||||
new GovernancePatchUpgrade(
|
||||
deployedStakingProxyContractAddress,
|
||||
gasCompensationVaultAddress,
|
||||
userVaultAddress
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Now upgrade the governance to the latest stuff
|
||||
LoopbackProxy(payable(governance)).upgradeTo(address(new GovernancePatchUpgrade(address(newStaking), gasComp, vault)));
|
||||
|
||||
// Return TORNs, which were withdrawn by bug, to Governance Staking contract
|
||||
TORN.transfer(address(newStaking), 94_092 ether);
|
||||
// Transfer TORN in compensation to the staking proxy
|
||||
TORN.transfer(deployedStakingProxyContractAddress, 94_092 ether);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
governance = _governance;
|
||||
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
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -179,7 +183,12 @@ contract RelayerRegistry is Initializable, EnsResolve {
|
||||
_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();
|
||||
require(relayer == ens.owner(ensHash), "only ens owner");
|
||||
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
|
||||
*
|
||||
*/
|
||||
function stakeToRelayerPermit(address relayer, uint256 stake, address staker, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
|
||||
external
|
||||
{
|
||||
function stakeToRelayerPermit(
|
||||
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);
|
||||
_stakeToRelayer(staker, relayer, stake);
|
||||
}
|
||||
|
||||
@@ -85,8 +85,9 @@ contract TornadoStakingRewards is Initializable, EnsResolve {
|
||||
*/
|
||||
function addBurnRewards(uint256 amount) external {
|
||||
require(msg.sender == address(Governance) || msg.sender == relayerRegistry, "unauthorized");
|
||||
accumulatedRewardPerTorn =
|
||||
accumulatedRewardPerTorn.add(amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault()))));
|
||||
accumulatedRewardPerTorn = accumulatedRewardPerTorn.add(
|
||||
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
|
||||
*
|
||||
*/
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) external onlyGovernance {
|
||||
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand)
|
||||
external
|
||||
onlyGovernance
|
||||
{
|
||||
uint256 claimed = _updateReward(account, amountLockedBeforehand);
|
||||
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
|
||||
* @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) {
|
||||
claimed =
|
||||
(accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(amountLockedBeforehand).div(ratioConstant);
|
||||
claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
|
||||
amountLockedBeforehand
|
||||
).div(ratioConstant);
|
||||
}
|
||||
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
|
||||
emit RewardsUpdated(account, claimed);
|
||||
@@ -135,7 +143,9 @@ contract TornadoStakingRewards is Initializable, EnsResolve {
|
||||
function checkReward(address account) external view returns (uint256 rewards) {
|
||||
uint256 amountLocked = Governance.lockedBalance(account);
|
||||
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]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
pragma solidity 0.6.12;
|
||||
|
||||
interface IMetamorphicContractFactory {
|
||||
function findMetamorphicContractAddress(bytes32 salt) external view returns (address metamorphicContractAddress);
|
||||
function findMetamorphicContractAddress(bytes32 salt)
|
||||
external
|
||||
view
|
||||
returns (address metamorphicContractAddress);
|
||||
|
||||
function deployMetamorphicContractFromExistingImplementation(
|
||||
bytes32 salt,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,20 @@ contract Mock {
|
||||
address public constant TEST_REAL_ADDRESS_WITH_BALANCE = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58;
|
||||
address public constant TEST_RELAYER_ADDRESS = 0x30F96AEF199B399B722F8819c9b0723016CEAe6C; // moon-relayer.eth (just for testing)
|
||||
|
||||
uint256 public constant TEST_PRIVATE_KEY_ONE = 0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0;
|
||||
uint256 public constant TEST_PRIVATE_KEY_TWO = 0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064;
|
||||
// Address and private key to test staking, Governance lock and rewards accruals
|
||||
address public constant TEST_STAKER_ADDRESS = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
||||
uint256 public constant TEST_STAKER_PRIVATE_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
|
||||
|
||||
// Two test accounts to create proposal and vote for it
|
||||
uint256 public constant TEST_PRIVATE_KEY_ONE =
|
||||
0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0;
|
||||
uint256 public constant TEST_PRIVATE_KEY_TWO =
|
||||
0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064;
|
||||
address public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
|
||||
address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
|
||||
|
||||
|
||||
uint256 public constant STAKING_FIX_PROPOSAL_ID = 22;
|
||||
uint256 public constant ATTACKER_PROPOSAL_ID = 21; // Last attacker proposal (to restore Governance Vault balance) id
|
||||
|
||||
uint256 public constant PROPOSAL_VOTING_DURATION = 5 days;
|
||||
64
test/forge/MockProposal.sol
Normal file
64
test/forge/MockProposal.sol
Normal 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());
|
||||
}
|
||||
}
|
||||
64
test/forge/ProposalUtils.sol
Normal file
64
test/forge/ProposalUtils.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,22 @@ contract TestContractsState is MockProposal {
|
||||
uint256 lockedBalanceAfterExecution = governance.lockedBalance(TEST_REAL_ADDRESS_WITH_BALANCE);
|
||||
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);
|
||||
console2.log("Bugged value of accumulated rewards per TORN: %s", oldStaking.accumulatedRewardPerTorn());
|
||||
console2.log(
|
||||
"User rewards balance (bugged) before execution: %s TORN",
|
||||
accumulatedRewardsBeforeExecution / 10 ** 18
|
||||
);
|
||||
console2.log(
|
||||
"Bugged value of accumulated rewards per TORN: %s\n", oldStaking.accumulatedRewardPerTorn()
|
||||
);
|
||||
|
||||
createAndExecuteProposal();
|
||||
|
||||
@@ -33,8 +41,13 @@ contract TestContractsState is MockProposal {
|
||||
uint256 accumulatedRewardsPerTORNAfterExecution = newStaking.accumulatedRewardPerTorn();
|
||||
uint256 accumulatedRewardsAfterExecution = newStaking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE);
|
||||
|
||||
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);
|
||||
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
|
||||
);
|
||||
|
||||
require(accumulatedRewardsBeforeExecution >= accumulatedRewardsAfterExecution, "Wtf");
|
||||
require(accumulatedRewardsAfterExecution == 0, "Accumulated rewards isn't nullified");
|
||||
@@ -59,10 +72,14 @@ contract TestContractsState is MockProposal {
|
||||
uint256 relayerStakedBalanceAfterExecution = registry.getRelayerBalance(TEST_RELAYER_ADDRESS);
|
||||
|
||||
console2.log(
|
||||
"Relayer balance in relayer registry contract after proposal execution: %s TORN", relayerStakedBalanceAfterExecution / 10 ** 18
|
||||
"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");
|
||||
require(
|
||||
relayerStakedBalanceBeforeExecution == relayerStakedBalanceAfterExecution,
|
||||
"Relayer stake balance differs after execution"
|
||||
);
|
||||
}
|
||||
}
|
||||
47
test/forge/TestGovernanceProtection.sol
Normal file
47
test/forge/TestGovernanceProtection.sol
Normal 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ contract TestProxyUpdating is MockProposal {
|
||||
}
|
||||
|
||||
function getUpgradeableProxyImplementationAddress(address proxy) internal view returns (address) {
|
||||
bytes32 proxyImplementationSlot = bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);
|
||||
bytes32 proxyImplementationSlot =
|
||||
bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);
|
||||
|
||||
return getAddressFromSlot(proxy, proxyImplementationSlot);
|
||||
}
|
||||
@@ -36,25 +37,36 @@ contract TestProxyUpdating is MockProposal {
|
||||
|
||||
function testGovernanceStakingProxyUpdated() public {
|
||||
address stakingProxyAddressBeforeExecution = getStakingProxyAddress();
|
||||
console2.log("Staking proxy address before proposal execution: %s", stakingProxyAddressBeforeExecution);
|
||||
console2.log(
|
||||
"Staking proxy address before proposal execution: %s", stakingProxyAddressBeforeExecution
|
||||
);
|
||||
|
||||
createAndExecuteProposal();
|
||||
|
||||
address stakingProxyAddressAfterExecution = getStakingProxyAddress();
|
||||
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));
|
||||
}
|
||||
|
||||
function testGovernanceStakingImplementationUpdated() public {
|
||||
address stakingImplementationAddressBeforeExecution = getStakingImplementationAddress();
|
||||
console2.log("Staking implementation address before proposal execution: %s", stakingImplementationAddressBeforeExecution);
|
||||
console2.log(
|
||||
"Staking implementation address before proposal execution: %s",
|
||||
stakingImplementationAddressBeforeExecution
|
||||
);
|
||||
|
||||
createAndExecuteProposal();
|
||||
|
||||
address stakingImplementationAddressAfterExecution = getStakingImplementationAddress();
|
||||
console2.log("Staking implementation address after proposal execution: %s", stakingImplementationAddressAfterExecution);
|
||||
console2.log(
|
||||
"Staking implementation address after proposal execution: %s",
|
||||
stakingImplementationAddressAfterExecution
|
||||
);
|
||||
|
||||
require(
|
||||
stakingImplementationAddressBeforeExecution != stakingImplementationAddressAfterExecution,
|
||||
@@ -64,33 +76,45 @@ contract TestProxyUpdating is MockProposal {
|
||||
}
|
||||
|
||||
function testRelayerRegistryImplementationUpdated() public {
|
||||
address relayerRegistryImplementationAddressBeforeExecution = getRelayerRegistryImplementationAddress();
|
||||
address relayerRegistryImplementationAddressBeforeExecution =
|
||||
getRelayerRegistryImplementationAddress();
|
||||
console2.log(
|
||||
"Relayer registry implementation address before proposal execution: %s", relayerRegistryImplementationAddressBeforeExecution
|
||||
"Relayer registry implementation address before proposal execution: %s",
|
||||
relayerRegistryImplementationAddressBeforeExecution
|
||||
);
|
||||
|
||||
createAndExecuteProposal();
|
||||
|
||||
address relayerRegistryImplementationAddressAfterExecution = getRelayerRegistryImplementationAddress();
|
||||
console2.log(
|
||||
"Relayer registry implementation address after proposal execution: %s", relayerRegistryImplementationAddressAfterExecution
|
||||
"Relayer registry implementation address after proposal execution: %s",
|
||||
relayerRegistryImplementationAddressAfterExecution
|
||||
);
|
||||
|
||||
require(
|
||||
relayerRegistryImplementationAddressBeforeExecution != relayerRegistryImplementationAddressAfterExecution,
|
||||
relayerRegistryImplementationAddressBeforeExecution
|
||||
!= relayerRegistryImplementationAddressAfterExecution,
|
||||
"Relayer registry implementation address didn't changed"
|
||||
);
|
||||
require(isContract(relayerRegistryImplementationAddressAfterExecution));
|
||||
}
|
||||
|
||||
function testGovernanceImplementationUpdated() public {
|
||||
address governanceImplementationAddressBeforeExecution = getUpgradeableProxyImplementationAddress(_governanceAddress);
|
||||
console2.log("Governance implementation address before proposal execution: %s", governanceImplementationAddressBeforeExecution);
|
||||
address governanceImplementationAddressBeforeExecution =
|
||||
getUpgradeableProxyImplementationAddress(_governanceAddress);
|
||||
console2.log(
|
||||
"Governance implementation address before proposal execution: %s",
|
||||
governanceImplementationAddressBeforeExecution
|
||||
);
|
||||
|
||||
createAndExecuteProposal();
|
||||
|
||||
address governanceImplementationAddressAfterExecution = getUpgradeableProxyImplementationAddress(_governanceAddress);
|
||||
console2.log("Governance implementation address after proposal execution: %s", governanceImplementationAddressAfterExecution);
|
||||
address governanceImplementationAddressAfterExecution =
|
||||
getUpgradeableProxyImplementationAddress(_governanceAddress);
|
||||
console2.log(
|
||||
"Governance implementation address after proposal execution: %s",
|
||||
governanceImplementationAddressAfterExecution
|
||||
);
|
||||
|
||||
require(
|
||||
governanceImplementationAddressBeforeExecution != governanceImplementationAddressAfterExecution,
|
||||
@@ -101,12 +125,16 @@ contract TestProxyUpdating is MockProposal {
|
||||
|
||||
function testRelayerRegistryProxyNotUpdated() public {
|
||||
address relayerRegistryProxyAddressBeforeExecution = getRelayerRegistryProxyAddress();
|
||||
console2.log("Relayer registry proxy before proposal execution: %s", relayerRegistryProxyAddressBeforeExecution);
|
||||
console2.log(
|
||||
"Relayer registry proxy before proposal execution: %s", relayerRegistryProxyAddressBeforeExecution
|
||||
);
|
||||
|
||||
createAndExecuteProposal();
|
||||
|
||||
address relayerRegistryProxyAddressAfterExecution = getRelayerRegistryProxyAddress();
|
||||
console2.log("Relayer registry proxyafter proposal execution: %s", relayerRegistryProxyAddressAfterExecution);
|
||||
console2.log(
|
||||
"Relayer registry proxyafter proposal execution: %s", relayerRegistryProxyAddressAfterExecution
|
||||
);
|
||||
|
||||
require(
|
||||
relayerRegistryProxyAddressBeforeExecution == relayerRegistryProxyAddressAfterExecution,
|
||||
@@ -113,10 +113,14 @@ contract TestRelayerBalance is MockProposal {
|
||||
for (uint256 i = 0; i < allRelayersAddresses.length; i++) {
|
||||
uint256 currentRelayerBalance = registry.getRelayerBalance(allRelayersAddresses[i]);
|
||||
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);
|
||||
console2.log(
|
||||
"\nSum of relayer balances on block %s: %s TORN", block.number, relayersBalancesSum / 10e17
|
||||
);
|
||||
|
||||
require(relayersBalancesSum > 0);
|
||||
}
|
||||
@@ -11,45 +11,73 @@ import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
|
||||
import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol";
|
||||
|
||||
contract TestGovernanceStakingRewards is MockProposal {
|
||||
modifier replenishGovernanceStakingBalanceBefore() {
|
||||
vm.startPrank(_governanceAddress);
|
||||
IERC20(_tokenAddress).transfer(getStakingProxyAddress(), 100_000 ether);
|
||||
vm.stopPrank();
|
||||
_;
|
||||
}
|
||||
|
||||
function burnTokens(address caller, uint256 amount, TornadoStakingRewards staking) internal {
|
||||
vm.startPrank(caller);
|
||||
staking.addBurnRewards(amount);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testAccumulatedRewardCanBeUpdated() public executeAttackerProposalBefore executeCurrentProposalBefore {
|
||||
function testAccumulatedRewardCanBeUpdated()
|
||||
public
|
||||
executeAttackerProposalBefore
|
||||
executeCurrentProposalBefore
|
||||
{
|
||||
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
|
||||
|
||||
uint256 accumulatedRewardPerTornBeforeBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
|
||||
uint256 accumulatedRewardPerTornBeforeBurning =
|
||||
staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
|
||||
|
||||
console2.log("Accumulated reward per TORN right after proposal execution: %s TORN", accumulatedRewardPerTornBeforeBurning / 10e17);
|
||||
console2.log(
|
||||
"Accumulated reward per TORN right after proposal execution: %s TORN",
|
||||
accumulatedRewardPerTornBeforeBurning / _tornDecimals
|
||||
);
|
||||
|
||||
burnTokens(_governanceAddress, 10_000_000 ether, staking);
|
||||
|
||||
uint256 accumulatedRewardPerTornAfterBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
|
||||
|
||||
console2.log("Accumulated reward per TORN after burning 10 000 000 TORN: ~ %s TORN", accumulatedRewardPerTornAfterBurning / 10e17);
|
||||
console2.log(
|
||||
"Accumulated reward per TORN after burning 10 000 000 TORN: ~ %s TORN",
|
||||
accumulatedRewardPerTornAfterBurning / _tornDecimals
|
||||
);
|
||||
|
||||
require(accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning, "Staking rewards isn't updated");
|
||||
require(
|
||||
accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning,
|
||||
"Staking rewards isn't updated"
|
||||
);
|
||||
}
|
||||
|
||||
function testStakerCanGetRewards() public executeAttackerProposalBefore executeCurrentProposalBefore {
|
||||
function testStakerCanGetRewards()
|
||||
public
|
||||
executeAttackerProposalBefore
|
||||
executeCurrentProposalBefore
|
||||
replenishGovernanceStakingBalanceBefore
|
||||
{
|
||||
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
|
||||
IERC20 TORN = IERC20(_tokenAddress);
|
||||
|
||||
uint256 toBurn = 10_000 ether;
|
||||
|
||||
// Remind that we have locked in Governance 25 000 TORN for TEST_ADDRESS_ONE while voting
|
||||
uint256 stakerLockedBalance = governance.lockedBalance(TEST_ADDRESS_ONE);
|
||||
require(stakerLockedBalance == 25_000 ether, "Invalid test staker locked balance");
|
||||
retrieveAndLockBalance(TEST_STAKER_PRIVATE_KEY, TEST_STAKER_ADDRESS, PROPOSAL_THRESHOLD);
|
||||
uint256 stakerLockedBalance = governance.lockedBalance(TEST_STAKER_ADDRESS);
|
||||
require(stakerLockedBalance == PROPOSAL_THRESHOLD, "Invalid test staker locked balance");
|
||||
|
||||
uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_ADDRESS_ONE);
|
||||
console2.log("Staking rewards before burning: %s TORN", stakerRewardsBeforeBurning / 10e17);
|
||||
uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_STAKER_ADDRESS);
|
||||
console2.log("Staking rewards before burning: %s TORN", stakerRewardsBeforeBurning / _tornDecimals);
|
||||
|
||||
burnTokens(_governanceAddress, toBurn, staking);
|
||||
|
||||
uint256 stakerRewardsAfterBurning = staking.checkReward(TEST_ADDRESS_ONE);
|
||||
console2.log("Staking rewards after burning 10 000 TORN: %s TORN\n", stakerRewardsAfterBurning / 10e17);
|
||||
uint256 stakerRewardsAfterBurning = staking.checkReward(TEST_STAKER_ADDRESS);
|
||||
console2.log(
|
||||
"Staking rewards after burning 10 000 TORN: %s TORN\n", stakerRewardsAfterBurning / _tornDecimals
|
||||
);
|
||||
require(stakerRewardsAfterBurning > stakerRewardsBeforeBurning, "Rewards isn't changed after burning");
|
||||
|
||||
// All TORN, locked by users in Governance, is on the userVault contract balance
|
||||
@@ -57,29 +85,38 @@ contract TestGovernanceStakingRewards is MockProposal {
|
||||
uint256 receivedReward = stakerRewardsAfterBurning - stakerRewardsBeforeBurning;
|
||||
uint256 expectedRewards = stakerLockedBalance * toBurn / governanceLockedAmount;
|
||||
|
||||
console2.log("Expected staking rewards: %s TORN", expectedRewards / 10e17);
|
||||
console2.log("Staker received rewards: %s TORN\n", receivedReward / 10e17);
|
||||
console2.log("Expected staking rewards: %s TORN", expectedRewards / _tornDecimals);
|
||||
console2.log("Staker received rewards: %s TORN\n", receivedReward / _tornDecimals);
|
||||
|
||||
require(receivedReward == expectedRewards, "Expected and received rewards don't match");
|
||||
|
||||
uint256 stakerTORNbalanceBeforeGettingRewards = TORN.balanceOf(TEST_ADDRESS_ONE);
|
||||
console2.log("Staker balance before getting (withdrawal) collected rewards: %s TORN", stakerTORNbalanceBeforeGettingRewards / 10e17);
|
||||
uint256 stakerTORNbalanceBeforeGettingRewards = TORN.balanceOf(TEST_STAKER_ADDRESS);
|
||||
console2.log(
|
||||
"Staker balance before getting (withdrawal) collected rewards: %s TORN",
|
||||
stakerTORNbalanceBeforeGettingRewards / _tornDecimals
|
||||
);
|
||||
|
||||
vm.startPrank(TEST_ADDRESS_ONE);
|
||||
vm.startPrank(TEST_STAKER_ADDRESS);
|
||||
staking.getReward();
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 stakerTORNbalanceAfterGettingRewards = TORN.balanceOf(TEST_ADDRESS_ONE);
|
||||
console2.log("Staker balance after getting (withdrawal) collected rewards: %s TORN", stakerTORNbalanceAfterGettingRewards / 10e17);
|
||||
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,
|
||||
"Rewards isn't withdrawed"
|
||||
);
|
||||
require(
|
||||
stakerTORNbalanceAfterGettingRewards - stakerTORNbalanceBeforeGettingRewards == receivedReward,
|
||||
"Incorrect rewards amount withdrawed"
|
||||
);
|
||||
}
|
||||
|
||||
function testCanBurnFromRelayerRegistry() public executeAttackerProposalBefore executeCurrentProposalBefore {
|
||||
function testCanBurnFromRelayerRegistry() public {
|
||||
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
|
||||
address relayerRegistryAddress = getRelayerRegistryProxyAddress();
|
||||
|
||||
@@ -87,6 +124,9 @@ contract TestGovernanceStakingRewards is MockProposal {
|
||||
|
||||
burnTokens(relayerRegistryAddress, 10_000 ether, staking);
|
||||
|
||||
require(staking.accumulatedRewardPerTorn() > accumulatedRewardPerTornBeforeBurning, "Burn from relayer registry failed");
|
||||
require(
|
||||
staking.accumulatedRewardPerTorn() > accumulatedRewardPerTornBeforeBurning,
|
||||
"Burn from relayer registry failed"
|
||||
);
|
||||
}
|
||||
}
|
||||
44
test/forge/Utils.sol
Normal file
44
test/forge/Utils.sol
Normal 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();
|
||||
/* ----------------------------*/
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@ struct Proposal {
|
||||
interface IGovernance {
|
||||
function proposals(uint256 index) external view returns (Proposal memory);
|
||||
|
||||
function proposalCount() external view returns (uint256);
|
||||
|
||||
function lockedBalance(address account) external view returns (uint256);
|
||||
|
||||
function propose(address target, string memory description) external returns (uint256);
|
||||
Reference in New Issue
Block a user