解析 GMX 限价订单逻辑
GMX 大体架构图
从上述的用户故事中可以看出,整个GMX 合约的核心角色是 EOA 用户 : 外部用户 订单执行账户 : 一般是交易机器人 清算账户 : 一般是清算机器人 收益管理账户: (需要平台管理员EOA账户和平台手续费收益管理员EOA账户来控制)
从上述架构图中,我们可以看到GMX 核心是围绕着,杠杆交易,现货交易,质押 来实现的 我们今天首先来介绍一下 杠杆交易。 杠杆交易: 一 限价单交易 BookOrder 二 市价单交易 PositionRouter
限价单的处理逻辑:
结合源码来看
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, 这块逻辑比较深会放到后续的内容中
第七步 支付执行费用给到交易机器人
第八步 发送消息到链上
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!