GMX 源码解析二,市价订单逻辑

  • Leo
  • 更新于 2024-09-04 23:10
  • 阅读 150

从上述时序图上来看,无论是市价单还是限价单,他们的执行逻辑其实都是一样的,都是EOA账户发起交易,智能合约生成交易单。由交易机器人根据当前市场价格进行匹配,匹配成功后,像智能合约发起执行命令。它们直接唯一的区别就是执行的合约不同(限价单:OrderBook,市价单:PositionRout

image.png 从上述时序图上来看,无论是 市价单 还是 限价单,他们的执行逻辑其实都是一样的,都是EOA账户发起交易,智能合约生成交易单。由交易机器人根据当前市场价格进行匹配,匹配成功后,像智能合约发起执行命令。它们 直接唯一的区别就是执行的合约不同(限价单:OrderBook,市价单:PositionRouter)

接下来,我们结合着源码一起来进行分析 这个是创建市价单的入口方法createIncreasePosition

function createIncreasePosition(
        address[] memory _path, 代币转换路径数组,表示用于加仓的代币转换路径(数量一般为1或者2)
        address _indexToken, 目标代币,即要建立仓位的标的代币(比如 ETH、BTC)。
        uint256 _amountIn,  输入的代币数量,用于建立仓位的代币数。
        uint256 _minOut,  最低输出数量,用于在代币转换时确定最低接收数量
        uint256 _sizeDelta,  仓位增加的大小  倍数
        bool _isLong,  (true 为多头,false 为空头)
        uint256 _acceptablePrice,  可接受的最大价格
        uint256 _executionFee,   执行费用
        bytes32 _referralCode,  推荐码,用于记录推荐关系
        address _callbackTarget  回调目标地址,如果交易成功可以触发回调
    ) external payable nonReentrant returns (bytes32) {
        // 交易执行费必须大于等于最低阈值
        require(_executionFee >= minExecutionFee, "fee");
        // 设置的交易费用 必须等于 执行费
        require(msg.value == _executionFee, "val");
        // _path 的大小必须等于1或者2
        require(_path.length == 1 || _path.length == 2, "len");
        // 将交易执行费用保存到WETH
        _transferInETH();
        // 用于记录推荐关系。这通常是为了奖励推荐人
        _setTraderReferralCode(_referralCode);

        if (_amountIn > 0) {
            // 主要是将代币从客户地址转移到当前的PositionRouter上
            IRouter(router).pluginTransfer(_path[0], msg.sender, address(this), _amountIn);
        }

        // 执行对参数打包并保存 
        return _createIncreasePosition(
            msg.sender,
            _path,
            _indexToken,
            _amountIn,
            _minOut,
            _sizeDelta,
            _isLong,
            _acceptablePrice,
            _executionFee,
            false,
            _callbackTarget
        );
    }

第一步 还是进行必要的校验,比如执行费用,交易金额,以及path的大小 第二步 将执行费用保存到WETH 第三步 用于记录推荐关系. 这通常是为了奖励推荐人 第四步 将代币从客户地址转移到当前的PositionRouter上,但是要注意,这里面有一个_amountIn > 0 的判断 什么情况下会出现_amountIn =0 呢(用户不需要增加额外资金,使用已有的抵押物增加杠杆的情况下) 第五步 执行对参数打包并保存的逻辑 接下来看具体的创建打包逻辑

function _createIncreasePosition(
        address _account,
        address[] memory _path,
        address _indexToken,
        uint256 _amountIn,
        uint256 _minOut,
        uint256 _sizeDelta,
        bool _isLong,
        uint256 _acceptablePrice,
        uint256 _executionFee,
        bool _hasCollateralInETH,
        address _callbackTarget
    ) internal returns (bytes32) {
        // 对参数进行打包
        IncreasePositionRequest memory request = IncreasePositionRequest(
            _account,
            _path,
            _indexToken,
            _amountIn,
            _minOut,
            _sizeDelta,
            _isLong,
            _acceptablePrice,
            _executionFee,
            block.number,
            block.timestamp,
            _hasCollateralInETH,
            _callbackTarget
        );
        // 将结构体保存到increasePositionRequests里面,将key添加到increasePositionRequestKeys里面
        (uint256 index, bytes32 requestKey) = _storeIncreasePositionRequest(request);
        // 发送事件
    emit CreateIncreasePosition(
            _account,
            _path,
            _indexToken,
            _amountIn,
            _minOut,
            _sizeDelta,
            _isLong,
            _acceptablePrice,
            _executionFee,
            index,
            increasePositionRequestKeys.length - 1,
            block.number,
            block.timestamp,
            tx.gasprice
        );
        return requestKey;
    }

第一步 对参数进行打包,打包成IncreasePositionRequest结构体 第二步 将结构体保存到increasePositionRequests里面,将key添加到increasePositionRequestKeys里面,其中key是通过account 和 index keccak256hash加密后的值。 index是increasePositionsIndex+1后的数值

 function _storeIncreasePositionRequest(IncreasePositionRequest memory _request) internal returns   (uint256, bytes32) {
        address account = _request.account;
        uint256 index = increasePositionsIndex[account].add(1);
        increasePositionsIndex[account] = index;
        bytes32 key = getRequestKey(account, index);

        increasePositionRequests[key] = _request;
        increasePositionRequestKeys.push(key);

        return (index, key);
    }

第三步 发送创建市价单事件

接下来看看 交易机器人执行市价单的逻辑 executeIncreasePosition 是具体执行加仓逻辑的函数

function executeIncreasePosition(bytes32 _key, address payable _executionFeeReceiver) public nonReentrant returns (bool) {
        // 根据key获取指定的request
            IncreasePositionRequest memory request = increasePositionRequests[_key];
        // if the request was already executed or cancelled, return true so that the executeIncreasePositions loop will continue executing the next request
        if (request.account == address(0)) { return true; }
        // 这里调用 _validateExecution 函数,检查当前请求是否满足执行条件
        bool shouldExecute = _validateExecution(request.blockNumber, request.blockTime, request.account);
        if (!shouldExecute) { return false; }
        // 删除请求防止重复调用
        delete increasePositionRequests[_key];

        if (request.amountIn > 0) {
            uint256 amountIn = request.amountIn;
            //  如果path>1 需要将代币进行交换
            if (request.path.length > 1) {
                IERC20(request.path[0]).safeTransfer(vault, request.amountIn);
                amountIn = _swap(request.path, request.minOut, address(this));
            }
            // 算并扣除用户在执行增加仓位操作时所需支付的手续费后
            uint256 afterFeeAmount = _collectFees(request.account, request.path, amountIn, request.indexToken, request.isLong, request.sizeDelta);
            // 进行代币转移
            IERC20(request.path[request.path.length - 1]).safeTransfer(vault, afterFeeAmount);
        }
        // 增加仓位,此方法的执行在BasePositionManager里面(下面会进行介绍)
        _increasePosition(request.account, request.path[request.path.length - 1], request.indexToken, request.sizeDelta, request.isLong, request.acceptablePrice);
        // 执行费支付给执行请求的交易机器人
        _transferOutETHWithGasLimitFallbackToWeth(request.executionFee, _executionFeeReceiver);

        emit ExecuteIncreasePosition(
            request.account,
            request.path,
            request.indexToken,
            request.amountIn,
            request.minOut,
            request.sizeDelta,
            request.isLong,
            request.acceptablePrice,
            request.executionFee,
            block.number.sub(request.blockNumber),
            block.timestamp.sub(request.blockTime)
        );
        // 执行回调逻辑
        _callRequestCallback(request.callbackTarget, _key, true, true);

        return true;
    }

第一步 根据key获取指定的request,并判断是否已经被执行,或者是否存在 第二步 这里调用 _validateExecution 函数,检查当前请求是否满足执行条件 第三步 删除请求防止重复调用 第四步 校验是否有新追加的资金,如果有要判断是否进行swap,然后再进行代币转移 第五步 最为核心的增加仓位,这里面可以看PositionUtils.increasePosition, 里面涉及到市场价格检查,获取治理权限,更新全局空头数据,执行仓位增加操作等等

function _increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong, uint256 _price) internal {
        _validateMaxGlobalSize(_indexToken, _isLong, _sizeDelta);

        PositionUtils.increasePosition(
            vault,
            router,
            shortsTracker,
            _account,
            _collateralToken,
            _indexToken,
            _sizeDelta,
            _isLong,
            _price
        );
        // 发送一个事件通知,用于记录仓位增加操作,并且可能用于计算推荐奖励等
        _emitIncreasePositionReferral(_account, _sizeDelta);
    }

第六步 执行费支付给执行请求的交易机器人 第七步 执行回调逻辑

杠杆交易的风险 综上所述,我们就可以了解了基本的杠杆加减仓的原理,但是,个人还是不建议玩杠杆,它的爆仓风险还是比较高的,下面两种情况就可能导致爆仓 条件一:抵押品总USD价值 + 仓位盈亏USD价值 < 资金USD费用 + 清算USD费用。 条件二:(抵押品总USD价值 + 仓位盈亏USD价值) * 最大杠杆倍数 < 仓位总USD价值。

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

0 条评论

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