Allow people to slash relayers who avoid on newer instances...
Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
parent
dd4de78165
commit
08a62fc18a
@ -14,6 +14,8 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
|
|||||||
|
|
||||||
// Local V2 imports
|
// Local V2 imports
|
||||||
|
|
||||||
|
import { RelayerRegistry } from "../v1/RelayerRegistry.sol";
|
||||||
|
|
||||||
import { FeeOracleManager } from "../v2/FeeOracleManager.sol";
|
import { FeeOracleManager } from "../v2/FeeOracleManager.sol";
|
||||||
|
|
||||||
import { InstanceRegistry } from "../v2/InstanceRegistry.sol";
|
import { InstanceRegistry } from "../v2/InstanceRegistry.sol";
|
||||||
@ -76,7 +78,11 @@ contract InfrastructureUpgradeProposal {
|
|||||||
|
|
||||||
TornadoRouter router = TornadoRouter(deployedTornadoRouterAddress);
|
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
|
// We also now need to upgrade the InstanceRegistry proxy and the FeeManager proxy
|
||||||
|
|
||||||
|
388
src/reference/RelayerRegistry.sol
Normal file
388
src/reference/RelayerRegistry.sol
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
155
src/reference/TornadoStakingRewards.sol
Normal file
155
src/reference/TornadoStakingRewards.sol
Normal file
@ -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,
|
* @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.
|
* making sure to not reference old data anywhere.
|
||||||
@ -203,11 +207,11 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
|
|||||||
oracle.update(torn, _feeInstance);
|
oracle.update(torn, _feeInstance);
|
||||||
|
|
||||||
// There must a be a fee set otherwise it's just 0
|
// 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
|
// Store
|
||||||
feesByInstance[_instance] = FeeData({
|
feesByInstance[_instance] = FeeData({
|
||||||
amount: newFee,
|
amount: fee.amount,
|
||||||
percent: fee.percent,
|
percent: fee.percent,
|
||||||
updateInterval: fee.updateInterval,
|
updateInterval: fee.updateInterval,
|
||||||
lastUpdateTime: uint32(now)
|
lastUpdateTime: uint32(now)
|
||||||
@ -290,10 +294,6 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
|
|||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
function version() public pure virtual returns (string memory) {
|
|
||||||
return "v2-oracle-manager";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUpdatedFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
|
function getUpdatedFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
|
||||||
return instanceFeeOracles[instance].getFee(torn, populateInstanceWithFeeData(instance));
|
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)
|
function initialize(ITornadoInstance[] memory _instances, TornadoRouter _router)
|
||||||
external
|
external
|
||||||
onlyGovernance
|
onlyGovernance
|
||||||
@ -259,10 +263,6 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
|||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
function version() public pure virtual returns (string memory) {
|
|
||||||
return "v2-oracle-manager";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllInstances() public view virtual returns (ITornadoInstance[] memory allInstances) {
|
function getAllInstances() public view virtual returns (ITornadoInstance[] memory allInstances) {
|
||||||
return getInstances(0, instances.length - 1);
|
return getInstances(0, instances.length - 1);
|
||||||
}
|
}
|
||||||
|
383
src/v2/RelayerRegistry.sol
Normal file
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 { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
|
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
|
||||||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.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";
|
import { Math } from "@openzeppelin/contracts/math/Math.sol";
|
||||||
|
|
||||||
// Tornado Imports
|
// Tornado Imports
|
||||||
@ -16,13 +17,14 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
|
|||||||
|
|
||||||
// Local imports
|
// Local imports
|
||||||
|
|
||||||
import { RelayerRegistry } from "../v1/RelayerRegistry.sol";
|
import { RelayerRegistry } from "./RelayerRegistry.sol";
|
||||||
import { InstanceRegistry, InstanceState } from "./InstanceRegistry.sol";
|
import { InstanceRegistry, InstanceState } from "./InstanceRegistry.sol";
|
||||||
|
import { FeeOracleManager } from "./FeeOracleManager.sol";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title TornadoRouter
|
* @title TornadoRouter
|
||||||
* @author AlienTornadosaurusHex
|
* @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.
|
* @dev This is an improved version of the TornadoRouter with a modified design from the original contract.
|
||||||
*/
|
*/
|
||||||
contract TornadoRouter is Initializable {
|
contract TornadoRouter is Initializable {
|
||||||
@ -36,22 +38,37 @@ contract TornadoRouter is Initializable {
|
|||||||
address public immutable governanceProxyAddress;
|
address public immutable governanceProxyAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @notice The instance registry
|
* @notice The Instance Registry
|
||||||
*/
|
*/
|
||||||
InstanceRegistry public instanceRegistry;
|
InstanceRegistry public instanceRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @notice The relayer registry
|
* @notice The Relayer Registry
|
||||||
*/
|
*/
|
||||||
RelayerRegistry public relayerRegistry;
|
RelayerRegistry public relayerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice The Fee Oracle Manager
|
||||||
|
*/
|
||||||
|
FeeOracleManager public feeOracleManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Note down proofs for slashing relayers
|
||||||
|
*/
|
||||||
|
mapping(bytes32 => bool) public slashProofs;
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
event EncryptedNote(address indexed sender, bytes encryptedNote);
|
event EncryptedNote(address indexed sender, bytes encryptedNote);
|
||||||
event TokenApproved(address indexed spender, uint256 amount);
|
event TokenApproved(address indexed spender, uint256 amount);
|
||||||
|
|
||||||
|
event WithdrawalWithRelayer(
|
||||||
|
address sender, address relayer, address instanceAddress, bytes32 nullifierHash
|
||||||
|
);
|
||||||
|
|
||||||
event InstanceRegistryUpdated(address newInstanceRegistryProxyAddress);
|
event InstanceRegistryUpdated(address newInstanceRegistryProxyAddress);
|
||||||
event RelayerRegistryUpdated(address newRelayerRegistryProxyAddress);
|
event RelayerRegistryUpdated(address newRelayerRegistryProxyAddress);
|
||||||
|
event FeeOracleManagerUpdated(address newFeeOracleManagerProxyAddress);
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
@ -69,17 +86,23 @@ contract TornadoRouter is Initializable {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize(address _instanceRegistryAddress, address _relayerRegistryAddress)
|
function version() public pure virtual returns (string memory) {
|
||||||
external
|
return "v2-slashing-and-oracles";
|
||||||
onlyGovernance
|
}
|
||||||
initializer
|
|
||||||
{
|
function initialize(
|
||||||
|
address _instanceRegistryAddress,
|
||||||
|
address _relayerRegistryAddress,
|
||||||
|
address _feeOracleManager
|
||||||
|
) external onlyGovernance initializer {
|
||||||
instanceRegistry = InstanceRegistry(_instanceRegistryAddress);
|
instanceRegistry = InstanceRegistry(_instanceRegistryAddress);
|
||||||
relayerRegistry = RelayerRegistry(_relayerRegistryAddress);
|
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 _tornado The instance to deposit into (address).
|
||||||
* @param _commitment The commitment which will be added to the Merkle Tree.
|
* @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.
|
* @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
|
* @notice Withdrawal function for Governance relayers by which they can safely process a withdrawal
|
||||||
* smart-contract relayer wallet, isn't deployed, this function will be the only gateway for withdrawing
|
* without getting slashed (which kicks them off the frontend).
|
||||||
* from the _new_ instances. But fear not! Each instance contains a permissionless function by the name of
|
* @param _tornado The Tornado instance to withdraw from.
|
||||||
* `checkInfrastructureIsDead()`, which can be used in the case that the infra is deactivated or
|
* @param _proof Bytes proof data.
|
||||||
* malfunctions (this contract can't malfunction to block withdrawals). It makes the instance work just
|
* @param _root A current or historical bytes32 root of the Merkle Tree within the proofs context.
|
||||||
* like a regular one again! The intention is to call the formerly mentioned function when the new
|
* @param _nullifierHash The bytes32 nullifierHash for the deposit.
|
||||||
* infrastructure is deployed, and until then, use the simple fix in the new instances to route
|
* @param _recipient The address of recipient for withdrawn funds.
|
||||||
* withdrawals
|
* @param _relayer The address of the relayer which will be making the withdrawal.
|
||||||
* through the registry. ALSO NOTE, that manual depositors and withdrawers will still be able to
|
* @param _fee The fee in bips to pay the relayer.
|
||||||
* permissionlessly use the instances, they anyways do not rely on the `relayer` address field, and as
|
* @param _refund If swapping into ETH on the other side, use this to specify how much should be paid for
|
||||||
* such they will be able to claim their deposits no matter whether the check is effective or not!
|
*/
|
||||||
|
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 _tornado The Tornado instance to withdraw from.
|
||||||
* @param _proof Bytes proof data.
|
* @param _proof Bytes proof data.
|
||||||
* @param _root A current or historical bytes32 root of the Merkle Tree within the proofs context.
|
* @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 _fee,
|
||||||
uint256 _refund
|
uint256 _refund
|
||||||
) public payable virtual {
|
) public payable virtual {
|
||||||
// If an instance is turned off, we still want withdrawals to pass
|
if (_relayer != address(0)) {
|
||||||
// so instead of requiring `isEnabled`, we will only use it to
|
slashProofs[keccak256(abi.encode(msg.sender, _relayer, address(_tornado), _nullifierHash))] = true;
|
||||||
// decide whether relayers should burn or not. This is beneficial
|
emit WithdrawalWithRelayer(msg.sender, _relayer, address(_tornado), _nullifierHash);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_tornado.withdraw{ value: msg.value }(
|
_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)
|
function rescueTokens(IERC20 _token, address payable _to, uint256 _amount)
|
||||||
public
|
public
|
||||||
virtual
|
virtual
|
||||||
@ -201,9 +284,8 @@ contract TornadoRouter is Initializable {
|
|||||||
emit RelayerRegistryUpdated(_newRelayerRegistryProxyAddress);
|
emit RelayerRegistryUpdated(_newRelayerRegistryProxyAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
function setFeeOracleManager(address _newFeeOracleManagerProxyAddress) external onlyGovernance {
|
||||||
|
feeOracleManager = FeeOracleManager(_newFeeOracleManagerProxyAddress);
|
||||||
function version() public pure virtual returns (string memory) {
|
emit FeeOracleManagerUpdated(_newFeeOracleManagerProxyAddress);
|
||||||
return "v2-oracle-manager";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
src/v2/TornadoStakingRewards.sol
Normal file
166
src/v2/TornadoStakingRewards.sol
Normal file
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
8
src/v2/interfaces/IENS.sol
Normal file
8
src/v2/interfaces/IENS.sol
Normal file
@ -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);
|
||||||
|
}
|
39
src/v2/libraries/ENSNamehash.sol
Normal file
39
src/v2/libraries/ENSNamehash.sol
Normal file
@ -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.expectRevert();
|
||||||
vm.prank(address(governance));
|
vm.prank(address(governance));
|
||||||
router.initialize(address(instanceRegistry), address(instanceRegistry));
|
router.initialize(address(instanceRegistry), address(instanceRegistry), address(feeOracleManager));
|
||||||
|
|
||||||
vm.expectRevert();
|
vm.expectRevert();
|
||||||
router.initialize(address(instanceRegistry), address(instanceRegistry));
|
router.initialize(address(instanceRegistry), address(instanceRegistry), address(feeOracleManager));
|
||||||
|
|
||||||
// Check fee logic and print to console.
|
// Check fee logic and print to console.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user