GMX 源码解析一,限价订单逻辑

  • Leo
  • 更新于 2024-09-04 16:34
  • 阅读 351

解析 GMX 限价订单逻辑

GMX 大体架构图

image.png

从上述的用户故事中可以看出,整个GMX 合约的核心角色是 EOA 用户 : 外部用户 订单执行账户 : 一般是交易机器人 清算账户 : 一般是清算机器人 收益管理账户: (需要平台管理员EOA账户和平台手续费收益管理员EOA账户来控制)

从上述架构图中,我们可以看到GMX 核心是围绕着,杠杆交易,现货交易,质押 来实现的 我们今天首先来介绍一下 杠杆交易。 杠杆交易: 一 限价单交易 BookOrder 二 市价单交易 PositionRouter

限价单的处理逻辑:

image.png

结合源码来看

function createIncreaseOrder(
        address[] memory _path,  这是一个地址数组,用来指定交换路径。
        uint256 _amountIn,  用户希望投入的代币数量
        address _indexToken,    用户需要指定加仓的代币
        uint256 _minOut,    最低输出数量,低于这个值,交换将不会发生
        uint256 _sizeDelta,  需要增加的头寸大小
        address _collateralToken,   抵押代币address
        bool _isLong,  true为多头,false空头
        uint256 _triggerPrice,  触发订单交易执行的价格
        bool _triggerAboveThreshold,   判断市场价格是否高于_triggerPrice
        uint256 _executionFee,   需要支付的手续费
        bool _shouldWrap   判断是否ETH需要转换为WETH
    ) external payable nonReentrant {
        // always need this call because of mandatory executionFee user has to transfer in ETH
        _transferInETH();  // 转移执行费用
        require(_executionFee >= minExecutionFee, "OrderBook: insufficient execution fee");  // 执行费用必须满足最低标准
        if (_shouldWrap) {   
            require(_path[0] == weth, "OrderBook: only weth could be wrapped");
            require(msg.value == _executionFee.add(_amountIn), "OrderBook: incorrect value transferred");
        } else {
            //  如果不需要转换WETH ,则用户仅仅执行执行费即可
            require(msg.value == _executionFee, "OrderBook: incorrect execution fee transferred");
            //  如果不需要执行
            IRouter(router).pluginTransfer(_path[0], msg.sender, address(this), _amountIn);
        }

        address _purchaseToken = _path[_path.length - 1];  //获取输出代币
        uint256 _purchaseTokenAmount;
        if (_path.length > 1) {
            require(_path[0] != _purchaseToken, "OrderBook: invalid _path");
            IERC20(_path[0]).safeTransfer(vault, _amountIn);   //将输入代币安全转移到vault合约中
            _purchaseTokenAmount = _swap(_path, _minOut, address(this));  // 安装路径进行代币交换,并返回实际获得的目标代币数量
        } else {
            _purchaseTokenAmount = _amountIn;  // 不需要进行交换
        }

        {
            // 根据美元价值进行计算,可以获得的最低数量
            uint256 _purchaseTokenAmountUsd = IVault(vault).tokenToUsdMin(_purchaseToken, _purchaseTokenAmount);
            // 同时这个数量需要满足最低要求,防止抵押不足
            require(_purchaseTokenAmountUsd >= minPurchaseTokenAmountUsd, "OrderBook: insufficient collateral");
        }
        // 执行加仓参数整合成限价单,保存到链上
        _createIncreaseOrder(
            msg.sender,
            _purchaseToken,
            _purchaseTokenAmount,
            _collateralToken,
            _indexToken,
            _sizeDelta,
            _isLong,
            _triggerPrice,
            _triggerAboveThreshold,
            _executionFee
        );
    }

第一步 :先需要转移执行费,到WETH中 第二步 :校验执行费用必须满足最低标准 第三步 :

if (_shouldWrap) {
            require(_path[0] == weth, "OrderBook: only weth could be wrapped");
            require(msg.value == _executionFee.add(_amountIn), "OrderBook: incorrect value transferred");
        } else {
            require(msg.value == _executionFee, "OrderBook: incorrect execution fee transferred");
            IRouter(router).pluginTransfer(_path[0], msg.sender, address(this), _amountIn);  将代币从用户转到当前合约
        }

上述这一步,核心判断是输入代币是ETH还是其他代币,如果单纯只是ETH,在后续的交易中,需要转换成为WETH以支持后续的交易。所以ETH的数值应该包含执行费用 和 交易代币的数据 如果用户使用其他的代币作为输入代币,则ETH只需要付出手执行费用即可 第四步 : 判断代币是否需要进行交换,并计算出输入代币的数量(如果path大小等于1,则输出代币数量等于_amountIn) 第五步 :根据美元的价值进行计算,得到最小USD数量,判断是否大于最低需求 第六步 : 执行_createIncreaseOrder保存这个限价单

function _createIncreaseOrder(
        address _account,
        address _purchaseToken,
        uint256 _purchaseTokenAmount,
        address _collateralToken,
        address _indexToken,
        uint256 _sizeDelta,
        bool _isLong,
        uint256 _triggerPrice,
        bool _triggerAboveThreshold,
        uint256 _executionFee
    ) private {
        uint256 _orderIndex = increaseOrdersIndex[msg.sender];
        IncreaseOrder memory order = IncreaseOrder(
            _account,
            _purchaseToken,
            _purchaseTokenAmount,
            _collateralToken,
            _indexToken,
            _sizeDelta,
            _isLong,
            _triggerPrice,
            _triggerAboveThreshold,
            _executionFee
        );
        // 加仓单据的索引 + 1
        increaseOrdersIndex[_account] = _orderIndex.add(1);
        // 将加仓单加入到合约里面
        increaseOrders[_account][_orderIndex] = order;

        emit CreateIncreaseOrder(
            _account,
            _orderIndex,
            _purchaseToken,
            _purchaseTokenAmount,
            _collateralToken,
            _indexToken,
            _sizeDelta,
            _isLong,
            _triggerPrice,
            _triggerAboveThreshold,
            _executionFee
        );
    }

_createIncreaseOrder 方法的核心功能是将参数按照IncreaseOrder结构化,然后保存到链上 第一步 将参数组合成结构体Struct 第二步 当前账户的加仓索引 + 1 第三步 存到increaseOrders mapping中,发送事件

function executeIncreaseOrder(address _address, uint256 _orderIndex, address payable _feeReceiver) override external nonReentrant {
        IncreaseOrder memory order = increaseOrders[_address][_orderIndex];
        require(order.account != address(0), "OrderBook: non-existent order");

        // increase long should use max price
        // increase short should use min price
        // 上面两句英文的意思就是多头用最大价格来验证,空头用最小价格来验证
        (uint256 currentPrice, ) = validatePositionOrderPrice(
            order.triggerAboveThreshold,
            order.triggerPrice,
            order.indexToken,
            order.isLong,
            true
        );
        // 删除订单数据,确保不会被重复执行
        delete increaseOrders[_address][_orderIndex];
        // 将购买到的token转移到Vault合约
        IERC20(order.purchaseToken).safeTransfer(vault, order.purchaseTokenAmount);
        // 如果加仓代币和抵押代币不一致,需要调用内部转换逻辑进行转换
        if (order.purchaseToken != order.collateralToken) {
            address[] memory path = new address[](2);
            path[0] = order.purchaseToken;
            path[1] = order.collateralToken;

            uint256 amountOut = _swap(path, 0, address(this));
            IERC20(order.collateralToken).safeTransfer(vault, amountOut);
        }
        // 这一步实际上执行的是合约增加仓位的方法Vault.increasePosition
        IRouter(router).pluginIncreasePosition(order.account, order.collateralToken, order.indexToken, order.sizeDelta, order.isLong);

        // pay executor //支付执行费用给到交易机器人
        _transferOutETH(order.executionFee, _feeReceiver);
        // 发送消息
        emit ExecuteIncreaseOrder(
            order.account,
            _orderIndex,
            order.purchaseToken,
            order.purchaseTokenAmount,
            order.collateralToken,
            order.indexToken,
            order.sizeDelta,
            order.isLong,
            order.triggerPrice,
            order.triggerAboveThreshold,
            order.executionFee,
            currentPrice
        );
    }

executeIncreaseOrder 是具体执行加仓的逻辑 第一步 判断当前账户下,指定索引的订单是否存在,若不存在,则直接报错 第二步 验证当前的市场价格是否满足 价格触发条件,(如果是多头,用最大价格,空头用最小价格) 第三步 删除对应的限价单,防止重复执行 第四步 将购买到的token转移到Vault合约 第五步 if (order.purchaseToken != order.collateralToken) ....这一步的逻辑是如果加仓代币和抵押代币不一致,需要调用内部转换逻辑进行转换,然后计算成抵押代币的数量,转移到Vault 第六步 这一步实际上执行的是合约增加仓位的方法Vault.increasePosition, 这块逻辑比较深会放到后续的内容中 第七步 支付执行费用给到交易机器人 第八步 发送消息到链上

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

0 条评论

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