GMX 源码解析五,质押和解质押逻辑

  • Leo
  • 更新于 2024-09-07 17:18
  • 阅读 645

首先我们需要理清几个代币GMXGMX是GMX平台的治理和实用代币,可以参与治理,质押它可以通过直接购买,或者通过一段时间后esGMX解锁GLPGMX平台的流动性提供者代币,是为平台上的交易者提供流动性的工具,通过增加流动性来获得GLP奖励esGMXesGMX是一种托管代币

首先我们需要理清几个代币 GMX GMX 是 GMX 平台的 治理和实用代币,可以参与治理,质押 它可以通过直接购买,或者通过一段时间后esGMX解锁

GLP GMX 平台的 流动性提供者代币,是为平台上的交易者提供流动性的工具,通过增加流动性来获得GLP奖励

esGMX esGMX 是一种托管代币奖励,质押 GMX 或 GLP 可获得。它可以继续质押获得收益,或在一段时间后解锁为实际 GMX

bnGMX bnGMX 是额外的奖励代币,用于提升用户的质押奖励收益,但不能交易或解锁为 GMX

有了这几个基础概念之后,我们接下来结合着源码来进行解析

首先我们来看质押的逻辑 质押流程 image.png 跟我们常见的质押处理逻辑不一样,我们通常的质押处理逻辑是直接将代币质押到一个合约中,然后直接在该合约计算奖励和份额。但是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里面即可。这样大大增强了代码的可读性已经扩展性

接下来我们看一下解质押的逻辑,刚好和质押的流程相反 解质押流程

image.png 可以看到流程完全想法,调整了一下。 同时也可以看到单独对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数量的份额 第七步 进行代币转移

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Leo
Leo
江湖只有他的大名,没有他的介绍。