// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IRewardTracker} from "./interfaces/IRewardTracker.sol"; import {IVester} from "./interfaces/IVester.sol"; import {Governable} from "../core/Governable.sol"; contract RewardRouter is ReentrancyGuard, Governable { using SafeERC20 for IERC20; address public gmx; address public esGmx; address public stakedGmxTracker; address public gmxVester; mapping(address sender => address receiver) public pendingReceivers; event StakeGmx(address account, address token, uint256 amount); event UnstakeGmx(address account, address token, uint256 amount); constructor(address _gmx, address _esGmx, address _stakedGmxTracker, address _gmxVester) { gmx = _gmx; esGmx = _esGmx; stakedGmxTracker = _stakedGmxTracker; gmxVester = _gmxVester; } // to help users who accidentally send their tokens to this contract function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { IERC20(_token).safeTransfer(_account, _amount); } function batchStakeGmxForAccount( address[] memory _accounts, uint256[] memory _amounts ) external nonReentrant onlyGov { address _gmx = gmx; for (uint256 i = 0; i < _accounts.length; i++) { _stakeGmx(msg.sender, _accounts[i], _gmx, _amounts[i]); } } function stakeGmxForAccount(address _account, uint256 _amount) external nonReentrant onlyGov { _stakeGmx(msg.sender, _account, gmx, _amount); } function stakeGmx(uint256 _amount) external nonReentrant { _stakeGmx(msg.sender, msg.sender, gmx, _amount); } function stakeEsGmx(uint256 _amount) external nonReentrant { _stakeGmx(msg.sender, msg.sender, esGmx, _amount); } function unstakeGmx(uint256 _amount) external nonReentrant { _unstakeGmx(msg.sender, gmx, _amount); } function unstakeEsGmx(uint256 _amount) external nonReentrant { _unstakeGmx(msg.sender, esGmx, _amount); } function claim() external nonReentrant { address account = msg.sender; IRewardTracker(stakedGmxTracker).claimForAccount(account, account); } function claimEsGmx() external nonReentrant { address account = msg.sender; IRewardTracker(stakedGmxTracker).claimForAccount(account, account); } function compound() external nonReentrant { _compound(msg.sender); } function compoundForAccount(address _account) external nonReentrant onlyGov { _compound(_account); } function handleRewards( bool _shouldClaimGmx, bool _shouldStakeGmx, bool _shouldClaimEsGmx, bool _shouldStakeEsGmx ) external nonReentrant { address account = msg.sender; uint256 gmxAmount = 0; if (_shouldClaimGmx) { gmxAmount = IVester(gmxVester).claimForAccount(account, account); } if (_shouldStakeGmx && gmxAmount > 0) { _stakeGmx(account, account, gmx, gmxAmount); } uint256 esGmxAmount = 0; if (_shouldClaimEsGmx) { esGmxAmount = IRewardTracker(stakedGmxTracker).claimForAccount(account, account); } if (_shouldStakeEsGmx && esGmxAmount > 0) { _stakeGmx(account, account, esGmx, esGmxAmount); } } function batchCompoundForAccounts(address[] memory _accounts) external nonReentrant onlyGov { for (uint256 i = 0; i < _accounts.length; i++) { _compound(_accounts[i]); } } function signalTransfer(address _receiver) external nonReentrant { require(IERC20(gmxVester).balanceOf(msg.sender) == 0, "sender has vested tokens"); _validateReceiver(_receiver); pendingReceivers[msg.sender] = _receiver; } function acceptTransfer(address _sender) external nonReentrant { require(IERC20(gmxVester).balanceOf(_sender) == 0, "sender has vested tokens"); address receiver = msg.sender; require(pendingReceivers[_sender] == receiver, "transfer not signalled"); delete pendingReceivers[_sender]; _validateReceiver(receiver); _compound(_sender); uint256 stakedGmx = IRewardTracker(stakedGmxTracker).depositBalances(_sender, gmx); if (stakedGmx > 0) { _unstakeGmx(_sender, gmx, stakedGmx); _stakeGmx(_sender, receiver, gmx, stakedGmx); } uint256 stakedEsGmx = IRewardTracker(stakedGmxTracker).depositBalances(_sender, esGmx); if (stakedEsGmx > 0) { _unstakeGmx(_sender, esGmx, stakedEsGmx); _stakeGmx(_sender, receiver, esGmx, stakedEsGmx); } uint256 esGmxBalance = IERC20(esGmx).balanceOf(_sender); if (esGmxBalance > 0) { IERC20(esGmx).transferFrom(_sender, receiver, esGmxBalance); } IVester(gmxVester).transferStakeValues(_sender, 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( IVester(gmxVester).transferredAverageStakedAmounts(_receiver) == 0, "gmxVester.transferredAverageStakedAmounts > 0" ); require( IVester(gmxVester).transferredCumulativeRewards(_receiver) == 0, "gmxVester.transferredCumulativeRewards > 0" ); require(IERC20(gmxVester).balanceOf(_receiver) == 0, "gmxVester.balance > 0"); } function _compound(address _account) private { _compoundGmx(_account); } function _compoundGmx(address _account) private { uint256 esGmxAmount = IRewardTracker(stakedGmxTracker).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); emit StakeGmx(_account, _token, _amount); } function _unstakeGmx(address _account, address _token, uint256 _amount) private { require(_amount > 0, "invalid _amount"); // uint256 balance = IRewardTracker(stakedGmxTracker).stakedAmounts(_account); IRewardTracker(stakedGmxTracker).unstakeForAccount(_account, _token, _amount, _account); emit UnstakeGmx(_account, _token, _amount); } }