add contract for staking
This commit is contained in:
parent
ffe2788c6d
commit
2cde28e424
@ -83,14 +83,7 @@ contract TokenClaim is HasSignature, ReentrancyGuard, Pausable, TimeChecker {
|
|||||||
uint256 current = claimedBitMap[account][token];
|
uint256 current = claimedBitMap[account][token];
|
||||||
require(current & vals[1] == 0, "TokenClaim: condition check failed");
|
require(current & vals[1] == 0, "TokenClaim: condition check failed");
|
||||||
address user = _msgSender();
|
address user = _msgSender();
|
||||||
bytes32 criteriaMessageHash = getMessageHash(
|
bytes32 criteriaMessageHash = getMessageHash(user, account, token, _CACHED_THIS, _CACHED_CHAIN_ID, vals);
|
||||||
user,
|
|
||||||
account,
|
|
||||||
token,
|
|
||||||
_CACHED_THIS,
|
|
||||||
_CACHED_CHAIN_ID,
|
|
||||||
vals
|
|
||||||
);
|
|
||||||
checkSigner(verifier, criteriaMessageHash, signature);
|
checkSigner(verifier, criteriaMessageHash, signature);
|
||||||
_useSignature(signature);
|
_useSignature(signature);
|
||||||
claimedBitMap[account][token] = current | vals[1];
|
claimedBitMap[account][token] = current | vals[1];
|
||||||
@ -106,13 +99,7 @@ contract TokenClaim is HasSignature, ReentrancyGuard, Pausable, TimeChecker {
|
|||||||
uint256 _chainId,
|
uint256 _chainId,
|
||||||
uint256[4] calldata _vals
|
uint256[4] calldata _vals
|
||||||
) public pure returns (bytes32) {
|
) public pure returns (bytes32) {
|
||||||
bytes memory encoded = abi.encodePacked(
|
bytes memory encoded = abi.encodePacked(_user, _account, _token, _contract, _chainId);
|
||||||
_user,
|
|
||||||
_account,
|
|
||||||
_token,
|
|
||||||
_contract,
|
|
||||||
_chainId
|
|
||||||
);
|
|
||||||
for (uint256 i = 0; i < _vals.length; i++) {
|
for (uint256 i = 0; i < _vals.length; i++) {
|
||||||
encoded = bytes.concat(encoded, abi.encodePacked(_vals[i]));
|
encoded = bytes.concat(encoded, abi.encodePacked(_vals[i]));
|
||||||
}
|
}
|
||||||
|
19
contracts/core/Governable.sol
Normal file
19
contracts/core/Governable.sol
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
contract Governable {
|
||||||
|
address public gov;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
gov = msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyGov() {
|
||||||
|
require(msg.sender == gov, "Governable: forbidden");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGov(address _gov) external onlyGov {
|
||||||
|
gov = _gov;
|
||||||
|
}
|
||||||
|
}
|
9
contracts/interfaces/IMintable.sol
Normal file
9
contracts/interfaces/IMintable.sol
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
interface IMintable {
|
||||||
|
function isMinter(address _account) external returns (bool);
|
||||||
|
function setMinter(address _minter, bool _isActive) external;
|
||||||
|
function mint(address _account, uint256 _amount) external;
|
||||||
|
function burn(address _account, uint256 _amount) external;
|
||||||
|
}
|
91
contracts/staking/RewardDistributor.sol
Normal file
91
contracts/staking/RewardDistributor.sol
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// 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 {IRewardDistributor} from "./interfaces/IRewardDistributor.sol";
|
||||||
|
import {IRewardTracker} from "./interfaces/IRewardTracker.sol";
|
||||||
|
import {Governable} from "../core/Governable.sol";
|
||||||
|
|
||||||
|
contract RewardDistributor is IRewardDistributor, ReentrancyGuard, Governable {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
address public override rewardToken;
|
||||||
|
// 1个token, 在指定周期内, 每秒可获得的收益
|
||||||
|
// 比如收益是150%, 周期是1年, 那么 1.5 / (365 * 24 * 3600)
|
||||||
|
uint256 public override tokensPerInterval;
|
||||||
|
uint256 public lastDistributionTime;
|
||||||
|
// RewardTracker合约地址
|
||||||
|
address public rewardTracker;
|
||||||
|
|
||||||
|
address public admin;
|
||||||
|
|
||||||
|
event Distribute(uint256 amount);
|
||||||
|
event TokensPerIntervalChange(uint256 amount);
|
||||||
|
|
||||||
|
modifier onlyAdmin() {
|
||||||
|
require(msg.sender == admin, "RewardDistributor: forbidden");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(address _rewardToken, address _rewardTracker) {
|
||||||
|
rewardToken = _rewardToken;
|
||||||
|
rewardTracker = _rewardTracker;
|
||||||
|
admin = msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAdmin(address _admin) external onlyGov {
|
||||||
|
admin = _admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 updateLastDistributionTime() external onlyAdmin {
|
||||||
|
lastDistributionTime = block.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTokensPerInterval(uint256 _amount) external onlyAdmin {
|
||||||
|
require(lastDistributionTime != 0, "RewardDistributor: invalid lastDistributionTime");
|
||||||
|
IRewardTracker(rewardTracker).updateRewards();
|
||||||
|
tokensPerInterval = _amount;
|
||||||
|
emit TokensPerIntervalChange(_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pendingRewards() public view override returns (uint256) {
|
||||||
|
if (block.timestamp == lastDistributionTime) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 timeDiff = block.timestamp - lastDistributionTime;
|
||||||
|
return tokensPerInterval * timeDiff;
|
||||||
|
}
|
||||||
|
// changed
|
||||||
|
// 从RewardTracker合约中调用
|
||||||
|
// 由于我们是每个用户单独计算收益, 所以这里需要传入具体数量, 用于计算需要transafer的数量
|
||||||
|
function distribute(uint256 _amount) external override returns (uint256) {
|
||||||
|
require(msg.sender == rewardTracker, "RewardDistributor: invalid msg.sender");
|
||||||
|
uint256 amount = pendingRewards();
|
||||||
|
if (amount == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDistributionTime = block.timestamp;
|
||||||
|
|
||||||
|
uint256 tokenAmount = amount * _amount;
|
||||||
|
|
||||||
|
uint256 balance = IERC20(rewardToken).balanceOf(address(this));
|
||||||
|
if (tokenAmount > balance) {
|
||||||
|
tokenAmount = balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
IERC20(rewardToken).safeTransfer(msg.sender, tokenAmount);
|
||||||
|
|
||||||
|
emit Distribute(tokenAmount);
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
544
contracts/staking/RewardRouter.sol
Normal file
544
contracts/staking/RewardRouter.sol
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
// 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 {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 {
|
||||||
|
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;
|
||||||
|
|
||||||
|
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,
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
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, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_compound(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compoundForAccount(address _account) external nonReentrant onlyGov {
|
||||||
|
_compound(_account);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRewards(
|
||||||
|
bool _shouldClaimGmx,
|
||||||
|
bool _shouldStakeGmx,
|
||||||
|
bool _shouldClaimEsGmx,
|
||||||
|
bool _shouldStakeEsGmx,
|
||||||
|
bool _shouldStakeMultiplierPoints,
|
||||||
|
bool _shouldClaimWeth,
|
||||||
|
bool _shouldConvertWethToEth
|
||||||
|
) 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_shouldStakeGmx && gmxAmount > 0) {
|
||||||
|
_stakeGmx(account, account, gmx, gmxAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 esGmxAmount = 0;
|
||||||
|
if (_shouldClaimEsGmx) {
|
||||||
|
uint256 esGmxAmount0 = IRewardTracker(stakedGmxTracker).claimForAccount(account, account);
|
||||||
|
uint256 esGmxAmount1 = IRewardTracker(stakedGlpTracker).claimForAccount(account, account);
|
||||||
|
esGmxAmount = esGmxAmount0.add(esGmxAmount1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
for (uint256 i = 0; i < _accounts.length; i++) {
|
||||||
|
_compound(_accounts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
delete pendingReceivers[_sender];
|
||||||
|
|
||||||
|
_validateReceiver(receiver);
|
||||||
|
_compound(_sender);
|
||||||
|
|
||||||
|
uint256 stakedGmx = IRewardTracker(stakedGmxTracker).depositBalances(_sender, gmx);
|
||||||
|
if (stakedGmx > 0) {
|
||||||
|
_unstakeGmx(_sender, gmx, stakedGmx, false);
|
||||||
|
_stakeGmx(_sender, receiver, gmx, stakedGmx);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 stakedEsGmx = IRewardTracker(stakedGmxTracker).depositBalances(_sender, esGmx);
|
||||||
|
if (stakedEsGmx > 0) {
|
||||||
|
_unstakeGmx(_sender, esGmx, stakedEsGmx, false);
|
||||||
|
_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 {
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
329
contracts/staking/RewardTracker.sol
Normal file
329
contracts/staking/RewardTracker.sol
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
// 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 {IRewardDistributor} from "./interfaces/IRewardDistributor.sol";
|
||||||
|
import {IRewardTracker} from "./interfaces/IRewardTracker.sol";
|
||||||
|
import {Governable} from "../core/Governable.sol";
|
||||||
|
|
||||||
|
contract RewardTracker is IERC20, ReentrancyGuard, IRewardTracker, Governable {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
|
||||||
|
uint256 public constant PRECISION = 1e30;
|
||||||
|
|
||||||
|
bool public isInitialized;
|
||||||
|
|
||||||
|
string public name;
|
||||||
|
string public symbol;
|
||||||
|
uint8 public decimals = 18;
|
||||||
|
uint256 public override totalSupply;
|
||||||
|
mapping(address account => uint256 amount) public balances;
|
||||||
|
mapping(address owner => mapping(address spender => uint256 amount)) public allowances;
|
||||||
|
|
||||||
|
address public distributor;
|
||||||
|
mapping(address token => bool status) public isDepositToken;
|
||||||
|
// 用户存入的token数量
|
||||||
|
mapping(address account => mapping(address token => uint256 amount)) public override depositBalances;
|
||||||
|
// token总存入数量, 所有用户
|
||||||
|
mapping(address token => uint256 amount) public totalDepositSupply;
|
||||||
|
|
||||||
|
uint256 public cumulativeRewardPerToken;
|
||||||
|
// 用户stake的token数量
|
||||||
|
mapping(address account => uint256 amount) public override stakedAmounts;
|
||||||
|
mapping(address account => uint256 amount) public claimableReward;
|
||||||
|
mapping(address account => uint256 amount) public previousCumulatedRewardPerToken;
|
||||||
|
// 用户累计收益
|
||||||
|
mapping(address account => uint256 amount) public override cumulativeRewards;
|
||||||
|
// 平均
|
||||||
|
mapping(address account => uint256 amount) public override averageStakedAmounts;
|
||||||
|
|
||||||
|
bool public inPrivateTransferMode;
|
||||||
|
bool public inPrivateStakingMode;
|
||||||
|
bool public inPrivateClaimingMode;
|
||||||
|
mapping(address handler => bool status) public isHandler;
|
||||||
|
|
||||||
|
event Claim(address receiver, uint256 amount);
|
||||||
|
|
||||||
|
constructor(string memory _name, string memory _symbol) {
|
||||||
|
name = _name;
|
||||||
|
symbol = _symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize(address[] memory _depositTokens, address _distributor) external onlyGov {
|
||||||
|
require(!isInitialized, "RewardTracker: already initialized");
|
||||||
|
isInitialized = true;
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < _depositTokens.length; i++) {
|
||||||
|
address depositToken = _depositTokens[i];
|
||||||
|
isDepositToken[depositToken] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
distributor = _distributor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDepositToken(address _depositToken, bool _isDepositToken) external onlyGov {
|
||||||
|
isDepositToken[_depositToken] = _isDepositToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInPrivateTransferMode(bool _inPrivateTransferMode) external onlyGov {
|
||||||
|
inPrivateTransferMode = _inPrivateTransferMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInPrivateStakingMode(bool _inPrivateStakingMode) external onlyGov {
|
||||||
|
inPrivateStakingMode = _inPrivateStakingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInPrivateClaimingMode(bool _inPrivateClaimingMode) external onlyGov {
|
||||||
|
inPrivateClaimingMode = _inPrivateClaimingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHandler(address _handler, bool _isActive) external onlyGov {
|
||||||
|
isHandler[_handler] = _isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 balanceOf(address _account) external view override returns (uint256) {
|
||||||
|
return balances[_account];
|
||||||
|
}
|
||||||
|
|
||||||
|
function stake(address _depositToken, uint256 _amount) external override nonReentrant {
|
||||||
|
if (inPrivateStakingMode) {
|
||||||
|
revert("RewardTracker: action not enabled");
|
||||||
|
}
|
||||||
|
_stake(msg.sender, msg.sender, _depositToken, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stakeForAccount(
|
||||||
|
address _fundingAccount,
|
||||||
|
address _account,
|
||||||
|
address _depositToken,
|
||||||
|
uint256 _amount
|
||||||
|
) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
_stake(_fundingAccount, _account, _depositToken, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unstake(address _depositToken, uint256 _amount) external override nonReentrant {
|
||||||
|
if (inPrivateStakingMode) {
|
||||||
|
revert("RewardTracker: action not enabled");
|
||||||
|
}
|
||||||
|
_unstake(msg.sender, _depositToken, _amount, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unstakeForAccount(
|
||||||
|
address _account,
|
||||||
|
address _depositToken,
|
||||||
|
uint256 _amount,
|
||||||
|
address _receiver
|
||||||
|
) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
_unstake(_account, _depositToken, _amount, _receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transfer(address _recipient, uint256 _amount) external override returns (bool) {
|
||||||
|
_transfer(msg.sender, _recipient, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allowance(address _owner, address _spender) external view override returns (uint256) {
|
||||||
|
return allowances[_owner][_spender];
|
||||||
|
}
|
||||||
|
|
||||||
|
function approve(address _spender, uint256 _amount) external override returns (bool) {
|
||||||
|
_approve(msg.sender, _spender, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function transferFrom(address _sender, address _recipient, uint256 _amount) external override returns (bool) {
|
||||||
|
if (isHandler[msg.sender]) {
|
||||||
|
_transfer(_sender, _recipient, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
require(allowances[_sender][msg.sender] >= _amount, "RewardTracker: transfer amount exceeds allowance");
|
||||||
|
uint256 nextAllowance = allowances[_sender][msg.sender] - _amount;
|
||||||
|
_approve(_sender, msg.sender, nextAllowance);
|
||||||
|
_transfer(_sender, _recipient, _amount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokensPerInterval() external view override returns (uint256) {
|
||||||
|
return IRewardDistributor(distributor).tokensPerInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRewards() external override nonReentrant {
|
||||||
|
_updateRewards(address(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function claim(address _receiver) external override nonReentrant returns (uint256) {
|
||||||
|
if (inPrivateClaimingMode) {
|
||||||
|
revert("RewardTracker: action not enabled");
|
||||||
|
}
|
||||||
|
return _claim(msg.sender, _receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
function claimForAccount(address _account, address _receiver) external override nonReentrant returns (uint256) {
|
||||||
|
_validateHandler();
|
||||||
|
return _claim(_account, _receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// changed
|
||||||
|
function claimable(address _account) public view override returns (uint256) {
|
||||||
|
uint256 stakedAmount = stakedAmounts[_account];
|
||||||
|
if (stakedAmount == 0) {
|
||||||
|
return claimableReward[_account];
|
||||||
|
}
|
||||||
|
uint256 pendingRewards = IRewardDistributor(distributor).pendingRewards() * PRECISION;
|
||||||
|
// 这里是pendingRewards是总的奖励,需要除以总的token数量, 我们是固定的比例的收益, 所以不需要再除以总数
|
||||||
|
uint256 nextCumulativeRewardPerToken = cumulativeRewardPerToken + pendingRewards;
|
||||||
|
return
|
||||||
|
claimableReward[_account] +
|
||||||
|
(stakedAmount * (nextCumulativeRewardPerToken - previousCumulatedRewardPerToken[_account])) /
|
||||||
|
PRECISION;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewardToken() public view returns (address) {
|
||||||
|
return IRewardDistributor(distributor).rewardToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _claim(address _account, address _receiver) private returns (uint256) {
|
||||||
|
_updateRewards(_account);
|
||||||
|
|
||||||
|
uint256 tokenAmount = claimableReward[_account];
|
||||||
|
claimableReward[_account] = 0;
|
||||||
|
|
||||||
|
if (tokenAmount > 0) {
|
||||||
|
IERC20(rewardToken()).safeTransfer(_receiver, tokenAmount);
|
||||||
|
emit Claim(_account, tokenAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mint(address _account, uint256 _amount) internal {
|
||||||
|
require(_account != address(0), "RewardTracker: mint to the zero address");
|
||||||
|
|
||||||
|
totalSupply = totalSupply + _amount;
|
||||||
|
balances[_account] = balances[_account] + _amount;
|
||||||
|
|
||||||
|
emit Transfer(address(0), _account, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _burn(address _account, uint256 _amount) internal {
|
||||||
|
require(_account != address(0), "RewardTracker: burn from the zero address");
|
||||||
|
require(balances[_account] >= _amount, "RewardTracker: burn amount exceeds balance");
|
||||||
|
balances[_account] = balances[_account] - _amount;
|
||||||
|
totalSupply = totalSupply / _amount;
|
||||||
|
|
||||||
|
emit Transfer(_account, address(0), _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _transfer(address _sender, address _recipient, uint256 _amount) private {
|
||||||
|
require(_sender != address(0), "RewardTracker: transfer from the zero address");
|
||||||
|
require(_recipient != address(0), "RewardTracker: transfer to the zero address");
|
||||||
|
|
||||||
|
if (inPrivateTransferMode) {
|
||||||
|
_validateHandler();
|
||||||
|
}
|
||||||
|
require(balances[_sender] >= _amount, "RewardTracker: transfer amount exceeds balance");
|
||||||
|
balances[_sender] = balances[_sender] - _amount;
|
||||||
|
balances[_recipient] = balances[_recipient] + _amount;
|
||||||
|
|
||||||
|
emit Transfer(_sender, _recipient, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _approve(address _owner, address _spender, uint256 _amount) private {
|
||||||
|
require(_owner != address(0), "RewardTracker: approve from the zero address");
|
||||||
|
require(_spender != address(0), "RewardTracker: approve to the zero address");
|
||||||
|
|
||||||
|
allowances[_owner][_spender] = _amount;
|
||||||
|
|
||||||
|
emit Approval(_owner, _spender, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _validateHandler() private view {
|
||||||
|
require(isHandler[msg.sender], "RewardTracker: forbidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _stake(address _fundingAccount, address _account, address _depositToken, uint256 _amount) private {
|
||||||
|
require(_amount > 0, "RewardTracker: invalid _amount");
|
||||||
|
require(isDepositToken[_depositToken], "RewardTracker: invalid _depositToken");
|
||||||
|
|
||||||
|
IERC20(_depositToken).safeTransferFrom(_fundingAccount, address(this), _amount);
|
||||||
|
|
||||||
|
_updateRewards(_account);
|
||||||
|
|
||||||
|
stakedAmounts[_account] = stakedAmounts[_account] + _amount;
|
||||||
|
depositBalances[_account][_depositToken] = depositBalances[_account][_depositToken] + _amount;
|
||||||
|
totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken] + _amount;
|
||||||
|
|
||||||
|
_mint(_account, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unstake(address _account, address _depositToken, uint256 _amount, address _receiver) private {
|
||||||
|
require(_amount > 0, "RewardTracker: invalid _amount");
|
||||||
|
require(isDepositToken[_depositToken], "RewardTracker: invalid _depositToken");
|
||||||
|
|
||||||
|
_updateRewards(_account);
|
||||||
|
|
||||||
|
uint256 stakedAmount = stakedAmounts[_account];
|
||||||
|
require(stakedAmounts[_account] >= _amount, "RewardTracker: _amount exceeds stakedAmount");
|
||||||
|
|
||||||
|
stakedAmounts[_account] = stakedAmount - _amount;
|
||||||
|
|
||||||
|
uint256 depositBalance = depositBalances[_account][_depositToken];
|
||||||
|
require(depositBalance >= _amount, "RewardTracker: _amount exceeds depositBalance");
|
||||||
|
depositBalances[_account][_depositToken] = depositBalance - _amount;
|
||||||
|
totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken] - _amount;
|
||||||
|
|
||||||
|
_burn(_account, _amount);
|
||||||
|
IERC20(_depositToken).safeTransfer(_receiver, _amount);
|
||||||
|
}
|
||||||
|
// changed
|
||||||
|
function _updateRewards(address _account) private {
|
||||||
|
uint256 supply = totalSupply;
|
||||||
|
uint256 blockReward = IRewardDistributor(distributor).distribute(supply);
|
||||||
|
|
||||||
|
|
||||||
|
uint256 _cumulativeRewardPerToken = cumulativeRewardPerToken;
|
||||||
|
if (supply > 0 && blockReward > 0) {
|
||||||
|
_cumulativeRewardPerToken = _cumulativeRewardPerToken + blockReward * PRECISION;
|
||||||
|
cumulativeRewardPerToken = _cumulativeRewardPerToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cumulativeRewardPerToken can only increase
|
||||||
|
// so if cumulativeRewardPerToken is zero, it means there are no rewards yet
|
||||||
|
if (_cumulativeRewardPerToken == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_account != address(0)) {
|
||||||
|
uint256 stakedAmount = stakedAmounts[_account];
|
||||||
|
uint256 accountReward = (stakedAmount * (_cumulativeRewardPerToken - previousCumulatedRewardPerToken[_account])) /
|
||||||
|
PRECISION;
|
||||||
|
uint256 _claimableReward = claimableReward[_account] + accountReward;
|
||||||
|
|
||||||
|
claimableReward[_account] = _claimableReward;
|
||||||
|
previousCumulatedRewardPerToken[_account] = _cumulativeRewardPerToken;
|
||||||
|
|
||||||
|
if (_claimableReward > 0 && stakedAmounts[_account] > 0) {
|
||||||
|
uint256 nextCumulativeReward = cumulativeRewards[_account] + accountReward;
|
||||||
|
|
||||||
|
averageStakedAmounts[_account] =
|
||||||
|
(averageStakedAmounts[_account] * cumulativeRewards[_account]) /
|
||||||
|
nextCumulativeReward +
|
||||||
|
(stakedAmount * accountReward) /
|
||||||
|
nextCumulativeReward;
|
||||||
|
|
||||||
|
cumulativeRewards[_account] = nextCumulativeReward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
387
contracts/staking/Vester.sol
Normal file
387
contracts/staking/Vester.sol
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
// 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 {IVester} from "./interfaces/IVester.sol";
|
||||||
|
import {IRewardTracker} from "./interfaces/IRewardTracker.sol";
|
||||||
|
import {Governable} from "../core/Governable.sol";
|
||||||
|
import {IMintable} from "../interfaces/IMintable.sol";
|
||||||
|
|
||||||
|
contract Vester is IVester, IERC20, ReentrancyGuard, Governable {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
string public name;
|
||||||
|
string public symbol;
|
||||||
|
uint8 public decimals = 18;
|
||||||
|
// remove?
|
||||||
|
uint256 public vestingDuration;
|
||||||
|
address public esToken;
|
||||||
|
address public pairToken;
|
||||||
|
address public claimableToken;
|
||||||
|
|
||||||
|
address public override rewardTracker;
|
||||||
|
|
||||||
|
uint256 public override totalSupply;
|
||||||
|
uint256 public pairSupply;
|
||||||
|
|
||||||
|
mapping(address account => uint256 amount) public balances;
|
||||||
|
mapping(address account => uint256 amount) public override pairAmounts;
|
||||||
|
// 累积claim的数量, 每次计算后, 将可claim的数量从balances中转移到cumulativeClaimAmounts中,
|
||||||
|
// 在claim和deposit时, 会更新cumulativeClaimAmounts
|
||||||
|
// 然后减去claimedAmounts, 就是可claim的数量
|
||||||
|
// claim以后, cumulativeClaimAmounts的值和claimedAmounts的值都会增加, 且相等
|
||||||
|
mapping(address account => uint256 amount) public override cumulativeClaimAmounts;
|
||||||
|
// 已经claim了的数量, 只有在claim以后才会更新
|
||||||
|
mapping(address account => uint256 amount) public override claimedAmounts;
|
||||||
|
// 最后claim的时间
|
||||||
|
mapping(address account => uint256 time) public lastVestingTimes;
|
||||||
|
|
||||||
|
mapping(address account => uint256 amount) public override transferredAverageStakedAmounts;
|
||||||
|
mapping(address account => uint256 amount) public override transferredCumulativeRewards;
|
||||||
|
mapping(address account => uint256 amount) public override cumulativeRewardDeductions;
|
||||||
|
mapping(address account => uint256 amount) public override bonusRewards;
|
||||||
|
|
||||||
|
mapping(address handler => bool status) public isHandler;
|
||||||
|
|
||||||
|
event Claim(address receiver, uint256 amount);
|
||||||
|
event Deposit(address account, uint256 amount);
|
||||||
|
event Withdraw(address account, uint256 claimedAmount, uint256 balance);
|
||||||
|
event PairTransfer(address indexed from, address indexed to, uint256 value);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
string memory _name,
|
||||||
|
string memory _symbol,
|
||||||
|
uint256 _vestingDuration,
|
||||||
|
address _esToken,
|
||||||
|
address _pairToken,
|
||||||
|
address _claimableToken,
|
||||||
|
address _rewardTracker
|
||||||
|
) {
|
||||||
|
name = _name;
|
||||||
|
symbol = _symbol;
|
||||||
|
|
||||||
|
vestingDuration = _vestingDuration;
|
||||||
|
|
||||||
|
esToken = _esToken;
|
||||||
|
pairToken = _pairToken;
|
||||||
|
claimableToken = _claimableToken;
|
||||||
|
|
||||||
|
rewardTracker = _rewardTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHandler(address _handler, bool _isActive) external onlyGov {
|
||||||
|
isHandler[_handler] = _isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deposit(uint256 _amount) external nonReentrant {
|
||||||
|
_deposit(msg.sender, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function depositForAccount(address _account, uint256 _amount) external nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
_deposit(_account, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function claim() external nonReentrant returns (uint256) {
|
||||||
|
return _claim(msg.sender, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function claimForAccount(address _account, address _receiver) external override nonReentrant returns (uint256) {
|
||||||
|
_validateHandler();
|
||||||
|
return _claim(_account, _receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 withdraw() external nonReentrant {
|
||||||
|
address account = msg.sender;
|
||||||
|
address _receiver = account;
|
||||||
|
_claim(account, _receiver);
|
||||||
|
|
||||||
|
uint256 claimedAmount = cumulativeClaimAmounts[account];
|
||||||
|
uint256 balance = balances[account];
|
||||||
|
uint256 totalVested = balance + claimedAmount;
|
||||||
|
require(totalVested > 0, "Vester: vested amount is zero");
|
||||||
|
|
||||||
|
if (hasPairToken()) {
|
||||||
|
uint256 pairAmount = pairAmounts[account];
|
||||||
|
_burnPair(account, pairAmount);
|
||||||
|
IERC20(pairToken).safeTransfer(_receiver, pairAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
IERC20(esToken).safeTransfer(_receiver, balance);
|
||||||
|
_burn(account, balance);
|
||||||
|
|
||||||
|
delete cumulativeClaimAmounts[account];
|
||||||
|
delete claimedAmounts[account];
|
||||||
|
delete lastVestingTimes[account];
|
||||||
|
|
||||||
|
emit Withdraw(account, claimedAmount, balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transferStakeValues(address _sender, address _receiver) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
|
||||||
|
transferredAverageStakedAmounts[_receiver] = getCombinedAverageStakedAmount(_sender);
|
||||||
|
transferredAverageStakedAmounts[_sender] = 0;
|
||||||
|
|
||||||
|
uint256 transferredCumulativeReward = transferredCumulativeRewards[_sender];
|
||||||
|
uint256 cumulativeReward = hasRewardTracker() ? IRewardTracker(rewardTracker).cumulativeRewards(_sender) : 0;
|
||||||
|
|
||||||
|
transferredCumulativeRewards[_receiver] = transferredCumulativeReward + cumulativeReward;
|
||||||
|
cumulativeRewardDeductions[_sender] = cumulativeReward;
|
||||||
|
transferredCumulativeRewards[_sender] = 0;
|
||||||
|
|
||||||
|
bonusRewards[_receiver] = bonusRewards[_sender];
|
||||||
|
bonusRewards[_sender] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTransferredAverageStakedAmounts(address _account, uint256 _amount) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
transferredAverageStakedAmounts[_account] = _amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTransferredCumulativeRewards(address _account, uint256 _amount) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
transferredCumulativeRewards[_account] = _amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCumulativeRewardDeductions(address _account, uint256 _amount) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
cumulativeRewardDeductions[_account] = _amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBonusRewards(address _account, uint256 _amount) external override nonReentrant {
|
||||||
|
_validateHandler();
|
||||||
|
bonusRewards[_account] = _amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getMaxVestableAmount(address _account) public view override returns (uint256) {
|
||||||
|
uint256 transferredCumulativeReward = transferredCumulativeRewards[_account];
|
||||||
|
uint256 bonusReward = bonusRewards[_account];
|
||||||
|
uint256 maxVestableAmount = transferredCumulativeReward + bonusReward;
|
||||||
|
|
||||||
|
if (hasRewardTracker()) {
|
||||||
|
uint256 cumulativeReward = IRewardTracker(rewardTracker).cumulativeRewards(_account);
|
||||||
|
maxVestableAmount = maxVestableAmount + cumulativeReward;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 cumulativeRewardDeduction = cumulativeRewardDeductions[_account];
|
||||||
|
|
||||||
|
if (maxVestableAmount < cumulativeRewardDeduction) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxVestableAmount - cumulativeRewardDeduction;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCombinedAverageStakedAmount(address _account) public view override returns (uint256) {
|
||||||
|
if (!hasRewardTracker()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 cumulativeReward = IRewardTracker(rewardTracker).cumulativeRewards(_account);
|
||||||
|
uint256 transferredCumulativeReward = transferredCumulativeRewards[_account];
|
||||||
|
uint256 totalCumulativeReward = cumulativeReward + transferredCumulativeReward;
|
||||||
|
if (totalCumulativeReward == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 averageStakedAmount = IRewardTracker(rewardTracker).averageStakedAmounts(_account);
|
||||||
|
uint256 transferredAverageStakedAmount = transferredAverageStakedAmounts[_account];
|
||||||
|
|
||||||
|
return
|
||||||
|
(averageStakedAmount * cumulativeReward) /
|
||||||
|
totalCumulativeReward +
|
||||||
|
(transferredAverageStakedAmount * transferredCumulativeReward) /
|
||||||
|
totalCumulativeReward;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPairAmount(address _account, uint256 _esAmount) public view returns (uint256) {
|
||||||
|
if (!hasRewardTracker()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 combinedAverageStakedAmount = getCombinedAverageStakedAmount(_account);
|
||||||
|
if (combinedAverageStakedAmount == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 maxVestableAmount = getMaxVestableAmount(_account);
|
||||||
|
if (maxVestableAmount == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_esAmount * combinedAverageStakedAmount) / maxVestableAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRewardTracker() public view returns (bool) {
|
||||||
|
return rewardTracker != address(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPairToken() public view returns (bool) {
|
||||||
|
return pairToken != address(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalVested(address _account) public view returns (uint256) {
|
||||||
|
return balances[_account] + cumulativeClaimAmounts[_account];
|
||||||
|
}
|
||||||
|
|
||||||
|
function balanceOf(address _account) public view override returns (uint256) {
|
||||||
|
return balances[_account];
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty implementation, tokens are non-transferrable
|
||||||
|
function transfer(address /* recipient */, uint256 /* amount */) public virtual override returns (bool) {
|
||||||
|
revert("Vester: non-transferrable");
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty implementation, tokens are non-transferrable
|
||||||
|
function allowance(address /* owner */, address /* spender */) public view virtual override returns (uint256) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty implementation, tokens are non-transferrable
|
||||||
|
function approve(address /* spender */, uint256 /* amount */) public virtual override returns (bool) {
|
||||||
|
revert("Vester: non-transferrable");
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty implementation, tokens are non-transferrable
|
||||||
|
function transferFrom(
|
||||||
|
address /* sender */,
|
||||||
|
address /* recipient */,
|
||||||
|
uint256 /* amount */
|
||||||
|
) public virtual override returns (bool) {
|
||||||
|
revert("Vester: non-transferrable");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVestedAmount(address _account) public view override returns (uint256) {
|
||||||
|
uint256 balance = balances[_account];
|
||||||
|
uint256 cumulativeClaimAmount = cumulativeClaimAmounts[_account];
|
||||||
|
return balance + cumulativeClaimAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mint(address _account, uint256 _amount) private {
|
||||||
|
require(_account != address(0), "Vester: mint to the zero address");
|
||||||
|
|
||||||
|
totalSupply = totalSupply + _amount;
|
||||||
|
balances[_account] = balances[_account] + _amount;
|
||||||
|
|
||||||
|
emit Transfer(address(0), _account, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mintPair(address _account, uint256 _amount) private {
|
||||||
|
require(_account != address(0), "Vester: mint to the zero address");
|
||||||
|
|
||||||
|
pairSupply = pairSupply + _amount;
|
||||||
|
pairAmounts[_account] = pairAmounts[_account] + _amount;
|
||||||
|
|
||||||
|
emit PairTransfer(address(0), _account, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _burn(address _account, uint256 _amount) private {
|
||||||
|
require(_account != address(0), "Vester: burn from the zero address");
|
||||||
|
require(balances[_account] >= _amount, "Vester: balance is not enough");
|
||||||
|
balances[_account] = balances[_account] - _amount;
|
||||||
|
totalSupply = totalSupply - _amount;
|
||||||
|
|
||||||
|
emit Transfer(_account, address(0), _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _burnPair(address _account, uint256 _amount) private {
|
||||||
|
require(_account != address(0), "Vester: burn from the zero address");
|
||||||
|
require(pairAmounts[_account] >= _amount, "Vester: balance is not enough");
|
||||||
|
pairAmounts[_account] = pairAmounts[_account] - _amount;
|
||||||
|
pairSupply = pairSupply - _amount;
|
||||||
|
|
||||||
|
emit PairTransfer(_account, address(0), _amount);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @dev Deposit ES tokens to the contract
|
||||||
|
* 将esToken transfer到合约, 更新cumulativeClaimAmounts, lastVestingTimes, balances
|
||||||
|
*/
|
||||||
|
function _deposit(address _account, uint256 _amount) private {
|
||||||
|
require(_amount > 0, "Vester: invalid _amount");
|
||||||
|
|
||||||
|
_updateVesting(_account);
|
||||||
|
|
||||||
|
IERC20(esToken).safeTransferFrom(_account, address(this), _amount);
|
||||||
|
|
||||||
|
_mint(_account, _amount);
|
||||||
|
|
||||||
|
if (hasPairToken()) {
|
||||||
|
uint256 pairAmount = pairAmounts[_account];
|
||||||
|
uint256 nextPairAmount = getPairAmount(_account, balances[_account]);
|
||||||
|
if (nextPairAmount > pairAmount) {
|
||||||
|
uint256 pairAmountDiff = nextPairAmount - pairAmount;
|
||||||
|
IERC20(pairToken).safeTransferFrom(_account, address(this), pairAmountDiff);
|
||||||
|
_mintPair(_account, pairAmountDiff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 maxAmount = getMaxVestableAmount(_account);
|
||||||
|
require(getTotalVested(_account) <= maxAmount, "Vester: max vestable amount exceeded");
|
||||||
|
|
||||||
|
emit Deposit(_account, _amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updateVesting(address _account) private {
|
||||||
|
uint256 amount = _getNextClaimableAmount(_account);
|
||||||
|
lastVestingTimes[_account] = block.timestamp;
|
||||||
|
|
||||||
|
if (amount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer claimableAmount from balances to cumulativeClaimAmounts
|
||||||
|
_burn(_account, amount);
|
||||||
|
cumulativeClaimAmounts[_account] = cumulativeClaimAmounts[_account] + amount;
|
||||||
|
|
||||||
|
IMintable(esToken).burn(address(this), amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getNextClaimableAmount(address _account) private view returns (uint256) {
|
||||||
|
uint256 timeDiff = block.timestamp - lastVestingTimes[_account];
|
||||||
|
|
||||||
|
uint256 balance = balances[_account];
|
||||||
|
if (balance == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// vestedAmount = balance + cumulativeClaimAmount + 最后一次vesting 至今的可提取金额
|
||||||
|
uint256 vestedAmount = getVestedAmount(_account);
|
||||||
|
uint256 claimableAmount = (vestedAmount * timeDiff) / vestingDuration;
|
||||||
|
// 如果claimableAmount < balance, 返回claimableAmount
|
||||||
|
// 否则返回balance, 保证claimableAmount <= balance
|
||||||
|
if (claimableAmount < balance) {
|
||||||
|
return claimableAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function claimable(address _account) public view override returns (uint256) {
|
||||||
|
uint256 amount = cumulativeClaimAmounts[_account] - claimedAmounts[_account];
|
||||||
|
uint256 nextClaimable = _getNextClaimableAmount(_account);
|
||||||
|
return amount + nextClaimable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _claim(address _account, address _receiver) private returns (uint256) {
|
||||||
|
_updateVesting(_account);
|
||||||
|
uint256 amount = claimable(_account);
|
||||||
|
claimedAmounts[_account] = claimedAmounts[_account] + amount;
|
||||||
|
IERC20(claimableToken).safeTransfer(_receiver, amount);
|
||||||
|
emit Claim(_account, amount);
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _validateHandler() private view {
|
||||||
|
require(isHandler[msg.sender], "Vester: forbidden");
|
||||||
|
}
|
||||||
|
}
|
10
contracts/staking/interfaces/IRewardDistributor.sol
Normal file
10
contracts/staking/interfaces/IRewardDistributor.sol
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
interface IRewardDistributor {
|
||||||
|
function rewardToken() external view returns (address);
|
||||||
|
function tokensPerInterval() external view returns (uint256);
|
||||||
|
function pendingRewards() external view returns (uint256);
|
||||||
|
// changed
|
||||||
|
function distribute(uint256 _amount) external returns (uint256);
|
||||||
|
}
|
7
contracts/staking/interfaces/IRewardRouter.sol
Normal file
7
contracts/staking/interfaces/IRewardRouter.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
interface IRewardRouter {
|
||||||
|
function feeGlpTracker() external view returns (address);
|
||||||
|
function stakedGlpTracker() external view returns (address);
|
||||||
|
}
|
18
contracts/staking/interfaces/IRewardTracker.sol
Normal file
18
contracts/staking/interfaces/IRewardTracker.sol
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
interface IRewardTracker {
|
||||||
|
function depositBalances(address _account, address _depositToken) external view returns (uint256);
|
||||||
|
function stakedAmounts(address _account) external view returns (uint256);
|
||||||
|
function updateRewards() external;
|
||||||
|
function stake(address _depositToken, uint256 _amount) external;
|
||||||
|
function stakeForAccount(address _fundingAccount, address _account, address _depositToken, uint256 _amount) external;
|
||||||
|
function unstake(address _depositToken, uint256 _amount) external;
|
||||||
|
function unstakeForAccount(address _account, address _depositToken, uint256 _amount, address _receiver) external;
|
||||||
|
function tokensPerInterval() external view returns (uint256);
|
||||||
|
function claim(address _receiver) external returns (uint256);
|
||||||
|
function claimForAccount(address _account, address _receiver) external returns (uint256);
|
||||||
|
function claimable(address _account) external view returns (uint256);
|
||||||
|
function averageStakedAmounts(address _account) external view returns (uint256);
|
||||||
|
function cumulativeRewards(address _account) external view returns (uint256);
|
||||||
|
}
|
27
contracts/staking/interfaces/IVester.sol
Normal file
27
contracts/staking/interfaces/IVester.sol
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
interface IVester {
|
||||||
|
function rewardTracker() external view returns (address);
|
||||||
|
|
||||||
|
function claimForAccount(address _account, address _receiver) external returns (uint256);
|
||||||
|
|
||||||
|
function claimable(address _account) external view returns (uint256);
|
||||||
|
function cumulativeClaimAmounts(address _account) external view returns (uint256);
|
||||||
|
function claimedAmounts(address _account) external view returns (uint256);
|
||||||
|
function pairAmounts(address _account) external view returns (uint256);
|
||||||
|
function getVestedAmount(address _account) external view returns (uint256);
|
||||||
|
function transferredAverageStakedAmounts(address _account) external view returns (uint256);
|
||||||
|
function transferredCumulativeRewards(address _account) external view returns (uint256);
|
||||||
|
function cumulativeRewardDeductions(address _account) external view returns (uint256);
|
||||||
|
function bonusRewards(address _account) external view returns (uint256);
|
||||||
|
|
||||||
|
function transferStakeValues(address _sender, address _receiver) external;
|
||||||
|
function setTransferredAverageStakedAmounts(address _account, uint256 _amount) external;
|
||||||
|
function setTransferredCumulativeRewards(address _account, uint256 _amount) external;
|
||||||
|
function setCumulativeRewardDeductions(address _account, uint256 _amount) external;
|
||||||
|
function setBonusRewards(address _account, uint256 _amount) external;
|
||||||
|
|
||||||
|
function getMaxVestableAmount(address _account) external view returns (uint256);
|
||||||
|
function getCombinedAverageStakedAmount(address _account) external view returns (uint256);
|
||||||
|
}
|
8
contracts/test/TestSth.sol
Normal file
8
contracts/test/TestSth.sol
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: Apache 2.0
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
contract TestSth {
|
||||||
|
function test(uint256 a, uint256 b) public view returns (uint256) {
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
}
|
@ -4,5 +4,11 @@
|
|||||||
"type": "logic",
|
"type": "logic",
|
||||||
"json": "assets/contracts/GameItemMall.json",
|
"json": "assets/contracts/GameItemMall.json",
|
||||||
"address": "0x3A85cA6615953c683826FBe54fA5e2a770ee8bA2"
|
"address": "0x3A85cA6615953c683826FBe54fA5e2a770ee8bA2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CEC",
|
||||||
|
"type": "erc20",
|
||||||
|
"json": "assets/contracts/FT.json",
|
||||||
|
"address": "0x111111267109489dc6f350608d5113B10c0C5cd7"
|
||||||
}
|
}
|
||||||
]
|
]
|
37
test/testTest.ts
Normal file
37
test/testTest.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import hre from "hardhat";
|
||||||
|
import {
|
||||||
|
getBytes,
|
||||||
|
solidityPackedKeccak256,
|
||||||
|
} from 'ethers'
|
||||||
|
import {
|
||||||
|
loadFixture,
|
||||||
|
} from "@nomicfoundation/hardhat-toolbox/network-helpers";
|
||||||
|
|
||||||
|
describe('Test', function() {
|
||||||
|
async function deployOneContract() {
|
||||||
|
// Contracts are deployed using the first signer/account by default
|
||||||
|
const [owner, otherAccount] = await hre.ethers.getSigners();
|
||||||
|
const verifier = owner.address;
|
||||||
|
|
||||||
|
const TestBit = await hre.ethers.getContractFactory("TestSth");
|
||||||
|
|
||||||
|
const contract = await TestBit.deploy( );
|
||||||
|
const chainId = hre.network.config.chainId
|
||||||
|
|
||||||
|
return { contract, owner, otherAccount, verifier, chainId };
|
||||||
|
}
|
||||||
|
describe("Deployment", function () {
|
||||||
|
it('should deploy TestSth', async function() {
|
||||||
|
const { contract } = await loadFixture(deployOneContract);
|
||||||
|
|
||||||
|
const result = await contract.test(2, 3);
|
||||||
|
console.log(result);
|
||||||
|
expect(result).to.equal(1);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user