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