Allow people to slash relayers who avoid on newer instances...

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-18 22:35:13 +00:00
parent dd4de78165
commit 08a62fc18a
11 changed files with 1277 additions and 50 deletions

@ -14,6 +14,8 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local V2 imports
import { RelayerRegistry } from "../v1/RelayerRegistry.sol";
import { FeeOracleManager } from "../v2/FeeOracleManager.sol";
import { InstanceRegistry } from "../v2/InstanceRegistry.sol";
@ -76,7 +78,11 @@ contract InfrastructureUpgradeProposal {
TornadoRouter router = TornadoRouter(deployedTornadoRouterAddress);
router.initialize(instanceRegistryProxyAddress, relayerRegistryProxyAddress);
router.initialize(instanceRegistryProxyAddress, relayerRegistryProxyAddress, feeManagerProxyAddress);
// Also set the Tornado Router in the registry
RelayerRegistry(relayerRegistryProxyAddress).setTornadoRouter(address(router));
// We also now need to upgrade the InstanceRegistry proxy and the FeeManager proxy

@ -0,0 +1,388 @@
// SPDX-License-Identifier: MIT
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 { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { EnsResolve } from "torn-token/contracts/ENS.sol";
import { TORN } from "torn-token/contracts/TORN.sol";
import { TornadoStakingRewards } from "../v1/staking/TornadoStakingRewards.sol";
interface ITornadoInstance {
function token() external view returns (address);
function denomination() external view returns (uint256);
function deposit(bytes32 commitment) external payable;
function withdraw(
bytes calldata proof,
bytes32 root,
bytes32 nullifierHash,
address payable recipient,
address payable relayer,
uint256 fee,
uint256 refund
) external payable;
}
interface IENS {
function owner(bytes32 node) external view returns (address);
}
/*
* @dev Solidity implementation of the ENS namehash algorithm.
*
* Warning! Does not normalize or validate names before hashing.
* Original version can be found here https://github.com/JonahGroendal/ens-namehash/
*/
library ENSNamehash {
function namehash(bytes memory domain) internal pure returns (bytes32) {
return namehash(domain, 0);
}
function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) {
if (domain.length <= i) return 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 len = labelLength(domain, i);
return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len)));
}
function labelLength(bytes memory domain, uint256 i) private pure returns (uint256) {
uint256 len;
while (i + len != domain.length && domain[i + len] != 0x2e) {
len++;
}
return len;
}
function keccak(bytes memory data, uint256 offset, uint256 len) private pure returns (bytes32 ret) {
require(offset + len <= data.length);
assembly {
ret := keccak256(add(add(data, 32), offset), len)
}
}
}
interface IFeeManager {
function instanceFeeWithUpdate(ITornadoInstance _instance) external returns (uint160);
}
struct RelayerState {
uint256 balance;
bytes32 ensHash;
}
/**
* @notice Registry contract, one of the main contracts of this protocol upgrade.
* The contract should store relayers' addresses and data attributed to the
* master address of the relayer. This data includes the relayers stake and
* his ensHash.
* A relayers master address has a number of subaddresses called "workers",
* these are all addresses which burn stake in communication with the proxy.
* If a relayer is not registered, he is not displayed on the frontend.
* @dev CONTRACT RISKS:
* - if setter functions are compromised, relayer metadata would be at risk, including the noted amount
* of his balance
* - if burn function is compromised, relayers run the risk of being unable to handle withdrawals
* - the above risk also applies to the nullify balance function
*
*/
contract RelayerRegistry is Initializable, EnsResolve {
using SafeMath for uint256;
using SafeERC20 for TORN;
using ENSNamehash for bytes;
TORN public immutable torn;
address public immutable governance;
IENS public immutable ens;
TornadoStakingRewards public immutable staking;
IFeeManager public immutable feeManager;
address public tornadoRouter;
uint256 public minStakeAmount;
mapping(address => RelayerState) public relayers;
mapping(address => address) public workers;
event RelayerBalanceNullified(address relayer);
event WorkerRegistered(address relayer, address worker);
event WorkerUnregistered(address relayer, address worker);
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
event StakeBurned(address relayer, uint256 amountBurned);
event MinimumStakeAmount(uint256 minStakeAmount);
event RouterRegistered(address tornadoRouter);
event RelayerRegistered(bytes32 relayer, string ensName, address relayerAddress, uint256 stakedAmount);
modifier onlyGovernance() {
require(msg.sender == governance, "only governance");
_;
}
modifier onlyTornadoRouter() {
require(msg.sender == tornadoRouter, "only proxy");
_;
}
modifier onlyRelayer(address sender, address relayer) {
require(workers[sender] == relayer, "only relayer");
_;
}
constructor(address _torn, address _governance, address _ens, address _staking, address _feeManager)
public
{
torn = TORN(_torn);
governance = _governance;
ens = IENS(_ens);
staking = TornadoStakingRewards(_staking);
feeManager = IFeeManager(_feeManager);
}
/**
* @notice initialize function for upgradeability
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
* params left out because self explainable
*
*/
function initialize(bytes32 _tornadoRouter) external initializer {
tornadoRouter = resolve(_tornadoRouter);
}
/**
* @notice This function should register a master address and optionally a set of workeres for a relayer +
* metadata
* @dev Relayer can't steal other relayers workers since they are registered, and a wallet (msg.sender
* check) can always unregister itself
* @param ensName ens name of the relayer
* @param stake the initial amount of stake in TORN the relayer is depositing
*
*/
function register(string calldata ensName, uint256 stake, address[] calldata workersToRegister)
external
{
_register(msg.sender, ensName, stake, workersToRegister);
}
/**
* @dev Register function equivalent with permit-approval instead of regular approve.
*
*/
function registerPermit(
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister,
address relayer,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(relayer, address(this), stake, deadline, v, r, s);
_register(relayer, ensName, stake, workersToRegister);
}
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");
RelayerState storage metadata = relayers[relayer];
require(metadata.ensHash == bytes32(0), "registered already");
require(stake >= minStakeAmount, "!min_stake");
torn.safeTransferFrom(relayer, address(staking), stake);
emit StakeAddedToRelayer(relayer, stake);
metadata.balance = stake;
metadata.ensHash = ensHash;
workers[relayer] = relayer;
for (uint256 i = 0; i < workersToRegister.length; i++) {
address worker = workersToRegister[i];
_registerWorker(relayer, worker);
}
emit RelayerRegistered(ensHash, ensName, relayer, stake);
}
/**
* @notice This function should allow relayers to register more workeres
* @param relayer Relayer which should send message from any worker which is already registered
* @param worker Address to register
*
*/
function registerWorker(address relayer, address worker) external onlyRelayer(msg.sender, relayer) {
_registerWorker(relayer, worker);
}
function _registerWorker(address relayer, address worker) internal {
require(workers[worker] == address(0), "can't steal an address");
workers[worker] = relayer;
emit WorkerRegistered(relayer, worker);
}
/**
* @notice This function should allow anybody to unregister an address they own
* @dev designed this way as to allow someone to unregister themselves in case a relayer misbehaves
* - this should be followed by an action like burning relayer stake
* - there was an option of allowing the sender to burn relayer stake in case of malicious behaviour,
* this feature was not included in the end
* - reverts if trying to unregister master, otherwise contract would break. in general, there should
* be no reason to unregister master at all
*
*/
function unregisterWorker(address worker) external {
if (worker != msg.sender) require(workers[worker] == msg.sender, "only owner of worker");
require(workers[worker] != worker, "cant unregister master");
emit WorkerUnregistered(workers[worker], worker);
workers[worker] = address(0);
}
/**
* @notice This function should allow anybody to stake to a relayer more TORN
* @param relayer Relayer main address to stake to
* @param stake Stake to be added to relayer
*
*/
function stakeToRelayer(address relayer, uint256 stake) external {
_stakeToRelayer(msg.sender, relayer, stake);
}
/**
* @dev stakeToRelayer function equivalent with permit-approval instead of regular approve.
* @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 {
torn.permit(staker, address(this), stake, deadline, v, r, s);
_stakeToRelayer(staker, relayer, stake);
}
function _stakeToRelayer(address staker, address relayer, uint256 stake) internal {
require(workers[relayer] == relayer, "!registered");
torn.safeTransferFrom(staker, address(staking), stake);
relayers[relayer].balance = stake.add(relayers[relayer].balance);
emit StakeAddedToRelayer(relayer, stake);
}
/**
* @notice This function should burn some relayer stake on withdraw and notify staking of this
* @dev IMPORTANT FUNCTION:
* - This should be only called by the tornado proxy
* - Should revert if relayer does not call proxy from valid worker
* - Should not overflow
* - Should underflow and revert (SafeMath) on not enough stake (balance)
* @param sender worker to check sender == relayer
* @param relayer address of relayer who's stake is being burned
* @param pool instance to get fee for
*
*/
function burn(address sender, address relayer, ITornadoInstance pool) external onlyTornadoRouter {
address masterAddress = workers[sender];
if (masterAddress == address(0)) {
require(workers[relayer] == address(0), "Only custom relayer");
return;
}
require(masterAddress == relayer, "only relayer");
uint256 toBurn = feeManager.instanceFeeWithUpdate(pool);
relayers[relayer].balance = relayers[relayer].balance.sub(toBurn);
staking.addBurnRewards(toBurn);
emit StakeBurned(relayer, toBurn);
}
/**
* @notice This function should allow governance to set the minimum stake amount
* @param minAmount new minimum stake amount
*
*/
function setMinStakeAmount(uint256 minAmount) external onlyGovernance {
minStakeAmount = minAmount;
emit MinimumStakeAmount(minAmount);
}
/**
* @notice This function should allow governance to set a new tornado proxy address
* @param tornadoRouterAddress address of the new proxy
*
*/
function setTornadoRouter(address tornadoRouterAddress) external onlyGovernance {
tornadoRouter = tornadoRouterAddress;
emit RouterRegistered(tornadoRouterAddress);
}
/**
* @notice This function should allow governance to nullify a relayers balance
* @dev IMPORTANT FUNCTION:
* - Should nullify the balance
* - Adding nullified balance as rewards was refactored to allow for the flexibility of these funds
* (for gov to operate with them)
* @param relayer address of relayer who's balance is to nullify
*
*/
function nullifyBalance(address relayer) external onlyGovernance {
address masterAddress = workers[relayer];
require(relayer == masterAddress, "must be master");
relayers[masterAddress].balance = 0;
emit RelayerBalanceNullified(relayer);
}
/**
* @notice This function should check if a worker is associated with a relayer
* @param toResolve address to check
* @return true if is associated
*
*/
function isRelayer(address toResolve) external view returns (bool) {
return workers[toResolve] != address(0);
}
/**
* @notice This function should check if a worker is registered to the relayer stated
* @param relayer relayer to check
* @param toResolve address to check
* @return true if registered
*
*/
function isRelayerRegistered(address relayer, address toResolve) external view returns (bool) {
return workers[toResolve] == relayer;
}
/**
* @notice This function should get a relayers ensHash
* @param relayer address to fetch for
* @return relayer's ensHash
*
*/
function getRelayerEnsHash(address relayer) external view returns (bytes32) {
return relayers[workers[relayer]].ensHash;
}
/**
* @notice This function should get a relayers balance
* @param relayer relayer who's balance is to fetch
* @return relayer's balance
*
*/
function getRelayerBalance(address relayer) external view returns (uint256) {
return relayers[workers[relayer]].balance;
}
}

@ -0,0 +1,155 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
import { EnsResolve } from "torn-token/contracts/ENS.sol";
interface ITornadoVault {
function withdrawTorn(address recipient, uint256 amount) external;
}
interface ITornadoGovernance {
function lockedBalance(address account) external view returns (uint256);
function userVault() external view returns (ITornadoVault);
}
/**
* @notice This is the staking contract of the governance staking upgrade.
* This contract should hold the staked funds which are received upon relayer registration,
* and properly attribute rewards to addresses without security issues.
* @dev CONTRACT RISKS:
* - Relayer staked TORN at risk if contract is compromised.
*
*/
contract TornadoStakingRewards is Initializable, EnsResolve {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/// @notice 1e25
uint256 public immutable ratioConstant;
ITornadoGovernance public immutable Governance;
IERC20 public immutable torn;
address public immutable relayerRegistry;
/// @notice the sum torn_burned_i/locked_amount_i*coefficient where i is incremented at each burn
uint256 public accumulatedRewardPerTorn;
/// @notice notes down accumulatedRewardPerTorn for an address on a lock/unlock/claim
mapping(address => uint256) public accumulatedRewardRateOnLastUpdate;
/// @notice notes down how much an account may claim
mapping(address => uint256) public accumulatedRewards;
event RewardsUpdated(address indexed account, uint256 rewards);
event RewardsClaimed(address indexed account, uint256 rewardsClaimed);
modifier onlyGovernance() {
require(msg.sender == address(Governance), "only governance");
_;
}
// Minor code change here we won't resolve the registry by ENS
constructor(address governanceAddress, address tornAddress, address _relayerRegistry) public {
Governance = ITornadoGovernance(governanceAddress);
torn = IERC20(tornAddress);
relayerRegistry = _relayerRegistry;
ratioConstant = IERC20(tornAddress).totalSupply();
}
/**
* @notice This function should safely send a user his rewards.
* @dev IMPORTANT FUNCTION:
* We know that rewards are going to be updated every time someone locks or unlocks
* so we know that this function can't be used to falsely increase the amount of
* lockedTorn by locking in governance and subsequently calling it.
* - set rewards to 0 greedily
*/
function getReward() external {
uint256 rewards = _updateReward(msg.sender, Governance.lockedBalance(msg.sender));
rewards = rewards.add(accumulatedRewards[msg.sender]);
accumulatedRewards[msg.sender] = 0;
torn.safeTransfer(msg.sender, rewards);
emit RewardsClaimed(msg.sender, rewards);
}
/**
* @notice This function should increment the proper amount of rewards per torn for the contract
* @dev IMPORTANT FUNCTION:
* - calculation must not overflow with extreme values
* (amount <= 1e25) * 1e25 / (balance of vault <= 1e25) -> (extreme values)
* @param amount amount to add to the rewards
*/
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())))
);
}
/**
* @notice This function should allow governance to properly update the accumulated rewards rate for an
* account
* @param account address of account to update data for
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
*
*/
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand)
external
onlyGovernance
{
uint256 claimed = _updateReward(account, amountLockedBeforehand);
accumulatedRewards[account] = accumulatedRewards[account].add(claimed);
}
/**
* @notice This function should allow governance rescue tokens from the staking rewards contract
*
*/
function withdrawTorn(uint256 amount) external onlyGovernance {
if (amount == type(uint256).max) amount = torn.balanceOf(address(this));
torn.safeTransfer(address(Governance), amount);
}
/**
* @notice This function should calculated the proper amount of rewards attributed to user since the last
* update
* @dev IMPORTANT FUNCTION:
* - calculation must not overflow with extreme values
* (accumulatedReward <= 1e25) * (lockedBeforehand <= 1e25) / 1e25
* - result may go to 0, since this implies on 1 TORN locked => accumulatedReward <= 1e7, meaning a
* very small reward
* @param account address of account to calculate rewards for
* @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)
{
if (amountLockedBeforehand != 0) {
claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
amountLockedBeforehand
).div(ratioConstant);
}
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
emit RewardsUpdated(account, claimed);
}
/**
* @notice This function should show a user his rewards.
* @param account address of account to calculate rewards for
*/
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 = rewards.add(accumulatedRewards[account]);
}
}

@ -122,6 +122,10 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
_;
}
function version() public pure virtual returns (string memory) {
return "v2-slashing-and-oracles";
}
/**
* @dev If there will be a need to initialize the proxy again, simply pad storage and inherit again,
* making sure to not reference old data anywhere.
@ -203,11 +207,11 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
oracle.update(torn, _feeInstance);
// There must a be a fee set otherwise it's just 0
uint160 newFee = fee.percent != 0 ? oracle.getFee(torn, _feeInstance) : 0;
fee.amount = fee.percent != 0 ? oracle.getFee(torn, _feeInstance) : 0;
// Store
feesByInstance[_instance] = FeeData({
amount: newFee,
amount: fee.amount,
percent: fee.percent,
updateInterval: fee.updateInterval,
lastUpdateTime: uint32(now)
@ -290,10 +294,6 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function version() public pure virtual returns (string memory) {
return "v2-oracle-manager";
}
function getUpdatedFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
return instanceFeeOracles[instance].getFee(torn, populateInstanceWithFeeData(instance));
}

@ -129,6 +129,10 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
_;
}
function version() public pure virtual returns (string memory) {
return "v2-slashing-and-oracles";
}
function initialize(ITornadoInstance[] memory _instances, TornadoRouter _router)
external
onlyGovernance
@ -259,10 +263,6 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function version() public pure virtual returns (string memory) {
return "v2-oracle-manager";
}
function getAllInstances() public view virtual returns (ITornadoInstance[] memory allInstances) {
return getInstances(0, instances.length - 1);
}

383
src/v2/RelayerRegistry.sol Normal file

@ -0,0 +1,383 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ imports
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
// Tornado imports
import { TORN } from "torn-token/contracts/TORN.sol";
import { EnsResolve } from "torn-token/contracts/ENS.sol";
// Local imports
import { IENS } from "./interfaces/IENS.sol";
import { ENSNamehash } from "./libraries/ENSNamehash.sol";
import { TornadoStakingRewards } from "./TornadoStakingRewards.sol";
import { FeeOracleManager } from "./FeeOracleManager.sol";
contract RegistryLegacyStorage {
/**
* @dev From Initializable.sol of first contract
*/
bool private _deprecatedInitialized;
/**
* @dev From Initializable.sol of first contract
*/
bool private _deprecatedInitializing;
/**
* @dev This one will be moved for visibility
*/
address internal _oldRouterAddress;
/**
* @dev We are not using this one because we will pull some magic
*/
uint256 internal _oldMinStakeAmount;
}
interface MinimumStakingAmountOracle {
function calculateMinimumStakingAmount() external view returns (uint256);
}
struct RelayerMetadata {
uint256 balance;
bytes32 node;
}
contract RelayerRegistry is RegistryLegacyStorage, EnsResolve, Initializable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LEGACY STORAGE, IN USE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/**
* @notice Relayer metadata: their (non-refundable) TORN balances and ENS node
*/
mapping(address => RelayerMetadata) public metadata;
/**
* @notice Subaddresses which may work for a given relayer
*/
mapping(address => address) public workers;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ NEW STORAGE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/**
* @notice Basis points are 1/100th of a %
*/
uint256 public constant BIP_DIVISOR = 10_000;
/**
* @notice The address of the Governance proxy.
*/
address public immutable governanceProxyAddress;
/**
* @notice The TORN token.
*/
TORN public immutable torn;
/**
* @notice The ENS Registry.
*/
IENS public ens;
/**
* @notice The Fee Oracle Manager contract.
*/
FeeOracleManager public feeOracleManager;
/**
* @notice The Staking Rewards contract.
*/
TornadoStakingRewards public stakingRewards;
/**
* @notice The address from which slashes may come from.
*/
address public routerAddress;
/**
* @notice Since registrations are maybe relatively frequent and this boils down to a view getter, we can
* use a bytes20 to either read out a min stake amount as a number or to fetch it from an address.
*/
bytes20 public minStakeAmountOracle;
/**
* @notice What the kickback to stakers is on a slash
*/
uint256 public stakerKickbackBips;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event ENSUpdated(address ens);
event RouterAddressUpdated(address routerAddress);
event StakingRewardsUpdated(address stakingRewards);
event FeeOracleManagerUpdated(address feeOracleManagerAddress);
event MinimumStakingAmountOracleUpdated(bytes20 oracle, bool isContract);
event WorkerRegistered(address relayer, address worker);
event WorkerUnregistered(address relayer, address worker);
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
event StakeBurned(address relayer, uint256 amountBurned);
event RelayerRegistered(string ensName, bytes32 relayer, address relayerAddress, uint256 stakedAmount);
event RelayerSlashed(
address indexed relayer, address beneficiary, uint256 slashedAmount, uint256 stakerKickbackBips
);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(address _governanceProxyAddress, address _tornTokenAddress) public {
governanceProxyAddress = _governanceProxyAddress;
torn = TORN(_tornTokenAddress);
}
modifier onlyGovernance() {
require(msg.sender == governanceProxyAddress, "RelayerRegistry: onlyGovernance");
_;
}
modifier onlyRouter() {
require(msg.sender == routerAddress, "RelayerRegistry: onlyRouter");
_;
}
modifier onlyRelayer(address _possibleRelayer, address _possibleWorker) {
require(isRegisteredRelayer(_possibleRelayer, _possibleWorker), "RelayerRegistry: onlyRelayer");
_;
}
modifier onlyENSOwner(address _relayer, bytes32 _node) {
require(_relayer == ens.owner(_node), "RelayerRegistry: onlyENSOwner");
_;
}
function version() public pure virtual returns (string memory) {
return "v2-slashing-and-oracles";
}
function initialize(
address _ensAddress,
address _feeOracleManagerAddress,
address _stakingRewardsAddress,
bytes20 _minStakeAmountOracle,
uint256 _stakerKickbackBips
) public virtual initializer {
feeOracleManager = FeeOracleManager(_feeOracleManagerAddress);
ens = IENS(_ensAddress);
stakingRewards = TornadoStakingRewards(_stakingRewardsAddress);
minStakeAmountOracle = _minStakeAmountOracle;
stakerKickbackBips = _stakerKickbackBips;
}
function register(string calldata _name, uint256 _staked, address[] calldata _workers) external {
_register(_name, ENSNamehash.namehash(bytes(_name)), msg.sender, _staked, _workers);
}
function registerPermit(
string calldata _name,
uint256 _staked,
address[] calldata _workers,
address _relayer,
uint256 _deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(_relayer, address(this), _staked, _deadline, v, r, s);
_register(_name, ENSNamehash.namehash(bytes(_name)), _relayer, _staked, _workers);
}
function _register(
string memory _name,
bytes32 _node,
address _relayer,
uint256 _staked,
address[] calldata _workers
) internal onlyENSOwner(_relayer, _node) {
bytes32 currentNodeValue = metadata[_relayer].node;
uint256 minStakeAmount = getMinimumStakingAmount();
require(minStakeAmount <= _staked, "RelayerRegistry: stake too low");
require(
workers[_relayer] == address(0) && currentNodeValue == bytes32(0),
"RelayerRegistry: already registered"
);
IERC20(torn).safeTransferFrom(_relayer, address(stakingRewards), _staked);
metadata[_relayer] = RelayerMetadata({ balance: _staked, node: _node });
workers[_relayer] = _relayer;
for (uint256 i = 0; i < _workers.length; i++) {
_registerWorker(_relayer, _workers[i]);
}
emit RelayerRegistered(_name, _node, _relayer, _staked);
}
function registerWorker(address _relayer, address _worker) external onlyRelayer(_relayer, msg.sender) {
_registerWorker(_relayer, _worker);
}
function _registerWorker(address _relayer, address _worker) internal {
require(workers[_worker] == address(0), "RelayerRegistry: already registered");
workers[_worker] = _relayer;
emit WorkerRegistered(_relayer, _worker);
}
function unregisterWorker(address _worker) external {
// We are doing this to not allow a relayer to unregister a worker and then slash themselves
require(workers[_worker] == msg.sender, "RelayerRegistry: sender must own worker");
require(workers[_worker] != _worker, "RelayerRegistry: cant remove owner");
workers[_worker] = address(0);
emit WorkerUnregistered(workers[_worker], _worker);
}
function stakeToRelayer(address _relayer, uint256 _staked) external {
_addStake(msg.sender, _relayer, _staked);
}
function stakeToRelayerPermit(
address _relayer,
uint256 _staked,
address _staker,
uint256 _deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(_staker, address(this), _staked, _deadline, v, r, s);
_addStake(_staker, _relayer, _staked);
}
function _addStake(address _staker, address _relayer, uint256 _staked)
internal
onlyRelayer(_relayer, _relayer)
{
IERC20(torn).safeTransferFrom(_staker, address(stakingRewards), _staked);
metadata[_relayer].balance = _staked.add(metadata[_relayer].balance);
emit StakeAddedToRelayer(_relayer, _staked);
}
function deductBalance(address _sender, address _relayer, uint256 _burned)
public
virtual
onlyRouter
onlyRelayer(_relayer, _sender)
{
metadata[_relayer].balance = metadata[_relayer].balance.sub(_burned);
stakingRewards.addBurnRewards(_burned);
emit StakeBurned(_relayer, _burned);
}
function slashRelayer(address _beneficiary, address _relayer) public virtual onlyRouter {
// Store to reward
uint256 balToSlash = metadata[_relayer].balance;
// Slash
metadata[_relayer].balance = 0;
// We can't give the slasher the full balance because in that case the relayer will just slash himself
stakingRewards.rewardSlasher(
_beneficiary, (balToSlash * (BIP_DIVISOR - stakerKickbackBips)) / BIP_DIVISOR
);
// Give rest to Gov stakers
stakingRewards.addBurnRewards(stakerKickbackBips / BIP_DIVISOR);
emit RelayerSlashed(_relayer, _beneficiary, balToSlash, stakerKickbackBips);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setENS(address _newENSAddress) public virtual onlyGovernance {
ens = IENS(_newENSAddress);
emit ENSUpdated(_newENSAddress);
}
function setRouterAddress(address _newRouterAddress) public virtual onlyGovernance {
routerAddress = _newRouterAddress;
emit RouterAddressUpdated(_newRouterAddress);
}
function setStakingRewards(address _newStakingRewardsAddress) public virtual onlyGovernance {
stakingRewards = TornadoStakingRewards(_newStakingRewardsAddress);
emit StakingRewardsUpdated(_newStakingRewardsAddress);
}
function setFeeOracleManager(address _newFeeOracleManagerAddress) public virtual onlyGovernance {
feeOracleManager = FeeOracleManager(_newFeeOracleManagerAddress);
emit FeeOracleManagerUpdated(_newFeeOracleManagerAddress);
}
function setMinimumStakingAmountOracle(bytes20 _newOracle, bool _isContractOracle)
public
virtual
onlyGovernance
{
bool oracleIsContract = Address.isContract(address(_newOracle));
if (_isContractOracle) {
require(oracleIsContract, "RelayerRegistry: oracle is not contract");
} else {
require(!oracleIsContract, "RelayerRegistry: oracle is contract");
}
minStakeAmountOracle = _newOracle;
emit MinimumStakingAmountOracleUpdated(_newOracle, oracleIsContract);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function isWorker(address _possibleWorker) public view virtual returns (bool) {
return workers[_possibleWorker] != address(0);
}
function isRegisteredRelayer(address _possibleRelayer, address _possibleWorker)
public
view
virtual
returns (bool)
{
return _possibleRelayer == workers[_possibleWorker];
}
function getRelayerENSNode(address _relayer) public view virtual returns (bytes32) {
return metadata[workers[_relayer]].node;
}
function getRelayerBalance(address _relayer) public view virtual returns (uint256) {
return metadata[workers[_relayer]].balance;
}
function getOwningRelayerOfWorker(address _worker) public view virtual returns (address) {
return workers[_worker];
}
function getMinimumStakingAmount() public view virtual returns (uint256) {
bytes20 _minStakeAmountOracle = minStakeAmountOracle;
if (Address.isContract(address(_minStakeAmountOracle))) {
return MinimumStakingAmountOracle(address(_minStakeAmountOracle)).calculateMinimumStakingAmount();
}
return uint160(_minStakeAmountOracle);
}
}

@ -8,6 +8,7 @@ pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
// Tornado Imports
@ -16,13 +17,14 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports
import { RelayerRegistry } from "../v1/RelayerRegistry.sol";
import { RelayerRegistry } from "./RelayerRegistry.sol";
import { InstanceRegistry, InstanceState } from "./InstanceRegistry.sol";
import { FeeOracleManager } from "./FeeOracleManager.sol";
/**
* @title TornadoRouter
* @author AlienTornadosaurusHex
* @notice This contracts is a router for all Tornado Cash deposits and withdrawals
* @notice This contract is a router for all Tornado Cash deposits and withdrawals
* @dev This is an improved version of the TornadoRouter with a modified design from the original contract.
*/
contract TornadoRouter is Initializable {
@ -36,22 +38,37 @@ contract TornadoRouter is Initializable {
address public immutable governanceProxyAddress;
/**
* @notice The instance registry
* @notice The Instance Registry
*/
InstanceRegistry public instanceRegistry;
/**
* @notice The relayer registry
* @notice The Relayer Registry
*/
RelayerRegistry public relayerRegistry;
/**
* @notice The Fee Oracle Manager
*/
FeeOracleManager public feeOracleManager;
/**
* @notice Note down proofs for slashing relayers
*/
mapping(bytes32 => bool) public slashProofs;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event EncryptedNote(address indexed sender, bytes encryptedNote);
event TokenApproved(address indexed spender, uint256 amount);
event WithdrawalWithRelayer(
address sender, address relayer, address instanceAddress, bytes32 nullifierHash
);
event InstanceRegistryUpdated(address newInstanceRegistryProxyAddress);
event RelayerRegistryUpdated(address newRelayerRegistryProxyAddress);
event FeeOracleManagerUpdated(address newFeeOracleManagerProxyAddress);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -69,17 +86,23 @@ contract TornadoRouter is Initializable {
_;
}
function initialize(address _instanceRegistryAddress, address _relayerRegistryAddress)
external
onlyGovernance
initializer
{
function version() public pure virtual returns (string memory) {
return "v2-slashing-and-oracles";
}
function initialize(
address _instanceRegistryAddress,
address _relayerRegistryAddress,
address _feeOracleManager
) external onlyGovernance initializer {
instanceRegistry = InstanceRegistry(_instanceRegistryAddress);
relayerRegistry = RelayerRegistry(_relayerRegistryAddress);
feeOracleManager = FeeOracleManager(_feeOracleManager);
}
/**
* @notice Function to deposit into a Tornado instance.
* @notice Function to deposit into a Tornado instance. We don't really case if an external contract
* breaks for deposit since deposits can go through the instances too if need be.
* @param _tornado The instance to deposit into (address).
* @param _commitment The commitment which will be added to the Merkle Tree.
* @param _encryptedNote An encrypted note tied to the commitment which may be logged.
@ -104,17 +127,83 @@ contract TornadoRouter is Initializable {
}
/**
* @notice Withdraw from a Tornado Instance. NOTE: Until infrastructure, which will include a
* smart-contract relayer wallet, isn't deployed, this function will be the only gateway for withdrawing
* from the _new_ instances. But fear not! Each instance contains a permissionless function by the name of
* `checkInfrastructureIsDead()`, which can be used in the case that the infra is deactivated or
* malfunctions (this contract can't malfunction to block withdrawals). It makes the instance work just
* like a regular one again! The intention is to call the formerly mentioned function when the new
* infrastructure is deployed, and until then, use the simple fix in the new instances to route
* withdrawals
* through the registry. ALSO NOTE, that manual depositors and withdrawers will still be able to
* permissionlessly use the instances, they anyways do not rely on the `relayer` address field, and as
* such they will be able to claim their deposits no matter whether the check is effective or not!
* @notice Withdrawal function for Governance relayers by which they can safely process a withdrawal
* without getting slashed (which kicks them off the frontend).
* @param _tornado The Tornado instance to withdraw from.
* @param _proof Bytes proof data.
* @param _root A current or historical bytes32 root of the Merkle Tree within the proofs context.
* @param _nullifierHash The bytes32 nullifierHash for the deposit.
* @param _recipient The address of recipient for withdrawn funds.
* @param _relayer The address of the relayer which will be making the withdrawal.
* @param _fee The fee in bips to pay the relayer.
* @param _refund If swapping into ETH on the other side, use this to specify how much should be paid for
*/
function processWithdrawAsGovernanceRelayer(
ITornadoInstance _tornado,
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee,
uint256 _refund
) public payable virtual {
// Deduct balance immediately
relayerRegistry.deductBalance(msg.sender, _relayer, feeOracleManager.updateFee(_tornado, true));
// Then call the regular withdraw function which will make the relayer slashable
withdraw(_tornado, _proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund);
// But then undo this and get gas refund
slashProofs[keccak256(abi.encode(msg.sender, _tornado, _relayer, _nullifierHash))] = false;
}
/**
* @notice Slash a relayer by proving that there is a nullifier hash for which he has not been deducted,
* get tokens back for it.
* @dev Events are emitted when withdrawals are processed, that is how you can find hashes.
* @param _relayer The relayer to be slashed
* @param _instanceAddress The instance for which nullifier hash is valid for
* @param _nullifierHash The nullifier hash
*/
function slashRelayer(address _sender, address _relayer, address _instanceAddress, bytes32 _nullifierHash)
public
virtual
{
// Construct the proof, msg.sender can't lie about _sender, because proof will be invalid
// And _sender was withdraw msg.sender
bytes32 possibleSlashProof =
keccak256(abi.encode(_sender, _relayer, _instanceAddress, _nullifierHash));
// Proof must be valid
require(slashProofs[possibleSlashProof], "TornadoRouter: invalid proof");
// Since the proof is valid, we know the _sender is correct
// First step against multicall
require(msg.sender != _sender, "TornadoRouter: slasher is withdrawer");
// Also require the sender is not a contract, multicall could proxy over one
require(!Address.isContract(msg.sender), "TornadoRouter: sender is contract");
// Further it doesn't make sense then to really assert it's not a worker,
// since relayer could use a different account.
//
// In any case searchers and the balance not being returned in full should prevent relayers from not
// behaving well, if they are listed on the frontend
//
// Otherwise they are on their own
// Slash relayer first, it also logs the slash event
relayerRegistry.slashRelayer(msg.sender, _relayer);
// Then set this back false
slashProofs[possibleSlashProof] = false;
}
/**
* @notice Withdraw from a Tornado Instance. Note that the withdraw function is totally independent of any
* Governance contracts, that means, even if the instances demand that only Router is the caller, and the
* Governance contracts get fucked, depositors won't lose their money AND third-party relayers will still
* have the opportunity to operate. Relayers not affiliated with Governance, sorry for that one extra
* SSTORE and emit!
* @param _tornado The Tornado instance to withdraw from.
* @param _proof Bytes proof data.
* @param _root A current or historical bytes32 root of the Merkle Tree within the proofs context.
@ -135,18 +224,9 @@ contract TornadoRouter is Initializable {
uint256 _fee,
uint256 _refund
) public payable virtual {
// If an instance is turned off, we still want withdrawals to pass
// so instead of requiring `isEnabled`, we will only use it to
// decide whether relayers should burn or not. This is beneficial
// for every party involved!
(,,, bool isEnabled) = instanceRegistry.instanceData(_tornado);
// Remove the require and instead just skip if there is no
// relayer being used or the instance is not enabled. Note that,
// even if the registry gets hijacked, you can just do
// manual withdrawals (using the SDK, for example).
if (_relayer != address(0) && isEnabled) {
relayerRegistry.burn(msg.sender, _relayer, _tornado);
if (_relayer != address(0)) {
slashProofs[keccak256(abi.encode(msg.sender, _relayer, address(_tornado), _nullifierHash))] = true;
emit WithdrawalWithRelayer(msg.sender, _relayer, address(_tornado), _nullifierHash);
}
_tornado.withdraw{ value: msg.value }(
@ -160,6 +240,9 @@ contract TornadoRouter is Initializable {
}
}
/**
* @notice Note that this contract doesn't leave dust unless someone sends dust to it.
*/
function rescueTokens(IERC20 _token, address payable _to, uint256 _amount)
public
virtual
@ -201,9 +284,8 @@ contract TornadoRouter is Initializable {
emit RelayerRegistryUpdated(_newRelayerRegistryProxyAddress);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function version() public pure virtual returns (string memory) {
return "v2-oracle-manager";
function setFeeOracleManager(address _newFeeOracleManagerProxyAddress) external onlyGovernance {
feeOracleManager = FeeOracleManager(_newFeeOracleManagerProxyAddress);
emit FeeOracleManagerUpdated(_newFeeOracleManagerProxyAddress);
}
}

@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
import { EnsResolve } from "torn-token/contracts/ENS.sol";
interface ITornadoVault {
function withdrawTorn(address recipient, uint256 amount) external;
}
interface ITornadoGovernance {
function lockedBalance(address account) external view returns (uint256);
function userVault() external view returns (ITornadoVault);
}
/**
* @notice This is the staking contract of the governance staking upgrade.
* This contract should hold the staked funds which are received upon relayer registration,
* and properly attribute rewards to addresses without security issues.
* @dev CONTRACT RISKS:
* - Relayer staked TORN at risk if contract is compromised.
*
*/
contract TornadoStakingRewards is Initializable, EnsResolve {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/// @notice 1e25
uint256 public immutable ratioConstant;
ITornadoGovernance public immutable Governance;
IERC20 public immutable torn;
address public immutable relayerRegistry;
/// @notice the sum torn_burned_i/locked_amount_i*coefficient where i is incremented at each burn
uint256 public accumulatedRewardPerTorn;
/// @notice notes down accumulatedRewardPerTorn for an address on a lock/unlock/claim
mapping(address => uint256) public accumulatedRewardRateOnLastUpdate;
/// @notice notes down how much an account may claim
mapping(address => uint256) public accumulatedRewards;
event RewardsUpdated(address indexed account, uint256 rewards);
event RewardsClaimed(address indexed account, uint256 rewardsClaimed);
modifier onlyGovernance() {
require(msg.sender == address(Governance), "only governance");
_;
}
modifier onlyRelayerRegistry() {
require(msg.sender == relayerRegistry, "only relayer registry");
_;
}
// Minor code change here we won't resolve the registry by ENS
constructor(address governanceAddress, address tornAddress, address _relayerRegistry) public {
Governance = ITornadoGovernance(governanceAddress);
torn = IERC20(tornAddress);
relayerRegistry = _relayerRegistry;
ratioConstant = IERC20(tornAddress).totalSupply();
}
/**
* @notice This function should safely send a user his rewards.
* @dev IMPORTANT FUNCTION:
* We know that rewards are going to be updated every time someone locks or unlocks
* so we know that this function can't be used to falsely increase the amount of
* lockedTorn by locking in governance and subsequently calling it.
* - set rewards to 0 greedily
*/
function getReward() external {
uint256 rewards = _updateReward(msg.sender, Governance.lockedBalance(msg.sender));
rewards = rewards.add(accumulatedRewards[msg.sender]);
accumulatedRewards[msg.sender] = 0;
torn.safeTransfer(msg.sender, rewards);
emit RewardsClaimed(msg.sender, rewards);
}
/**
* @notice This function should increment the proper amount of rewards per torn for the contract
* @dev IMPORTANT FUNCTION:
* - calculation must not overflow with extreme values
* (amount <= 1e25) * 1e25 / (balance of vault <= 1e25) -> (extreme values)
* @param amount amount to add to the rewards
*/
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())))
);
}
/**
* @notice This function should allow governance to properly update the accumulated rewards rate for an
* account
* @param account address of account to update data for
* @param amountLockedBeforehand the balance locked beforehand in the governance contract
*
*/
function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand)
external
onlyGovernance
{
uint256 claimed = _updateReward(account, amountLockedBeforehand);
accumulatedRewards[account] = accumulatedRewards[account].add(claimed);
}
/**
* @notice This function should allow governance rescue tokens from the staking rewards contract
*/
function withdrawTorn(uint256 amount) external onlyGovernance {
if (amount == type(uint256).max) amount = torn.balanceOf(address(this));
torn.safeTransfer(address(Governance), amount);
}
/**
* @notice This function may be used to reward a slasher (since it is registry gated)
*/
function rewardSlasher(address _slasher, uint256 _amount) external onlyRelayerRegistry {
torn.safeTransfer(_slasher, _amount);
}
/**
* @notice This function should calculated the proper amount of rewards attributed to user since the last
* update
* @dev IMPORTANT FUNCTION:
* - calculation must not overflow with extreme values
* (accumulatedReward <= 1e25) * (lockedBeforehand <= 1e25) / 1e25
* - result may go to 0, since this implies on 1 TORN locked => accumulatedReward <= 1e7, meaning a
* very small reward
* @param account address of account to calculate rewards for
* @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)
{
if (amountLockedBeforehand != 0) {
claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul(
amountLockedBeforehand
).div(ratioConstant);
}
accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn;
emit RewardsUpdated(account, claimed);
}
/**
* @notice This function should show a user his rewards.
* @param account address of account to calculate rewards for
*/
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 = rewards.add(accumulatedRewards[account]);
}
}

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
interface IENS {
function owner(bytes32 node) external view returns (address);
}

@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
/*
* @dev Solidity implementation of the ENS namehash algorithm.
*
* Warning! Does not normalize or validate names before hashing.
* Original version can be found here https://github.com/JonahGroendal/ens-namehash/
*/
library ENSNamehash {
function namehash(bytes memory domain) internal pure returns (bytes32) {
return namehash(domain, 0);
}
function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) {
if (domain.length <= i) return 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 len = labelLength(domain, i);
return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len)));
}
function labelLength(bytes memory domain, uint256 i) private pure returns (uint256) {
uint256 len;
while (i + len != domain.length && domain[i + len] != 0x2e) {
len++;
}
return len;
}
function keccak(bytes memory data, uint256 offset, uint256 len) private pure returns (bytes32 ret) {
require(offset + len <= data.length);
assembly {
ret := keccak256(add(add(data, 32), offset), len)
}
}
}

@ -347,10 +347,10 @@ contract ProposalTests is Instances, TornadoProposalTest {
vm.expectRevert();
vm.prank(address(governance));
router.initialize(address(instanceRegistry), address(instanceRegistry));
router.initialize(address(instanceRegistry), address(instanceRegistry), address(feeOracleManager));
vm.expectRevert();
router.initialize(address(instanceRegistry), address(instanceRegistry));
router.initialize(address(instanceRegistry), address(instanceRegistry), address(feeOracleManager));
// Check fee logic and print to console.