0x00 前言

上一篇文章:

第五篇:GMX源码解读(一)

这周我们详细解析一下上次GMX文章的几个关键合约:Vault、VaultPriceFeed和FastPriceFeed。

本文将会对代码进行以行为单位的解读,逐行讲解,非常硬核,建议一键三连

虽然Notion没有一键三连)

0x01 Vault

Vault合约是GMX最核心的合约,管理了资金池(Pool)、仓位(Position)等,部署在:GMX: Vault | Address 0x489ee077994B6658eAfA855C308275EAd8097C4A | Arbiscan(Arbitrum)

1. increasePosition

首先我们来详细分析下增加仓位的代码:(为了让代码更简洁,我把源码改写为Solidity 8.0+的版本,即将SafeMath的函数改回运算符)

function increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external nonReentrant {
    _validate(isLeverageEnabled, 28);
    _validateGasPrice();
    _validateRouter(_account);
    _validateTokens(_collateralToken, _indexToken, _isLong);
    vaultUtils.validateIncreasePosition(_account, _collateralToken, _indexToken, _sizeDelta, _isLong);

    updateCumulativeFundingRate(_collateralToken, _indexToken);

    bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong);
    Position storage position = positions[key];

    uint256 price = _isLong ? vault().getMaxPrice(_indexToken) : vault().getMinPrice(_indexToken);

    if (position.size == 0) {
      position.averagePrice = price;
    }

    if (position.size > 0 && _sizeDelta > 0) {
      position.averagePrice = getNextAveragePrice(_indexToken, position.size, position.averagePrice, _isLong, price, _sizeDelta, position.lastIncreasedTime);
    }

    uint256 fee = _collectMarginFees(_account, _collateralToken, _indexToken, _isLong, _sizeDelta, position.size, position.entryFundingRate);
    uint256 collateralDelta = _transferIn(_collateralToken);
    uint256 collateralDeltaUsd = tokenToUsdMin(_collateralToken, collateralDelta);

    position.collateral = position.collateral + collateralDeltaUsd;
    _validate(position.collateral >= fee, 29);

    position.collateral = position.collateral - fee;
    position.entryFundingRate = getEntryFundingRate(_collateralToken, _indexToken, _isLong);
    position.size = position.size + _sizeDelta;
    position.lastIncreasedTime = block.timestamp;

    _validate(position.size > 0, 30);
    _validatePosition(position.size, position.collateral);
    validateLiquidation(_account, _collateralToken, _indexToken, _isLong, true);

    // reserve tokens to pay profits on the position
    uint256 reserveDelta = usdToTokenMax(_collateralToken, _sizeDelta);
    position.reserveAmount = position.reserveAmount + reserveDelta;
    _increaseReservedAmount(_collateralToken, reserveDelta);

    if (_isLong) {
      // guaranteedUsd stores the sum of (position.size - position.collateral) for all positions
      // if a fee is charged on the collateral then guaranteedUsd should be increased by that fee amount
      // since (position.size - position.collateral) would have increased by `fee`
      _increaseGuaranteedUsd(_collateralToken, _sizeDelta + fee);
      _decreaseGuaranteedUsd(_collateralToken, collateralDeltaUsd);
      // treat the deposited collateral as part of the pool
      _increasePoolAmount(_collateralToken, collateralDelta);
      // fees need to be deducted from the pool since fees are deducted from position.collateral
      // and collateral is treated as part of the pool
      _decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, fee));
    } else {
      if (globalShortSizes[_indexToken] == 0) {
        globalShortAveragePrices[_indexToken] = price;
      } else {
        globalShortAveragePrices[_indexToken] = getNextGlobalShortAveragePrice(_indexToken, price, _sizeDelta);
      }

      _increaseGlobalShortSize(_indexToken, _sizeDelta);
    }

    emit IncreasePosition(key, _account, _collateralToken, _indexToken, collateralDeltaUsd, _sizeDelta, _isLong, price, fee);
    emit UpdatePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl, price);
  }

该函数可以分为三个部分:

至此,increasePosition函数解析完毕。

2. decreasePosition

接下来是减少仓位的代码:

function decreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) external nonReentrant returns (uint256) {
  _validateGasPrice();
  _validateRouter(_account);
  return _decreasePosition(_account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, _receiver);
}

function _decreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) private returns (uint256) {
  vaultUtils.validateDecreasePosition(_account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, _receiver);
  updateCumulativeFundingRate(_collateralToken, _indexToken);

  bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong);
  Position storage position = positions[key];
  _validate(position.size > 0, 31);
  _validate(position.size >= _sizeDelta, 32);
  _validate(position.collateral >= _collateralDelta, 33);

  uint256 collateral = position.collateral;
  // scrop variables to avoid stack too deep errors
  {
    uint256 reserveDelta = position.reserveAmount * _sizeDelta / position.size;
    position.reserveAmount = position.reserveAmount - reserveDelta;
    _decreaseReservedAmount(_collateralToken, reserveDelta);
  }

  (uint256 usdOut, uint256 usdOutAfterFee) = _reduceCollateral(_account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong);

  if (position.size != _sizeDelta) {
    position.entryFundingRate = getEntryFundingRate(_collateralToken, _indexToken, _isLong);
    position.size = position.size - _sizeDelta;

    _validatePosition(position.size, position.collateral);
    validateLiquidation(_account, _collateralToken, _indexToken, _isLong, true);

    if (_isLong) {
      _increaseGuaranteedUsd(_collateralToken, collateral - position.collateral);
      _decreaseGuaranteedUsd(_collateralToken, _sizeDelta);
    }

    uint256 price = _isLong ? vault().getMinPrice(_indexToken) : vault().getMaxPrice(_indexToken);
    emit DecreasePosition(key, _account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, price, usdOut - usdOutAfterFee);
    emit UpdatePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl, price);
  } else {
    if (_isLong) {
      _increaseGuaranteedUsd(_collateralToken, collateral);
      _decreaseGuaranteedUsd(_collateralToken, _sizeDelta);
    }

    uint256 price = _isLong ? vault().getMinPrice(_indexToken) : vault().getMaxPrice(_indexToken);
    emit DecreasePosition(key, _account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, price, usdOut - usdOutAfterFee);
    emit ClosePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl);

    delete positions[key];
  }

  if (!_isLong) {
    _decreaseGlobalShortSize(_indexToken, _sizeDelta);
  }

  if (usdOut > 0) {
    if (_isLong) {
      _decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, usdOut));
    }
    uint256 amountOutAfterFees = usdToTokenMin(_collateralToken, usdOutAfterFee);
    _transferOut(_collateralToken, amountOutAfterFees, _receiver);
    return amountOutAfterFees;
  }

  return 0;
}