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
|
||||
|
||||
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
|
||||
|
||||
|
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,
|
||||
* 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
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);
|
||||
}
|
||||
}
|
||||
|
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.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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user