2024-08-23 17:06:50 +08:00

388 lines
14 KiB
Solidity

// 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");
}
}