首先我们需要理清几个代币GMXGMX是GMX平台的治理和实用代币,可以参与治理,质押它可以通过直接购买,或者通过一段时间后esGMX解锁GLPGMX平台的流动性提供者代币,是为平台上的交易者提供流动性的工具,通过增加流动性来获得GLP奖励esGMXesGMX是一种托管代币
首先我们需要理清几个代币 GMX GMX 是 GMX 平台的 治理和实用代币,可以参与治理,质押 它可以通过直接购买,或者通过一段时间后esGMX解锁
GLP GMX 平台的 流动性提供者代币,是为平台上的交易者提供流动性的工具,通过增加流动性来获得GLP奖励
esGMX esGMX 是一种托管代币奖励,质押 GMX 或 GLP 可获得。它可以继续质押获得收益,或在一段时间后解锁为实际 GMX
bnGMX bnGMX 是额外的奖励代币,用于提升用户的质押奖励收益,但不能交易或解锁为 GMX
有了这几个基础概念之后,我们接下来结合着源码来进行解析
首先我们来看质押的逻辑 质押流程 跟我们常见的质押处理逻辑不一样,我们通常的质押处理逻辑是直接将代币质押到一个合约中,然后直接在该合约计算奖励和份额。但是GMX平台的质押进行了三层的传递。 三者分别代表不同的追踪合约 stakeGMXTracker是基础治理代币GMX的追踪合约 bonusGmxTracker是奖励代币或激励代币的追踪合约 feeGmxTracker 是费用相关的奖励的追踪合约 但是这三者其实都是RewardTracker的实现 让我们看看它的源码 stakeGMX
function _stakeGmx(address _fundingAccount, address _account, address _token, uint256 _amount) private {
require(_amount > 0, "RewardRouter: invalid _amount");
// 将 _amount 的 GMX 代币从 _fundingAccount 质押到 stakedGmxTracker 合约中,为 _account 用户质押
IRewardTracker(stakedGmxTracker).stakeForAccount(_fundingAccount, _account, _token, _amount);
// 将 _amount 的 stakedGmxTracker 代币(由上一操作质押的结果)质押到 bonusGmxTracker 合约中,为 _account 用户质押。
IRewardTracker(bonusGmxTracker).stakeForAccount(_account, _account, stakedGmxTracker, _amount);
// 将 _amount 的 bonusGmxTracker 代币 (由上一操作质押的结果) 质押到 bofeeGmxTracker 合约中,为 _account 用户质押。
IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bonusGmxTracker, _amount);
emit StakeGmx(_account, _amount);
}
逻辑很清晰,就是三层的传递 我们来看看,RewardTracker里面的实现
function stakeForAccount(address _fundingAccount, address _account, address _depositToken, uint256 _amount) external override nonReentrant {
// 校验发送者的资格
_validateHandler();
// 进行质押保存
_stake(_fundingAccount, _account, _depositToken, _amount);
}
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);
// 然后增加stakeAmounts
stakedAmounts[_account] = stakedAmounts[_account].add(_amount);
// 增加 depositBalances
depositBalances[_account][_depositToken] = depositBalances[_account][_depositToken].add(_amount);
// 增加总存款数
totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken].add(_amount);
// mint amount对应的数量
_mint(_account, _amount);
}
function _updateRewards(address _account) private {
uint256 blockReward = IRewardDistributor(distributor).distribute();
// 质押代币的总量
uint256 supply = totalSupply;
// 更新每个代币的累积奖励
uint256 _cumulativeRewardPerToken = cumulativeRewardPerToken;
if (supply > 0 && blockReward > 0) {
_cumulativeRewardPerToken = _cumulativeRewardPerToken.add(blockReward.mul(PRECISION).div(supply));
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.mul(_cumulativeRewardPerToken.sub(previousCumulatedRewardPerToken[_account])).div(PRECISION);
// 更新用户的可领取奖励 claimableReward。
uint256 _claimableReward = claimableReward[_account].add(accountReward);
claimableReward[_account] = _claimableReward;
previousCumulatedRewardPerToken[_account] = _cumulativeRewardPerToken;
// 如果用户有可领取的奖励且他们的质押金额大于零,则计算用户的累计奖励
if (_claimableReward > 0 && stakedAmounts[_account] > 0) {
uint256 nextCumulativeReward = cumulativeRewards[_account].add(accountReward);
// 更新账户的平均质押金额
averageStakedAmounts[_account] = averageStakedAmounts[_account].mul(cumulativeRewards[_account]).div(nextCumulativeReward)
.add(stakedAmount.mul(accountReward).div(nextCumulativeReward));
// 更新累积奖励金额
cumulativeRewards[_account] = nextCumulativeReward;
}
}
}
可以看到实际的质押逻辑,和普通的质押没有什么差别,都是代币转移,更新奖励状态,更新质押,存款数量。在此基础上增加了_mint 自身的操作。 看到这里,大家一定会有疑问,为什么好端端的要抽象成三层呢,好处是啥? 接下来我们结合着另外一个场景来看一下 _compoundGmx方法
function _compoundGmx(address _account) private {
// 从stakedGmxTracker提取出esGmx,可以对esGmx进行单独质押
uint256 esGmxAmount = IRewardTracker(stakedGmxTracker).claimForAccount(_account, _account);
if (esGmxAmount > 0) {
_stakeGmx(_account, _account, esGmx, esGmxAmount);
}
// 从bonusGmxTracker 中提取出 bnGmx
uint256 bnGmxAmount = IRewardTracker(bonusGmxTracker).claimForAccount(_account, _account);
if (bnGmxAmount > 0) {
// 将bnGmxAmount 作为奖励份额,质押到feeGmxTracker,(它自身不能被解锁)
IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bnGmx, bnGmxAmount);
}
}
从这一步上,我们就可以看出它设计的精妙了,esGmx作为它的奖励代币,可以进行二次质押。bnGmx作为额外奖励,不能进行解锁,也不能进行交换,但是可以增加代币的奖励份额。因此只需要stake到feeGmxTracker里面即可。这样大大增强了代码的可读性已经扩展性
接下来我们看一下解质押的逻辑,刚好和质押的流程相反 解质押流程
可以看到流程完全想法,调整了一下。 同时也可以看到单独对bnGmx进行了处理 结合着源码一起来看
function _unstakeGmx(address _account, address _token, uint256 _amount) private {
require(_amount > 0, "RewardRouter: invalid _amount");
uint256 balance = IRewardTracker(stakedGmxTracker).stakedAmounts(_account);
// 从 feeGmxTracker 中解质押 _amount 的 GMX,并转移到 bonusGmxTracker。
IRewardTracker(feeGmxTracker).unstakeForAccount(_account, bonusGmxTracker, _amount, _account);
// 从 bonusGmxTracker 中解质押 _amount 的GMX,并转移到 stakedGmxTracker
IRewardTracker(bonusGmxTracker).unstakeForAccount(_account, stakedGmxTracker, _amount, _account);
// 从 stakedGmxTracker 中解质押 _amount 的GMX,并转移到 _token 中
IRewardTracker(stakedGmxTracker).unstakeForAccount(_account, _token, _amount, _account);
// 领取 bonusGmxTracker 中的奖励(bnGmx),如果有奖励则将这些奖励在 feeGmxTracker 中重新质押。
uint256 bnGmxAmount = IRewardTracker(bonusGmxTracker).claimForAccount(_account, _account);
if (bnGmxAmount > 0) {
IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bnGmx, bnGmxAmount);
}
// 计算需要减少的 bnGmx 数量,解质押相应的 bnGmx 数量并进行销毁,以反映减少的质押量。
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);
}
emit UnstakeGmx(_account, _amount);
}
第一步 校验解质押的数量必须大于0 第二步 从 feeGmxTracker 中解质押 _amount 的 GMX,并转移到 bonusGmxTracker 第三步 从 bonusGmxTracker 中解质押 _amount 的GMX,并转移到 stakedGmxTracker 第四步 从 stakedGmxTracker 中解质押 _amount 的GMX,并转移到 _token 中 第五步 领取 bonusGmxTracker 中的奖励(bnGmx),如果有奖励则将这些奖励在 feeGmxTracker 中重新质押。 第六步 计算需要减少的 bnGmx 数量,解质押相应的 bnGmx 数量并进行销毁,以反映减少的质押量。 整体上的逻辑很清晰 接下来,看一下unstakeForAccount逻辑
function unstakeForAccount(address _account, address _depositToken, uint256 _amount, address _receiver) external override nonReentrant {
_validateHandler();
_unstake(_account, _depositToken, _amount, _receiver);
}
核心处理依然在_unstake里面
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 的奖励状态。这是因为解质押可能影响到用户的奖励计算,需要确保奖励数据的最新性。
_updateRewards(_account);
uint256 stakedAmount = stakedAmounts[_account];
// 确保解质押的数量不超过用户的当前质押量。
require(stakedAmounts[_account] >= _amount, "RewardTracker: _amount exceeds stakedAmount");
// 从用户的质押量中减去解质押的数量。
stakedAmounts[_account] = stakedAmount.sub(_amount);
uint256 depositBalance = depositBalances[_account][_depositToken];
// 确保用户的存款余额能大于解质押的两
require(depositBalance >= _amount, "RewardTracker: _amount exceeds depositBalance");
depositBalances[_account][_depositToken] = depositBalance.sub(_amount);
totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken].sub(_amount);
// 销毁_amount数量的份额
_burn(_account, _amount);
// 进行代币转移
IERC20(_depositToken).safeTransfer(_receiver, _amount);
}
第一步 依然校验数字 和 代币的合法性 第二步 调整奖励状态 第三步 校验确保解质押的数量不超过用户的当前质押量。 第四步 确保用户的存款余额能大于解质押的量 第五步 减少总存款量 第六步 销毁_amount数量的份额 第七步 进行代币转移
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!