133 lines
5.8 KiB
Solidity
133 lines
5.8 KiB
Solidity
|
// 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";
|
||
|
import { ITornadoGovernance } from "../interfaces/ITornadoGovernance.sol";
|
||
|
|
||
|
/**
|
||
|
* @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");
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
constructor(
|
||
|
address governanceAddress,
|
||
|
address tornAddress,
|
||
|
bytes32 _relayerRegistry
|
||
|
) public {
|
||
|
Governance = ITornadoGovernance(governanceAddress);
|
||
|
torn = IERC20(tornAddress);
|
||
|
relayerRegistry = resolve(_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]);
|
||
|
}
|
||
|
}
|