{"author":{"address":null,"user":"https://learnblockchain.cn/people/22539"},"content":{"body":"![image.png](https://img.learnblockchain.cn/attachments/2024/09/kfLUWa4S66d875f3dc790.png)\r\n从上述时序图上来看，无论是 市价单 还是 限价单，他们的执行逻辑其实都是一样的，都是EOA账户发起交易，智能合约生成交易单。由交易机器人根据当前市场价格进行匹配，匹配成功后，像智能合约发起执行命令。它们\r\n直接唯一的区别就是执行的合约不同（限价单:OrderBook,市价单:PositionRouter）\r\n\r\n接下来，我们结合着源码一起来进行分析\r\n**这个是创建市价单的入口方法createIncreasePosition**\r\n\r\n```\r\nfunction createIncreasePosition(\r\n        address[] memory _path, 代币转换路径数组，表示用于加仓的代币转换路径（数量一般为1或者2）\r\n        address _indexToken, 目标代币，即要建立仓位的标的代币（比如 ETH、BTC）。\r\n        uint256 _amountIn,  输入的代币数量，用于建立仓位的代币数。\r\n        uint256 _minOut,  最低输出数量，用于在代币转换时确定最低接收数量\r\n        uint256 _sizeDelta,  仓位增加的大小  倍数\r\n        bool _isLong,  （true 为多头，false 为空头）\r\n        uint256 _acceptablePrice,  可接受的最大价格\r\n        uint256 _executionFee,   执行费用\r\n        bytes32 _referralCode,  推荐码，用于记录推荐关系\r\n        address _callbackTarget  回调目标地址，如果交易成功可以触发回调\r\n    ) external payable nonReentrant returns (bytes32) {\r\n        // 交易执行费必须大于等于最低阈值\r\n        require(_executionFee \u003e= minExecutionFee, \"fee\");\r\n        // 设置的交易费用 必须等于 执行费\r\n        require(msg.value == _executionFee, \"val\");\r\n        // _path 的大小必须等于1或者2\r\n        require(_path.length == 1 || _path.length == 2, \"len\");\r\n      \t// 将交易执行费用保存到WETH\r\n        _transferInETH();\r\n        // 用于记录推荐关系。这通常是为了奖励推荐人\r\n        _setTraderReferralCode(_referralCode);\r\n\r\n        if (_amountIn \u003e 0) {\r\n            // 主要是将代币从客户地址转移到当前的PositionRouter上\r\n            IRouter(router).pluginTransfer(_path[0], msg.sender, address(this), _amountIn);\r\n        }\r\n  \r\n      \t// 执行对参数打包并保存 \r\n        return _createIncreasePosition(\r\n            msg.sender,\r\n            _path,\r\n            _indexToken,\r\n            _amountIn,\r\n            _minOut,\r\n            _sizeDelta,\r\n            _isLong,\r\n            _acceptablePrice,\r\n            _executionFee,\r\n            false,\r\n            _callbackTarget\r\n        );\r\n    }\r\n```\r\n第一步  还是进行必要的校验，比如执行费用，交易金额，以及path的大小\r\n第二步  将执行费用保存到WETH\r\n第三步  用于记录推荐关系. 这通常是为了奖励推荐人\r\n第四步  将代币从客户地址转移到当前的PositionRouter上，但是要注意，这里面有一个_amountIn \u003e 0 的判断\r\n什么情况下会出现_amountIn =0 呢（用户不需要增加额外资金，使用已有的抵押物增加杠杆的情况下）\r\n第五步  执行对参数打包并保存的逻辑\r\n**接下来看具体的创建打包逻辑**\r\n\r\n```\r\nfunction _createIncreasePosition(\r\n        address _account,\r\n        address[] memory _path,\r\n        address _indexToken,\r\n        uint256 _amountIn,\r\n        uint256 _minOut,\r\n        uint256 _sizeDelta,\r\n        bool _isLong,\r\n        uint256 _acceptablePrice,\r\n        uint256 _executionFee,\r\n        bool _hasCollateralInETH,\r\n        address _callbackTarget\r\n    ) internal returns (bytes32) {\r\n        // 对参数进行打包\r\n        IncreasePositionRequest memory request = IncreasePositionRequest(\r\n            _account,\r\n            _path,\r\n            _indexToken,\r\n            _amountIn,\r\n            _minOut,\r\n            _sizeDelta,\r\n            _isLong,\r\n            _acceptablePrice,\r\n            _executionFee,\r\n            block.number,\r\n            block.timestamp,\r\n            _hasCollateralInETH,\r\n            _callbackTarget\r\n        );\r\n      \t// 将结构体保存到increasePositionRequests里面，将key添加到increasePositionRequestKeys里面\r\n        (uint256 index, bytes32 requestKey) = _storeIncreasePositionRequest(request);\r\n        // 发送事件\r\n  \temit CreateIncreasePosition(\r\n            _account,\r\n            _path,\r\n            _indexToken,\r\n            _amountIn,\r\n            _minOut,\r\n            _sizeDelta,\r\n            _isLong,\r\n            _acceptablePrice,\r\n            _executionFee,\r\n            index,\r\n            increasePositionRequestKeys.length - 1,\r\n            block.number,\r\n            block.timestamp,\r\n            tx.gasprice\r\n        );\r\n   \t\treturn requestKey;\r\n    }\r\n```\r\n第一步  对参数进行打包，打包成IncreasePositionRequest结构体\r\n第二步  将结构体保存到increasePositionRequests里面，将key添加到increasePositionRequestKeys里面，其中key是通过account 和 index keccak256hash加密后的值。  index是increasePositionsIndex+1后的数值\r\n\r\n```\r\n function _storeIncreasePositionRequest(IncreasePositionRequest memory _request) internal returns   (uint256, bytes32) {\r\n        address account = _request.account;\r\n        uint256 index = increasePositionsIndex[account].add(1);\r\n        increasePositionsIndex[account] = index;\r\n        bytes32 key = getRequestKey(account, index);\r\n\r\n        increasePositionRequests[key] = _request;\r\n        increasePositionRequestKeys.push(key);\r\n\r\n        return (index, key);\r\n    }\r\n```\r\n第三步 发送创建市价单事件\r\n\r\n**接下来看看 交易机器人执行市价单的逻辑**\r\nexecuteIncreasePosition  是具体执行加仓逻辑的函数\r\n\r\n```\r\nfunction executeIncreasePosition(bytes32 _key, address payable _executionFeeReceiver) public nonReentrant returns (bool) {\r\n        // 根据key获取指定的request\r\n  \t\t\tIncreasePositionRequest memory request = increasePositionRequests[_key];\r\n        // if the request was already executed or cancelled, return true so that the executeIncreasePositions loop will continue executing the next request\r\n        if (request.account == address(0)) { return true; }\r\n      \t// 这里调用 _validateExecution 函数，检查当前请求是否满足执行条件\r\n        bool shouldExecute = _validateExecution(request.blockNumber, request.blockTime, request.account);\r\n        if (!shouldExecute) { return false; }\r\n      \t// 删除请求防止重复调用\r\n        delete increasePositionRequests[_key];\r\n      \t\r\n        if (request.amountIn \u003e 0) {\r\n            uint256 amountIn = request.amountIn;\r\n          \t//  如果path\u003e1 需要将代币进行交换\r\n            if (request.path.length \u003e 1) {\r\n                IERC20(request.path[0]).safeTransfer(vault, request.amountIn);\r\n                amountIn = _swap(request.path, request.minOut, address(this));\r\n            }\r\n            // 算并扣除用户在执行增加仓位操作时所需支付的手续费后\r\n            uint256 afterFeeAmount = _collectFees(request.account, request.path, amountIn, request.indexToken, request.isLong, request.sizeDelta);\r\n            // 进行代币转移\r\n            IERC20(request.path[request.path.length - 1]).safeTransfer(vault, afterFeeAmount);\r\n        }\r\n      \t// 增加仓位，此方法的执行在BasePositionManager里面(下面会进行介绍)\r\n        _increasePosition(request.account, request.path[request.path.length - 1], request.indexToken, request.sizeDelta, request.isLong, request.acceptablePrice);\r\n      \t// 执行费支付给执行请求的交易机器人\r\n        _transferOutETHWithGasLimitFallbackToWeth(request.executionFee, _executionFeeReceiver);\r\n    \t\r\n        emit ExecuteIncreasePosition(\r\n            request.account,\r\n            request.path,\r\n            request.indexToken,\r\n            request.amountIn,\r\n            request.minOut,\r\n            request.sizeDelta,\r\n            request.isLong,\r\n            request.acceptablePrice,\r\n            request.executionFee,\r\n            block.number.sub(request.blockNumber),\r\n            block.timestamp.sub(request.blockTime)\r\n        );\r\n        // 执行回调逻辑\r\n        _callRequestCallback(request.callbackTarget, _key, true, true);\r\n\r\n        return true;\r\n    }\r\n```\r\n第一步  根据key获取指定的request，并判断是否已经被执行，或者是否存在\r\n第二步   这里调用 _validateExecution 函数，检查当前请求是否满足执行条件\r\n第三步   删除请求防止重复调用\r\n第四步  校验是否有新追加的资金，如果有要判断是否进行swap，然后再进行代币转移\r\n第五步  最为核心的增加仓位，这里面可以看PositionUtils.increasePosition, 里面涉及到市场价格检查，获取治理权限，更新全局空头数据，执行仓位增加操作等等\r\n\r\n```\r\nfunction _increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong, uint256 _price) internal {\r\n        _validateMaxGlobalSize(_indexToken, _isLong, _sizeDelta);\r\n\r\n        PositionUtils.increasePosition(\r\n            vault,\r\n            router,\r\n            shortsTracker,\r\n            _account,\r\n            _collateralToken,\r\n            _indexToken,\r\n            _sizeDelta,\r\n            _isLong,\r\n            _price\r\n        );\r\n      \t// 发送一个事件通知，用于记录仓位增加操作，并且可能用于计算推荐奖励等\r\n        _emitIncreasePositionReferral(_account, _sizeDelta);\r\n    }\r\n```\r\n第六步  执行费支付给执行请求的交易机器人\r\n第七步  执行回调逻辑\r\n\r\n**杠杆交易的风险**\r\n综上所述，我们就可以了解了基本的杠杆加减仓的原理，但是，个人还是不建议玩杠杆，它的爆仓风险还是比较高的，下面两种情况就可能导致爆仓\r\n条件一：抵押品总USD价值 + 仓位盈亏USD价值 \u003c 资金USD费用 + 清算USD费用。\r\n条件二：(抵押品总USD价值 + 仓位盈亏USD价值) * 最大杠杆倍数 \u003c 仓位总USD价值。","title":"GMX 源码解析二，市价订单逻辑"},"history":null,"timestamp":1725462625,"version":1}