Compare commits

...

3 Commits

Author SHA1 Message Date
9525b3dae4 Merge pull request 'main' (#1) from AlienTornadosaurusHex/proposal-22-governance-and-rewards-patch:main into main
Reviewed-on: #1
2023-05-27 17:43:26 +02:00
AlienTornadosaurusHex
2aabdc4bbe cleanup
Signed-off-by: AlienTornadosaurusHex <>
2023-05-27 15:29:13 +00:00
AlienTornadosaurusHex
7cc789f48f Merge branch 'main' of https://git.tornado.ws/Theo/proposal-22-governance-and-rewards-patch 2023-05-27 15:28:12 +00:00
26 changed files with 671 additions and 6440 deletions

1
.gitignore vendored

@ -16,3 +16,4 @@ docs/
# yarn
yarn-error*
yarn.lock

@ -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'

@ -6,7 +6,7 @@
"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"
"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/
@proprietary/=test/proprietary/
@interfaces/=test/interfaces/
@root/=src/
@forge-std/=lib/forge-std/src/

@ -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);
}

@ -24,7 +24,10 @@ contract PatchProposalContractsFactory {
* @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) {
function createStakingRewards(address governance, address torn, address registry)
external
returns (address)
{
return address(new TornadoStakingRewards(governance, torn, registry));
}
@ -36,10 +39,13 @@ contract PatchProposalContractsFactory {
* @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)
{
function createRegistryContract(
address torn,
address governance,
address ens,
address staking,
address feeManager
) external returns (address) {
return address(new RelayerRegistry(torn, governance, ens, staking, feeManager));
}
}
@ -51,9 +57,9 @@ contract PatchProposal {
using SafeMath for uint256;
using Address for address;
address public immutable feeManagerAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7;
address public immutable feeManagerProxyAddress = 0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7;
address public immutable registryProxyAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
address public immutable ensAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
address public immutable registry = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
@ -73,31 +79,39 @@ contract PatchProposal {
address vault = address(GovernancePatchUpgrade(governance).userVault());
// Get the old staking contract
TornadoStakingRewards oldStaking = TornadoStakingRewards(address(GovernancePatchUpgrade(governance).Staking()));
TornadoStakingRewards oldStaking =
TornadoStakingRewards(address(GovernancePatchUpgrade(governance).Staking()));
// 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));
TornadoStakingRewards newStakingImplementation = TornadoStakingRewards(
patchProposalContractsFactory.createStakingRewards(
address(governance), address(TORN), registryProxyAddress
)
);
// Create new staking proxy contract (without initialization value)
bytes memory empty;
AdminUpgradeableProxy newStaking = new AdminUpgradeableProxy(address(newStakingImplementation), address(governance), empty);
address newStaking =
address(new AdminUpgradeableProxy(address(newStakingImplementation), address(governance), empty));
// And a new registry implementation
address newRegistryImplementationAddress = patchProposalContractsFactory.createRegistryContract(
address(TORN), address(governance), ensAddress, address(newStaking), feeManagerAddress
address(TORN), address(governance), ensAddress, newStaking, feeManagerProxyAddress
);
// Upgrade the registry proxy
AdminUpgradeableProxy(payable(registry)).upgradeTo(newRegistryImplementationAddress);
AdminUpgradeableProxy(payable(registryProxyAddress)).upgradeTo(newRegistryImplementationAddress);
// Now upgrade the governance to the latest stuff
LoopbackProxy(payable(governance)).upgradeTo(address(new GovernancePatchUpgrade(address(newStaking), gasComp, vault)));
LoopbackProxy(payable(governance)).upgradeTo(
address(new GovernancePatchUpgrade(newStaking, gasComp, vault))
);
// Return TORNs, which were withdrawn by bug, to Governance Staking contract
TORN.transfer(address(newStaking), 94_092 ether);
// Compensate TORN for staking
TORN.transfer(newStaking, 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,40 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
contract Mock {
// Developer address with 22 staked TORN
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 public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
uint256 public constant ATTACKER_PROPOSAL_ID = 21; // Last attacker proposal (to restore Governance Vault balance) id
uint256 public constant PROPOSAL_VOTING_DURATION = 5 days;
uint256 public constant PROPOSAL_LOCKED_DURATION = 2 days;
uint256 public constant PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION;
uint256 public constant PROPOSAL_EXECUTION_MAX_DURATION = 3 days;
uint256 public constant PROPOSAL_THRESHOLD = 25_000 ether;
string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}";
address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant EIP712_DOMAIN = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("TornadoCash")),
keccak256(bytes("1")),
1,
VERIFIER_ADDRESS
)
);
uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
contract Mock {
// Developer address with 22 staked TORN
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 public constant TEST_ADDRESS_ONE = 0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
address public constant TEST_ADDRESS_TWO = 0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
uint256 public constant ATTACKER_PROPOSAL_ID = 21; // Last attacker proposal (to restore Governance Vault balance) id
uint256 public constant PROPOSAL_VOTING_DURATION = 5 days;
uint256 public constant PROPOSAL_LOCKED_DURATION = 2 days;
uint256 public constant PROPOSAL_DURATION = PROPOSAL_VOTING_DURATION + PROPOSAL_LOCKED_DURATION;
uint256 public constant PROPOSAL_EXECUTION_MAX_DURATION = 3 days;
uint256 public constant PROPOSAL_THRESHOLD = 25_000 ether;
string public constant PROPOSAL_DESCRIPTION = "{title:'Some proposal',description:''}";
address public constant VERIFIER_ADDRESS = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant EIP712_DOMAIN = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("TornadoCash")),
keccak256(bytes("1")),
1,
VERIFIER_ADDRESS
)
);
uint16 public constant PERMIT_FUNC_SELECTOR = uint16(0x1901);
}

@ -1,89 +1,91 @@
// 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);
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);
}
}
// 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);
}
}

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

@ -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,

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

@ -1,100 +1,130 @@
// 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 { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
import { RelayerRegistry } from "@root/v4-patch/RelayerRegistry.sol";
import { TornadoStakingRewards } from "@root/v4-patch/TornadoStakingRewards.sol";
contract TestGovernanceStakingRewards is MockProposal {
function burnTokens(address caller, uint256 amount, TornadoStakingRewards staking) internal {
vm.startPrank(caller);
staking.addBurnRewards(amount);
vm.stopPrank();
}
function testAccumulatedRewardCanBeUpdated() public executeAttackerProposalBefore executeCurrentProposalBefore {
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
uint256 accumulatedRewardPerTornBeforeBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
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 / _tornDecimals
);
require(accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning, "Staking rewards isn't updated");
}
function testStakerCanGetRewards() public executeAttackerProposalBefore executeCurrentProposalBefore {
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");
uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_ADDRESS_ONE);
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 / _tornDecimals);
require(stakerRewardsAfterBurning > stakerRewardsBeforeBurning, "Rewards isn't changed after burning");
// All TORN, locked by users in Governance, is on the userVault contract balance
uint256 governanceLockedAmount = TORN.balanceOf(address(governance.userVault()));
uint256 receivedReward = stakerRewardsAfterBurning - stakerRewardsBeforeBurning;
uint256 expectedRewards = stakerLockedBalance * toBurn / governanceLockedAmount;
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 / _tornDecimals
);
vm.startPrank(TEST_ADDRESS_ONE);
staking.getReward();
vm.stopPrank();
uint256 stakerTORNbalanceAfterGettingRewards = TORN.balanceOf(TEST_ADDRESS_ONE);
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 executeAttackerProposalBefore executeCurrentProposalBefore {
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");
}
}
// 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 { GovernancePatchUpgrade } from "@root/v4-patch/GovernancePatchUpgrade.sol";
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
{
TornadoStakingRewards staking = TornadoStakingRewards(getStakingProxyAddress());
uint256 accumulatedRewardPerTornBeforeBurning =
staking.accumulatedRewardPerTorn() / _tornMaximumSupply;
console2.log(
"Accumulated reward per TORN right after proposal execution: %s TORN",
accumulatedRewardPerTornBeforeBurning / 10e17
);
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
);
require(
accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning,
"Staking rewards isn't updated"
);
}
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");
uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_ADDRESS_ONE);
console2.log("Staking rewards before burning: %s TORN", stakerRewardsBeforeBurning / 10e17);
burnTokens(_governanceAddress, toBurn, staking);
uint256 stakerRewardsAfterBurning = staking.checkReward(TEST_ADDRESS_ONE);
console2.log("Staking rewards after burning 10 000 TORN: %s TORN", stakerRewardsAfterBurning / 10e17);
require(stakerRewardsAfterBurning > stakerRewardsBeforeBurning, "Rewards isn't changed after burning");
// All TORN, locked by users in Governance, is on the userVault contract balance
uint256 governanceLockedAmount = TORN.balanceOf(address(governance.userVault()));
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", receivedReward / 10e17);
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
);
vm.startPrank(TEST_ADDRESS_ONE);
staking.getReward();
vm.stopPrank();
uint256 stakerTORNbalanceAfterGettingRewards = TORN.balanceOf(TEST_ADDRESS_ONE);
console2.log(
"Staker balance after getting (withdrawal) collected rewards: %s TORN",
stakerTORNbalanceAfterGettingRewards / 10e17
);
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"
);
}
}

@ -7,7 +7,5 @@ contract Parameters {
address constant _governanceAddress = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
address constant _governanceVaultAddress = 0x2F50508a8a3D323B91336FA3eA6ae50E55f32185;
address constant _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
uint256 constant _tornMaximumSupply = 10_000_000;
uint256 constant _tornDecimals = 1e18;
}

5893
yarn.lock

File diff suppressed because it is too large Load Diff