Line data Source code
1 : // SPDX-License-Identifier: MIT 2 : 3 : pragma solidity ^0.6.12; 4 : pragma experimental ABIEncoderV2; 5 : 6 : import { console2 } from "forge-std/console2.sol"; 7 : 8 : import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 9 : import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 10 : import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 11 : import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol"; 12 : import { EnsResolve } from "torn-token/contracts/ENS.sol"; 13 : 14 : interface ITornadoVault { 15 : function withdrawTorn(address recipient, uint256 amount) external; 16 : } 17 : 18 : interface ITornadoGovernance { 19 : function lockedBalance(address account) external view returns (uint256); 20 : 21 : function userVault() external view returns (ITornadoVault); 22 : } 23 : 24 : /** 25 : * @notice This is the staking contract of the governance staking upgrade. 26 : * This contract should hold the staked funds which are received upon relayer registration, 27 : * and properly attribute rewards to addresses without security issues. 28 : * @dev CONTRACT RISKS: 29 : * - Relayer staked TORN at risk if contract is compromised. 30 : * 31 : */ 32 : contract TornadoStakingRewards is Initializable, EnsResolve { 33 : using SafeMath for uint256; 34 : using SafeERC20 for IERC20; 35 : 36 : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMMUTABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 37 : 38 : /// @notice 1e25 39 : uint256 public immutable ratioConstant; 40 : ITornadoGovernance public immutable Governance; 41 : IERC20 public immutable torn; 42 : address public immutable router; 43 : 44 : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MUTABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 45 : 46 : /// @notice the sum torn_burned_i/locked_amount_i*coefficient where i is incremented at each burn 47 : uint256 public accumulatedRewardPerTorn; 48 : /// @notice notes down accumulatedRewardPerTorn for an address on a lock/unlock/claim 49 : mapping(address => uint256) public accumulatedRewardRateOnLastUpdate; 50 : /// @notice notes down how much an account may claim 51 : mapping(address => uint256) public accumulatedRewards; 52 : 53 : event RewardsUpdated(address indexed account, uint256 rewards); 54 : event RewardsClaimed(address indexed account, uint256 rewardsClaimed); 55 : 56 : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 57 : 58 : // Minor code change here we won't resolve the registry by ENS 59 : constructor(address governanceAddress, address tornAddress, address _router) public { 60 : Governance = ITornadoGovernance(governanceAddress); 61 : torn = IERC20(tornAddress); 62 : router = _router; 63 : ratioConstant = IERC20(tornAddress).totalSupply(); 64 : } 65 : 66 : modifier onlyGovernance() { 67 : require(msg.sender == address(Governance), "TornadoStakingRewards: onlyGovernance"); 68 : _; 69 : } 70 : 71 : modifier onlyRouter() { 72 : require(msg.sender == router, "TornadoStakingRewards: onlyRouter"); 73 : _; 74 : } 75 : 76 : /** 77 : * @notice This function should safely send a user his rewards. 78 : * @dev IMPORTANT FUNCTION: 79 : * We know that rewards are going to be updated every time someone locks or unlocks 80 : * so we know that this function can't be used to falsely increase the amount of 81 : * lockedTorn by locking in governance and subsequently calling it. 82 : * - set rewards to 0 greedily 83 : */ 84 : function getReward() external { 85 0 : uint256 rewards = _updateReward(msg.sender, Governance.lockedBalance(msg.sender)); 86 0 : rewards = rewards.add(accumulatedRewards[msg.sender]); 87 0 : accumulatedRewards[msg.sender] = 0; 88 0 : torn.safeTransfer(msg.sender, rewards); 89 0 : emit RewardsClaimed(msg.sender, rewards); 90 : } 91 : 92 : /** 93 : * @notice This function should increment the proper amount of rewards per torn for the contract 94 : * @dev IMPORTANT FUNCTION: 95 : * - calculation must not overflow with extreme values 96 : * (amount <= 1e25) * 1e25 / (balance of vault <= 1e25) -> (extreme values) 97 : * @param amount amount to add to the rewards 98 : */ 99 : function addBurnRewards(uint256 amount) external { 100 1 : require( 101 : msg.sender == address(Governance) || msg.sender == router, 102 : "TornadoStakingRewards: only gov or router" 103 : ); 104 1 : accumulatedRewardPerTorn = accumulatedRewardPerTorn.add( 105 : amount.mul(ratioConstant).div(torn.balanceOf(address(Governance.userVault()))) 106 : ); 107 : } 108 : 109 : /** 110 : * @notice This function should allow governance to properly update the accumulated rewards rate for an 111 : * account 112 : * @param account address of account to update data for 113 : * @param amountLockedBeforehand the balance locked beforehand in the governance contract 114 : * 115 : */ 116 : function updateRewardsOnLockedBalanceChange(address account, uint256 amountLockedBeforehand) 117 : external 118 : onlyGovernance 119 : { 120 4 : uint256 claimed = _updateReward(account, amountLockedBeforehand); 121 4 : accumulatedRewards[account] = accumulatedRewards[account].add(claimed); 122 : } 123 : 124 : /** 125 : * @notice This function should allow governance rescue tokens from the staking rewards contract 126 : */ 127 : function withdrawTorn(uint256 amount) external onlyGovernance { 128 0 : if (amount == type(uint256).max) amount = torn.balanceOf(address(this)); 129 0 : torn.safeTransfer(address(Governance), amount); 130 : } 131 : 132 : /** 133 : * @notice This function should calculated the proper amount of rewards attributed to user since the last 134 : * update 135 : * @dev IMPORTANT FUNCTION: 136 : * - calculation must not overflow with extreme values 137 : * (accumulatedReward <= 1e25) * (lockedBeforehand <= 1e25) / 1e25 138 : * - result may go to 0, since this implies on 1 TORN locked => accumulatedReward <= 1e7, meaning a 139 : * very small reward 140 : * @param account address of account to calculate rewards for 141 : * @param amountLockedBeforehand the balance locked beforehand in the governance contract 142 : * @return claimed the rewards attributed to user since the last update 143 : */ 144 : function _updateReward(address account, uint256 amountLockedBeforehand) 145 : private 146 : returns (uint256 claimed) 147 : { 148 4 : if (amountLockedBeforehand != 0) { 149 4 : claimed = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul( 150 : amountLockedBeforehand 151 : ).div(ratioConstant); 152 : } 153 4 : accumulatedRewardRateOnLastUpdate[account] = accumulatedRewardPerTorn; 154 4 : emit RewardsUpdated(account, claimed); 155 : } 156 : 157 : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ 158 : 159 : /** 160 : * @notice This function should show a user his rewards. 161 : * @param account address of account to calculate rewards for 162 : */ 163 : function checkReward(address account) external view returns (uint256 rewards) { 164 0 : uint256 amountLocked = Governance.lockedBalance(account); 165 0 : if (amountLocked != 0) { 166 0 : rewards = (accumulatedRewardPerTorn.sub(accumulatedRewardRateOnLastUpdate[account])).mul( 167 : amountLocked 168 : ).div(ratioConstant); 169 : } 170 0 : rewards = rewards.add(accumulatedRewards[account]); 171 : } 172 : }