概述

GMX是一个衍生品交易所,可以进行做空和做多,其使用质押的方式提供流动性,流动行提供方和合约交易者是对手方,合约交易者进行杠杆操作时实际上是向流动性提供方借钱,所以合约交易者赚的越多,流动性提供者亏的越多,反之亦然。由于不是采用AMA的方式来获取交易对的价格而是直接从ChainLink和其他交易所获取的价格当作标记价格,所以交易不会存在滑点的问题。

合约

Vault.sol

// ...

contract Vault is ReentrancyGuard, IVault {
		// ...

    function buyUSDG(address _token, address _receiver) external override nonReentrant returns (uint256) {
        _validateManager();
        _validate(whitelistedTokens[_token], 16);
        useSwapPricing = true;

        uint256 tokenAmount = _transferIn(_token);  // 获取转进来的token数量
        _validate(tokenAmount > 0, 17); 

        updateCumulativeFundingRate(_token, _token);    // 更新token的累计FundingRate

        uint256 price = getMinPrice(_token);    // 获取各个价格提供方中最小的价格

        // 将token的数量转成usdg数量
        uint256 usdgAmount = tokenAmount.mul(price).div(PRICE_PRECISION);
        usdgAmount = adjustForDecimals(usdgAmount, _token, usdg);
        _validate(usdgAmount > 0, 18);

        uint256 feeBasisPoints = vaultUtils.getBuyUsdgFeeBasisPoints(_token, usdgAmount);   // 获取手续费率,其会根据数量和余额有所不同
        uint256 amountAfterFees = _collectSwapFees(_token, tokenAmount, feeBasisPoints);    // 扣除手续费后的token数量
        uint256 mintAmount = amountAfterFees.mul(price).div(PRICE_PRECISION);   // 获取最终获得的最小usdg数量
        mintAmount = adjustForDecimals(mintAmount, _token, usdg);

        // 增加usdg的总数量和token池子中的数量
        _increaseUsdgAmount(_token, mintAmount);
        _increasePoolAmount(_token, amountAfterFees);

        // mint出usdg给交易接受者
        IUSDG(usdg).mint(_receiver, mintAmount);

        emit BuyUSDG(_receiver, _token, tokenAmount, mintAmount, feeBasisPoints);

        useSwapPricing = false;
        return mintAmount;
    }

    function sellUSDG(address _token, address _receiver) external override nonReentrant returns (uint256) {
        _validateManager();
        _validate(whitelistedTokens[_token], 19);
        useSwapPricing = true;

        uint256 usdgAmount = _transferIn(usdg); // 获取转进来的usdg数量
        _validate(usdgAmount > 0, 20);

        updateCumulativeFundingRate(_token, _token);    // 更新token的累计FundingRate

        uint256 redemptionAmount = getRedemptionAmount(_token, usdgAmount); // 获取可以赎回的金额
        _validate(redemptionAmount > 0, 21);

        // 更新token对应置换成的usdg数量和token的余额
        _decreaseUsdgAmount(_token, usdgAmount);
        _decreasePoolAmount(_token, redemptionAmount);

        // burn掉usdg
        IUSDG(usdg).burn(address(this), usdgAmount);

        // the _transferIn call increased the value of tokenBalances[usdg]
        // usually decreases in token balances are synced by calling _transferOut
        // however, for usdg, the tokens are burnt, so _updateTokenBalance should
        // be manually called to record the decrease in tokens
        _updateTokenBalance(usdg);

        uint256 feeBasisPoints = vaultUtils.getSellUsdgFeeBasisPoints(_token, usdgAmount);
        uint256 amountOut = _collectSwapFees(_token, redemptionAmount, feeBasisPoints); // 扣除交易手续费后的数量
        _validate(amountOut > 0, 22);

        _transferOut(_token, amountOut, _receiver);

        emit SellUSDG(_receiver, _token, usdgAmount, amountOut, feeBasisPoints);

        useSwapPricing = false;
        return amountOut;
    }

    function swap(address _tokenIn, address _tokenOut, address _receiver) external override nonReentrant returns (uint256) {
        _validate(isSwapEnabled, 23);
        _validate(whitelistedTokens[_tokenIn], 24);
        _validate(whitelistedTokens[_tokenOut], 25);
        _validate(_tokenIn != _tokenOut, 26);

        useSwapPricing = true;

        // 更新累计资金费率
        updateCumulativeFundingRate(_tokenIn, _tokenIn);
        updateCumulativeFundingRate(_tokenOut, _tokenOut);

        uint256 amountIn = _transferIn(_tokenIn);   // 转入数量
        _validate(amountIn > 0, 27);

        uint256 priceIn = getMinPrice(_tokenIn);    // 转入token的最小价格
        uint256 priceOut = getMaxPrice(_tokenOut);  // 转出token的最大价格

        uint256 amountOut = amountIn.mul(priceIn).div(priceOut);    // 计算转出token的数量
        amountOut = adjustForDecimals(amountOut, _tokenIn, _tokenOut);

        // adjust usdgAmounts by the same usdgAmount as debt is shifted between the assets
        uint256 usdgAmount = amountIn.mul(priceIn).div(PRICE_PRECISION);    // 转入token转成usdg数量
        usdgAmount = adjustForDecimals(usdgAmount, _tokenIn, usdg);

        uint256 feeBasisPoints = vaultUtils.getSwapFeeBasisPoints(_tokenIn, _tokenOut, usdgAmount);
        uint256 amountOutAfterFees = _collectSwapFees(_tokenOut, amountOut, feeBasisPoints);    // 扣除手续费后的转出token数量

        // 更新tokenIn和tokenOut余额对应的usdg数量
        _increaseUsdgAmount(_tokenIn, usdgAmount);
        _decreaseUsdgAmount(_tokenOut, usdgAmount);

        // 更新池子中tokenIn和tokenOut的数量
        _increasePoolAmount(_tokenIn, amountIn);
        _decreasePoolAmount(_tokenOut, amountOut);

        _validateBufferAmount(_tokenOut);

        _transferOut(_tokenOut, amountOutAfterFees, _receiver);

        emit Swap(_receiver, _tokenIn, _tokenOut, amountIn, amountOut, amountOutAfterFees, feeBasisPoints);

        useSwapPricing = false;
        return amountOutAfterFees;
    }

		// ...

    function getRedemptionAmount(address _token, uint256 _usdgAmount) public override view returns (uint256) {
        uint256 price = getMaxPrice(_token);    // 获取各个市场上token的最大值(及最终获取到token的数量小的方案)
        // redemptionAmount = _usdgAmount / tokenPrice
        uint256 redemptionAmount = _usdgAmount.mul(PRICE_PRECISION).div(price);
        return adjustForDecimals(redemptionAmount, usdg, _token);
    }

		// ...

    function updateCumulativeFundingRate(address _collateralToken, address _indexToken) public {
        bool shouldUpdate = vaultUtils.updateCumulativeFundingRate(_collateralToken, _indexToken);
        if (!shouldUpdate) {
            return;
        }

        if (lastFundingTimes[_collateralToken] == 0) {
            lastFundingTimes[_collateralToken] = block.timestamp.div(fundingInterval).mul(fundingInterval);
            return;
        }

        // 距离上次更新间隔大于fundingInterval才更新
        if (lastFundingTimes[_collateralToken].add(fundingInterval) > block.timestamp) {
            return;
        }

        uint256 fundingRate = getNextFundingRate(_collateralToken);
        cumulativeFundingRates[_collateralToken] = cumulativeFundingRates[_collateralToken].add(fundingRate);
        lastFundingTimes[_collateralToken] = block.timestamp.div(fundingInterval).mul(fundingInterval);

        emit UpdateFundingRate(_collateralToken, cumulativeFundingRates[_collateralToken]);
    }

    function getNextFundingRate(address _token) public override view returns (uint256) {
        if (lastFundingTimes[_token].add(fundingInterval) > block.timestamp) { return 0; }

        // intervals = (now - lastTime) / fundingInterval
        uint256 intervals = block.timestamp.sub(lastFundingTimes[_token]).div(fundingInterval);
        uint256 poolAmount = poolAmounts[_token];
        if (poolAmount == 0) { return 0; }

        uint256 _fundingRateFactor = stableTokens[_token] ? stableFundingRateFactor : fundingRateFactor;
        // FundingRate = _fundingRateFactor  * intervals * reservedAmounts/poolAmount
        // 从这里可以看出,reservedAmounts/poolAmount越大,FundingRate越大
        return _fundingRateFactor.mul(reservedAmounts[_token]).mul(intervals).div(poolAmount);
    }
		// ...
}

参考链接

GitHub - gmx-io/gmx-contracts at v1

gmx-io

GMX项目分享 ( part 1 )

Dapp-Learning/defi/GMX at main · Dapp-Learning-DAO/Dapp-Learning