diff --git a/contracts/staking/RewardRouter.sol b/contracts/staking/RewardRouter.sol index fc946f2..3323337 100644 --- a/contracts/staking/RewardRouter.sol +++ b/contracts/staking/RewardRouter.sol @@ -6,116 +6,33 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard. import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IRewardTracker} from "./interfaces/IRewardTracker.sol"; -import {IRewardRouter} from "./interfaces/IRewardRouter.sol"; import {IVester} from "./interfaces/IVester.sol"; -import {IMintable} from "../interfaces/IMintable.sol"; -import "../core/interfaces/IGlpManager.sol"; import {Governable} from "../core/Governable.sol"; -contract RewardRouter is IRewardRouterV2, ReentrancyGuard, Governable { +contract RewardRouter is ReentrancyGuard, Governable { using SafeERC20 for IERC20; - using Address for address payable; - - enum VotingPowerType { - None, - BaseStakedAmount, - BaseAndBonusStakedAmount - } - - uint256 public constant BASIS_POINTS_DIVISOR = 10000; - - bool public isInitialized; - - address public weth; address public gmx; address public esGmx; - address public bnGmx; - - address public glp; // GMX Liquidity Provider token address public stakedGmxTracker; - address public bonusGmxTracker; - address public feeGmxTracker; - - address public override stakedGlpTracker; - address public override feeGlpTracker; - - address public glpManager; - address public gmxVester; - address public glpVester; - uint256 public maxBoostBasisPoints; - bool public inStrictTransferMode; - - address public govToken; - VotingPowerType public votingPowerType; - - mapping (address => address) public pendingReceivers; + mapping (address sender=> address receiver) public pendingReceivers; event StakeGmx(address account, address token, uint256 amount); event UnstakeGmx(address account, address token, uint256 amount); - event StakeGlp(address account, uint256 amount); - event UnstakeGlp(address account, uint256 amount); - - receive() external payable { - require(msg.sender == weth, "Router: invalid sender"); - } - - function initialize( - address _weth, + constructor( address _gmx, address _esGmx, - address _bnGmx, - address _glp, address _stakedGmxTracker, - address _bonusGmxTracker, - address _feeGmxTracker, - address _feeGlpTracker, - address _stakedGlpTracker, - address _glpManager, - address _gmxVester, - address _glpVester, - address _govToken - ) external onlyGov { - require(!isInitialized, "already initialized"); - isInitialized = true; - - weth = _weth; - + address _gmxVester + ) { gmx = _gmx; esGmx = _esGmx; - bnGmx = _bnGmx; - - glp = _glp; - stakedGmxTracker = _stakedGmxTracker; - bonusGmxTracker = _bonusGmxTracker; - feeGmxTracker = _feeGmxTracker; - - feeGlpTracker = _feeGlpTracker; - stakedGlpTracker = _stakedGlpTracker; - - glpManager = _glpManager; - gmxVester = _gmxVester; - glpVester = _glpVester; - - govToken = _govToken; - } - - function setInStrictTransferMode(bool _inStrictTransferMode) external onlyGov { - inStrictTransferMode = _inStrictTransferMode; - } - - function setMaxBoostBasisPoints(uint256 _maxBoostBasisPoints) external onlyGov { - maxBoostBasisPoints = _maxBoostBasisPoints; - } - - function setVotingPowerType(VotingPowerType _votingPowerType) external onlyGov { - votingPowerType = _votingPowerType; } // to help users who accidentally send their tokens to this contract @@ -143,95 +60,22 @@ contract RewardRouter is IRewardRouterV2, ReentrancyGuard, Governable { } function unstakeGmx(uint256 _amount) external nonReentrant { - _unstakeGmx(msg.sender, gmx, _amount, true); + _unstakeGmx(msg.sender, gmx, _amount); } function unstakeEsGmx(uint256 _amount) external nonReentrant { - _unstakeGmx(msg.sender, esGmx, _amount, true); - } - - function mintAndStakeGlp(address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external nonReentrant returns (uint256) { - require(_amount > 0, "invalid _amount"); - - address account = msg.sender; - uint256 glpAmount = IGlpManager(glpManager).addLiquidityForAccount(account, account, _token, _amount, _minUsdg, _minGlp); - IRewardTracker(feeGlpTracker).stakeForAccount(account, account, glp, glpAmount); - IRewardTracker(stakedGlpTracker).stakeForAccount(account, account, feeGlpTracker, glpAmount); - - emit StakeGlp(account, glpAmount); - - return glpAmount; - } - - function mintAndStakeGlpETH(uint256 _minUsdg, uint256 _minGlp) external payable nonReentrant returns (uint256) { - require(msg.value > 0, "invalid msg.value"); - - IWETH(weth).deposit{value: msg.value}(); - IERC20(weth).approve(glpManager, msg.value); - - address account = msg.sender; - uint256 glpAmount = IGlpManager(glpManager).addLiquidityForAccount(address(this), account, weth, msg.value, _minUsdg, _minGlp); - - IRewardTracker(feeGlpTracker).stakeForAccount(account, account, glp, glpAmount); - IRewardTracker(stakedGlpTracker).stakeForAccount(account, account, feeGlpTracker, glpAmount); - - emit StakeGlp(account, glpAmount); - - return glpAmount; - } - - function unstakeAndRedeemGlp(address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external nonReentrant returns (uint256) { - require(_glpAmount > 0, "invalid _glpAmount"); - - address account = msg.sender; - IRewardTracker(stakedGlpTracker).unstakeForAccount(account, feeGlpTracker, _glpAmount, account); - IRewardTracker(feeGlpTracker).unstakeForAccount(account, glp, _glpAmount, account); - uint256 amountOut = IGlpManager(glpManager).removeLiquidityForAccount(account, _tokenOut, _glpAmount, _minOut, _receiver); - - emit UnstakeGlp(account, _glpAmount); - - return amountOut; - } - - function unstakeAndRedeemGlpETH(uint256 _glpAmount, uint256 _minOut, address payable _receiver) external nonReentrant returns (uint256) { - require(_glpAmount > 0, "invalid _glpAmount"); - - address account = msg.sender; - IRewardTracker(stakedGlpTracker).unstakeForAccount(account, feeGlpTracker, _glpAmount, account); - IRewardTracker(feeGlpTracker).unstakeForAccount(account, glp, _glpAmount, account); - uint256 amountOut = IGlpManager(glpManager).removeLiquidityForAccount(account, weth, _glpAmount, _minOut, address(this)); - - IWETH(weth).withdraw(amountOut); - - _receiver.sendValue(amountOut); - - emit UnstakeGlp(account, _glpAmount); - - return amountOut; + _unstakeGmx(msg.sender, esGmx, _amount); } function claim() external nonReentrant { address account = msg.sender; - IRewardTracker(feeGmxTracker).claimForAccount(account, account); - IRewardTracker(feeGlpTracker).claimForAccount(account, account); - IRewardTracker(stakedGmxTracker).claimForAccount(account, account); - IRewardTracker(stakedGlpTracker).claimForAccount(account, account); } function claimEsGmx() external nonReentrant { address account = msg.sender; - IRewardTracker(stakedGmxTracker).claimForAccount(account, account); - IRewardTracker(stakedGlpTracker).claimForAccount(account, account); - } - - function claimFees() external nonReentrant { - address account = msg.sender; - - IRewardTracker(feeGmxTracker).claimForAccount(account, account); - IRewardTracker(feeGlpTracker).claimForAccount(account, account); } function compound() external nonReentrant { @@ -246,18 +90,13 @@ contract RewardRouter is IRewardRouterV2, ReentrancyGuard, Governable { bool _shouldClaimGmx, bool _shouldStakeGmx, bool _shouldClaimEsGmx, - bool _shouldStakeEsGmx, - bool _shouldStakeMultiplierPoints, - bool _shouldClaimWeth, - bool _shouldConvertWethToEth + bool _shouldStakeEsGmx ) external nonReentrant { address account = msg.sender; uint256 gmxAmount = 0; if (_shouldClaimGmx) { - uint256 gmxAmount0 = IVester(gmxVester).claimForAccount(account, account); - uint256 gmxAmount1 = IVester(glpVester).claimForAccount(account, account); - gmxAmount = gmxAmount0.add(gmxAmount1); + gmxAmount = IVester(gmxVester).claimForAccount(account, account); } if (_shouldStakeGmx && gmxAmount > 0) { @@ -266,35 +105,12 @@ contract RewardRouter is IRewardRouterV2, ReentrancyGuard, Governable { uint256 esGmxAmount = 0; if (_shouldClaimEsGmx) { - uint256 esGmxAmount0 = IRewardTracker(stakedGmxTracker).claimForAccount(account, account); - uint256 esGmxAmount1 = IRewardTracker(stakedGlpTracker).claimForAccount(account, account); - esGmxAmount = esGmxAmount0.add(esGmxAmount1); + esGmxAmount = IRewardTracker(stakedGmxTracker).claimForAccount(account, account); } if (_shouldStakeEsGmx && esGmxAmount > 0) { _stakeGmx(account, account, esGmx, esGmxAmount); } - - if (_shouldStakeMultiplierPoints) { - _stakeBnGmx(account); - } - - if (_shouldClaimWeth) { - if (_shouldConvertWethToEth) { - uint256 weth0 = IRewardTracker(feeGmxTracker).claimForAccount(account, address(this)); - uint256 weth1 = IRewardTracker(feeGlpTracker).claimForAccount(account, address(this)); - - uint256 wethAmount = weth0.add(weth1); - IWETH(weth).withdraw(wethAmount); - - payable(account).sendValue(wethAmount); - } else { - IRewardTracker(feeGmxTracker).claimForAccount(account, account); - IRewardTracker(feeGlpTracker).claimForAccount(account, account); - } - } - - _syncVotingPower(account); } function batchCompoundForAccounts(address[] memory _accounts) external nonReentrant onlyGov { @@ -303,32 +119,16 @@ contract RewardRouter is IRewardRouterV2, ReentrancyGuard, Governable { } } - // the _validateReceiver function checks that the averageStakedAmounts and cumulativeRewards - // values of an account are zero, this is to help ensure that vesting calculations can be - // done correctly - // averageStakedAmounts and cumulativeRewards are updated if the claimable reward for an account - // is more than zero - // it is possible for multiple transfers to be sent into a single account, using signalTransfer and - // acceptTransfer, if those values have not been updated yet - // for GLP transfers it is also possible to transfer GLP into an account using the StakedGlp contract function signalTransfer(address _receiver) external nonReentrant { require(IERC20(gmxVester).balanceOf(msg.sender) == 0, "sender has vested tokens"); - require(IERC20(glpVester).balanceOf(msg.sender) == 0, "sender has vested tokens"); _validateReceiver(_receiver); - if (inStrictTransferMode) { - uint256 balance = IRewardTracker(feeGmxTracker).stakedAmounts(msg.sender); - uint256 allowance = IERC20(feeGmxTracker).allowance(msg.sender, _receiver); - require(allowance >= balance, "insufficient allowance"); - } - pendingReceivers[msg.sender] = _receiver; } function acceptTransfer(address _sender) external nonReentrant { require(IERC20(gmxVester).balanceOf(_sender) == 0, "sender has vested tokens"); - require(IERC20(glpVester).balanceOf(_sender) == 0, "sender has vested tokens"); address receiver = msg.sender; require(pendingReceivers[_sender] == receiver, "transfer not signalled"); @@ -339,206 +139,59 @@ contract RewardRouter is IRewardRouterV2, ReentrancyGuard, Governable { uint256 stakedGmx = IRewardTracker(stakedGmxTracker).depositBalances(_sender, gmx); if (stakedGmx > 0) { - _unstakeGmx(_sender, gmx, stakedGmx, false); + _unstakeGmx(_sender, gmx, stakedGmx); _stakeGmx(_sender, receiver, gmx, stakedGmx); } uint256 stakedEsGmx = IRewardTracker(stakedGmxTracker).depositBalances(_sender, esGmx); if (stakedEsGmx > 0) { - _unstakeGmx(_sender, esGmx, stakedEsGmx, false); + _unstakeGmx(_sender, esGmx, stakedEsGmx); _stakeGmx(_sender, receiver, esGmx, stakedEsGmx); } - uint256 stakedBnGmx = IRewardTracker(feeGmxTracker).depositBalances(_sender, bnGmx); - if (stakedBnGmx > 0) { - IRewardTracker(feeGmxTracker).unstakeForAccount(_sender, bnGmx, stakedBnGmx, _sender); - IRewardTracker(feeGmxTracker).stakeForAccount(_sender, receiver, bnGmx, stakedBnGmx); - } - uint256 esGmxBalance = IERC20(esGmx).balanceOf(_sender); if (esGmxBalance > 0) { IERC20(esGmx).transferFrom(_sender, receiver, esGmxBalance); } - uint256 bnGmxBalance = IERC20(bnGmx).balanceOf(_sender); - if (bnGmxBalance > 0) { - IMintable(bnGmx).burn(_sender, bnGmxBalance); - IMintable(bnGmx).mint(receiver, bnGmxBalance); - } - - uint256 glpAmount = IRewardTracker(feeGlpTracker).depositBalances(_sender, glp); - if (glpAmount > 0) { - IRewardTracker(stakedGlpTracker).unstakeForAccount(_sender, feeGlpTracker, glpAmount, _sender); - IRewardTracker(feeGlpTracker).unstakeForAccount(_sender, glp, glpAmount, _sender); - - IRewardTracker(feeGlpTracker).stakeForAccount(_sender, receiver, glp, glpAmount); - IRewardTracker(stakedGlpTracker).stakeForAccount(receiver, receiver, feeGlpTracker, glpAmount); - } - IVester(gmxVester).transferStakeValues(_sender, receiver); - IVester(glpVester).transferStakeValues(_sender, receiver); - - _syncVotingPower(_sender); - _syncVotingPower(receiver); } function _validateReceiver(address _receiver) private view { require(IRewardTracker(stakedGmxTracker).averageStakedAmounts(_receiver) == 0, "stakedGmxTracker.averageStakedAmounts > 0"); require(IRewardTracker(stakedGmxTracker).cumulativeRewards(_receiver) == 0, "stakedGmxTracker.cumulativeRewards > 0"); - require(IRewardTracker(bonusGmxTracker).averageStakedAmounts(_receiver) == 0, "bonusGmxTracker.averageStakedAmounts > 0"); - require(IRewardTracker(bonusGmxTracker).cumulativeRewards(_receiver) == 0, "bonusGmxTracker.cumulativeRewards > 0"); - - require(IRewardTracker(feeGmxTracker).averageStakedAmounts(_receiver) == 0, "feeGmxTracker.averageStakedAmounts > 0"); - require(IRewardTracker(feeGmxTracker).cumulativeRewards(_receiver) == 0, "feeGmxTracker.cumulativeRewards > 0"); - require(IVester(gmxVester).transferredAverageStakedAmounts(_receiver) == 0, "gmxVester.transferredAverageStakedAmounts > 0"); require(IVester(gmxVester).transferredCumulativeRewards(_receiver) == 0, "gmxVester.transferredCumulativeRewards > 0"); - require(IRewardTracker(stakedGlpTracker).averageStakedAmounts(_receiver) == 0, "stakedGlpTracker.averageStakedAmounts > 0"); - require(IRewardTracker(stakedGlpTracker).cumulativeRewards(_receiver) == 0, "stakedGlpTracker.cumulativeRewards > 0"); - - require(IRewardTracker(feeGlpTracker).averageStakedAmounts(_receiver) == 0, "feeGlpTracker.averageStakedAmounts > 0"); - require(IRewardTracker(feeGlpTracker).cumulativeRewards(_receiver) == 0, "feeGlpTracker.cumulativeRewards > 0"); - - require(IVester(glpVester).transferredAverageStakedAmounts(_receiver) == 0, "gmxVester.transferredAverageStakedAmounts > 0"); - require(IVester(glpVester).transferredCumulativeRewards(_receiver) == 0, "gmxVester.transferredCumulativeRewards > 0"); - require(IERC20(gmxVester).balanceOf(_receiver) == 0, "gmxVester.balance > 0"); - require(IERC20(glpVester).balanceOf(_receiver) == 0, "glpVester.balance > 0"); } function _compound(address _account) private { _compoundGmx(_account); - _compoundGlp(_account); - _syncVotingPower(_account); } - function _compoundGmx(address _account) private { + function _compoundGmx(address _account) private { uint256 esGmxAmount = IRewardTracker(stakedGmxTracker).claimForAccount(_account, _account); if (esGmxAmount > 0) { _stakeGmx(_account, _account, esGmx, esGmxAmount); } - - _stakeBnGmx(_account); } - function _compoundGlp(address _account) private { - uint256 esGmxAmount = IRewardTracker(stakedGlpTracker).claimForAccount(_account, _account); - if (esGmxAmount > 0) { - _stakeGmx(_account, _account, esGmx, esGmxAmount); - } - } function _stakeGmx(address _fundingAccount, address _account, address _token, uint256 _amount) private { require(_amount > 0, "invalid _amount"); IRewardTracker(stakedGmxTracker).stakeForAccount(_fundingAccount, _account, _token, _amount); - IRewardTracker(bonusGmxTracker).stakeForAccount(_account, _account, stakedGmxTracker, _amount); - IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bonusGmxTracker, _amount); - - _syncVotingPower(_account); emit StakeGmx(_account, _token, _amount); } - // note that _syncVotingPower is not called here, in functions which - // call _stakeBnGmx it should be ensured that _syncVotingPower is called - // after - function _stakeBnGmx(address _account) private { - IRewardTracker(bonusGmxTracker).claimForAccount(_account, _account); - - // get the bnGmx balance of the user, this would be the amount of - // bnGmx that has not been staked - uint256 bnGmxAmount = IERC20(bnGmx).balanceOf(_account); - if (bnGmxAmount == 0) { return; } - - // get the baseStakedAmount which would be the sum of staked gmx and staked esGmx tokens - uint256 baseStakedAmount = IRewardTracker(stakedGmxTracker).stakedAmounts(_account); - uint256 maxAllowedBnGmxAmount = baseStakedAmount.mul(maxBoostBasisPoints).div(BASIS_POINTS_DIVISOR); - uint256 currentBnGmxAmount = IRewardTracker(feeGmxTracker).depositBalances(_account, bnGmx); - if (currentBnGmxAmount == maxAllowedBnGmxAmount) { return; } - - // if the currentBnGmxAmount is more than the maxAllowedBnGmxAmount - // unstake the excess tokens - if (currentBnGmxAmount > maxAllowedBnGmxAmount) { - uint256 amountToUnstake = currentBnGmxAmount.sub(maxAllowedBnGmxAmount); - IRewardTracker(feeGmxTracker).unstakeForAccount(_account, bnGmx, amountToUnstake, _account); - return; - } - - uint256 maxStakeableBnGmxAmount = maxAllowedBnGmxAmount.sub(currentBnGmxAmount); - if (bnGmxAmount > maxStakeableBnGmxAmount) { - bnGmxAmount = maxStakeableBnGmxAmount; - } - - IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bnGmx, bnGmxAmount); - } - - function _unstakeGmx(address _account, address _token, uint256 _amount, bool _shouldReduceBnGmx) private { + function _unstakeGmx(address _account, address _token, uint256 _amount) private { require(_amount > 0, "invalid _amount"); - - uint256 balance = IRewardTracker(stakedGmxTracker).stakedAmounts(_account); - - IRewardTracker(feeGmxTracker).unstakeForAccount(_account, bonusGmxTracker, _amount, _account); - IRewardTracker(bonusGmxTracker).unstakeForAccount(_account, stakedGmxTracker, _amount, _account); + // uint256 balance = IRewardTracker(stakedGmxTracker).stakedAmounts(_account); IRewardTracker(stakedGmxTracker).unstakeForAccount(_account, _token, _amount, _account); - if (_shouldReduceBnGmx) { - IRewardTracker(bonusGmxTracker).claimForAccount(_account, _account); - - // unstake and burn staked bnGmx tokens - uint256 stakedBnGmx = IRewardTracker(feeGmxTracker).depositBalances(_account, bnGmx); - if (stakedBnGmx > 0) { - uint256 reductionAmount = stakedBnGmx.mul(_amount).div(balance); - IRewardTracker(feeGmxTracker).unstakeForAccount(_account, bnGmx, reductionAmount, _account); - IMintable(bnGmx).burn(_account, reductionAmount); - } - - // burn bnGmx tokens from user's balance - uint256 bnGmxBalance = IERC20(bnGmx).balanceOf(_account); - if (bnGmxBalance > 0) { - uint256 amountToBurn = bnGmxBalance.mul(_amount).div(balance); - IMintable(bnGmx).burn(_account, amountToBurn); - } - } - - _syncVotingPower(_account); - emit UnstakeGmx(_account, _token, _amount); } - - function _syncVotingPower(address _account) private { - if (votingPowerType == VotingPowerType.None) { - return; - } - - if (votingPowerType == VotingPowerType.BaseStakedAmount) { - uint256 baseStakedAmount = IRewardTracker(stakedGmxTracker).stakedAmounts(_account); - _syncVotingPower(_account, baseStakedAmount); - return; - } - - if (votingPowerType == VotingPowerType.BaseAndBonusStakedAmount) { - uint256 stakedAmount = IRewardTracker(feeGmxTracker).stakedAmounts(_account); - _syncVotingPower(_account, stakedAmount); - return; - } - - revert("unsupported votingPowerType"); - } - - function _syncVotingPower(address _account, uint256 _amount) private { - uint256 currentVotingPower = IERC20(govToken).balanceOf(_account); - if (currentVotingPower == _amount) { return; } - - if (currentVotingPower > _amount) { - uint256 amountToBurn = currentVotingPower.sub(_amount); - IMintable(govToken).burn(_account, amountToBurn); - return; - } - - uint256 amountToMint = _amount.sub(currentVotingPower); - IMintable(govToken).mint(_account, amountToMint); - } }