前面几节课呢,我们都了解过GMX是如何通过限价单,市价单进行杠杆的交易。同时也大致了解了系统是怎么处理清算的逻辑的,现在我们来看看,GMX最最核心的合约:VaultstructPosition{//头寸的大小,表示用户在该头寸中投入的总金额uint256
前面几节课呢,我们都了解过GMX是如何通过限价单,市价单进行杠杆的交易。同时也大致了解了系统是怎么处理清算的逻辑的,现在我们来看看,GMX最最核心的合约:Vault
struct Position {
// 头寸的大小,表示用户在该头寸中投入的总金额
uint256 size;
// 头寸的抵押品金额
uint256 collateral;
// 头寸的平均价格
uint256 averagePrice;
// 进入头寸时的资金费率
uint256 entryFundingRate;
// 预留金额
uint256 reserveAmount;
// 已实现的盈亏
int256 realisedPnl;
// 最后一次增加头寸的时间
uint256 lastIncreasedTime;
}
这个是Vault最主要的结构体。包含头寸大小,抵押品金额,头寸均价,预留金额......
然后,我们接下来解析,核心的几个方法 首先我们来看一下buyUSDG() 这个方法主要是用来购买稳定币
function buyUSDG(address _token, address _receiver) external override nonReentrant returns (uint256) {
// 校验是否是管理员
_validateManager();
// 校验token是否处于白名单里面
_validate(whitelistedTokens[_token], 16);
// 启用交换定价机制(在通过预言机获取最小价格的时候,会用到)
useSwapPricing = true;
// 获取转移数量
uint256 tokenAmount = _transferIn(_token);
// 校验转移数量>0
_validate(tokenAmount > 0, 17);
// 更新累积融资利率
updateCumulativeFundingRate(_token, _token);
// 获取最小价格(通过预言机获取)
uint256 price = getMinPrice(_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);
// 使用净金额计算实际可以购买的数量
uint256 mintAmount = amountAfterFees.mul(price).div(PRICE_PRECISION);
// 调整小数位精度
mintAmount = adjustForDecimals(mintAmount, _token, usdg);
// 增加USDG的供应量
_increaseUsdgAmount(_token, mintAmount);
// 增加代币池中代币的供应量
_increasePoolAmount(_token, amountAfterFees);
// 铸造 计算净额 数量的 USDG
IUSDG(usdg).mint(_receiver, mintAmount);
emit BuyUSDG(_receiver, _token, tokenAmount, mintAmount, feeBasisPoints);
// 再关闭交换定价机制
useSwapPricing = false;
return mintAmount;
}
第一步,还是进行执行前的校验,包括白名单和管理员校验 第二步,获取需要兑换的token数量,并校验这个数量必须大于0 第三步,更新与该代币相关的累积融资利率,通常这是在杠杆交易中跟踪与借贷相关的费用。(这里只作更新)
function updateCumulativeFundingRate(address _collateralToken, address _indexToken) public {
bool shouldUpdate = vaultUtils.updateCumulativeFundingRate(_collateralToken, _indexToken);
if (!shouldUpdate) {
return;
}
// 初始化lastFundingTimes
if (lastFundingTimes[_collateralToken] == 0) {
lastFundingTimes[_collateralToken] = block.timestamp.div(fundingInterval).mul(fundingInterval);
return;
}
// 检查是否到了下一资金间隔
if (lastFundingTimes[_collateralToken].add(fundingInterval) > block.timestamp) {
return;
}
// 获取下一个资金利率fundingRate 资金利率因子×预留金额×间隔数/池子资金
uint256 fundingRate = getNextFundingRate(_collateralToken);
// 增加指定token的资金利率
cumulativeFundingRates[_collateralToken] = cumulativeFundingRates[_collateralToken].add(fundingRate);
// 重新设置lastFundingTimes
lastFundingTimes[_collateralToken] = block.timestamp.div(fundingInterval).mul(fundingInterval);
emit UpdateFundingRate(_collateralToken, cumulativeFundingRates[_collateralToken]);
}
第四步,获取最小价格(通过ChainLink预言机获取)
第五步,算出可以购买到的USDG数量
第六步,计算出需要缴纳的费用以及扣除了手续费用之后的净金额,然后使用这个金额得到购买数量
第七步,针对当前token,增加该token已经发出的USDG数量
第八步,增加代币池中代币的供应量
第九步,铸造指定数量的USDG给到用户
第十步,发送事件,并关闭交换定价机制
通过上述的流程整理,大致的逻辑其实还是比较简单的。
接下来我们来看sellUSDG 和buyUSDG相比,就是方向相反,用USDG购买原生代币(BTC,ETH)
function sellUSDG(address _token, address _receiver) external override nonReentrant returns (uint256) {
_validateManager();
_validate(whitelistedTokens[_token], 19);
useSwapPricing = true;
// 获取转usdg的转移数量
uint256 usdgAmount = _transferIn(usdg);
_validate(usdgAmount > 0, 20);
// 更新累积融资利率
updateCumulativeFundingRate(_token, _token);
// 用于根据usdgAmount 计算可赎回的token数量
uint256 redemptionAmount = getRedemptionAmount(_token, usdgAmount);
_validate(redemptionAmount > 0, 21);
// 降低USDG的供应量
_decreaseUsdgAmount(_token, usdgAmount);
// 降低TOKEN的供应量
_decreasePoolAmount(_token, redemptionAmount);
// 销毁转移数量的 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);
// 给_receiver 转净额度数量的token
_transferOut(_token, amountOut, _receiver);
emit SellUSDG(_receiver, _token, usdgAmount, amountOut, feeBasisPoints);
useSwapPricing = false;
return amountOut;
}
第一步 校验是否是管理员,是否代币处于白名单上面 第二步 开启交换定价机制(主要是用来预言机喂价格) 第三步 获取需要进行转换的USDG数量 第四步 更新累积融资利率 第五步 根据目标token的价格,计算出可以赎回/购买的token数量 第六步,针对当前token,减少该token已经发出的USDG数量 第七步,减少代币池中代币的供应量 第八步,同步销毁指定数量的USDG 第九步,更新当前合约中USDG剩余数量 第十步,计算需要缴纳的费率,得到扣除了手续费用后的净额 第十一步,转移净额度之后数数量 第十二步, 关闭交换定价机制 上述两个都是管理员才有权限操作的方法。
接下来看swap方法,该方法主要是用来进行现货的交易
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);
// 获取tokenIn的转移数量
uint256 amountIn = _transferIn(_tokenIn);
_validate(amountIn > 0, 27);
// 预测tokenIn的最小价格
uint256 priceIn = getMinPrice(_tokenIn);
// 预测tokenOut的最大价格
uint256 priceOut = getMaxPrice(_tokenOut);
// 计算可以出多少数量的tokenOut
uint256 amountOut = amountIn.mul(priceIn).div(priceOut);
amountOut = adjustForDecimals(amountOut, _tokenIn, _tokenOut);
// adjust usdgAmounts by the same usdgAmount as debt is shifted between the assets
// 计算这些的转入token需要消耗多少的usdg
uint256 usdgAmount = amountIn.mul(priceIn).div(PRICE_PRECISION);
usdgAmount = adjustForDecimals(usdgAmount, _tokenIn, usdg);
// 需要交换token,需要的费率
uint256 feeBasisPoints = vaultUtils.getSwapFeeBasisPoints(_tokenIn, _tokenOut, usdgAmount);
// 得到扣除手续费后实际的交货数量
uint256 amountOutAfterFees = _collectSwapFees(_tokenOut, amountOut, feeBasisPoints);
// 增加转入的_token 的usdg的数量
_increaseUsdgAmount(_tokenIn, usdgAmount);
// 减少转出的_token 的usdg的数量
_decreaseUsdgAmount(_tokenOut, usdgAmount);
// 增加池子中tokenIn 的数量
_increasePoolAmount(_tokenIn, amountIn);
// 减少池子中tokenOut 的数量
_decreasePoolAmount(_tokenOut, amountOut);
// 校验池子中的token数量,必须小于缓存的数量(为什么要这步逻辑?)
_validateBufferAmount(_tokenOut);
// 转给receiver 扣除手续费后的数量
_transferOut(_tokenOut, amountOutAfterFees, _receiver);
emit Swap(_receiver, _tokenIn, _tokenOut, amountIn, amountOut, amountOutAfterFees, feeBasisPoints);
useSwapPricing = false;
return amountOutAfterFees;
}
第一步,依然是参数的合法性交易,没有什么难度 第二步,开启交换定价逻辑 第三步,同时更新输入 和 输出代币的累积融资利率 第四步,获取输入的最小价格,输出的最大价格 第五步,计算出可以获取多少的输出token 第六步,计算手续费,以及扣除手续费后,用户实际可以获取的token 第七步,增加转入token的USDG数量 第八步,减少转出token的USDG数量 第九步,增加池子中转入token的数量 第十步,减少池子中转出token的数量 第十一步,校验池子中的token数量,必须小于缓存的数量 这一步,核心主要是避免价格操纵或异常波动,当某种代币的池中数量非常少时,流动性不足会导致价格波动加剧,容易被市场操纵。因此,设置一个代币缓存量(Buffer Amount)可以避免这种情况,保证池子的深度和稳定性。 第十二步,转给receiver 兑换后的token数量 第十三步,关闭交换定价逻辑
增加头寸方法increasePosition 即执行限价单和市价单之后最终的执行方法
//加码操作
function increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external override nonReentrant {
// 校验是否启用了杠杆
_validate(isLeverageEnabled, 28);
// 校验当前的gas小于最大的gas费率
_validateGasPrice();
// 验证路由器地址是否开启
_validateRouter(_account);
// 验证代币是否合法
_validateTokens(_collateralToken, _indexToken, _isLong);
//验证增加头寸的条件
vaultUtils.validateIncreasePosition(_account, _collateralToken, _indexToken, _sizeDelta, _isLong);
// 更新累积融资利率
updateCumulativeFundingRate(_collateralToken, _indexToken);
bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong);
Position storage position = positions[key];
// 如果是多头,价格就用最大价格,否则就用最低价格
uint256 price = _isLong ? getMaxPrice(_indexToken) : getMinPrice(_indexToken);
if (position.size == 0) {
// 初始头寸,就用获取的预测价格
position.averagePrice = price;
}
if (position.size > 0 && _sizeDelta > 0) {
// 多头 nextSize * nextPrice / nextSize + 盈利 空头 nextSize * nextPrice / nextSize - 盈利
position.averagePrice = getNextAveragePrice(_indexToken, position.size, position.averagePrice, _isLong, price, _sizeDelta, position.lastIncreasedTime);
}
// 计算保证金费用
uint256 fee = _collectMarginFees(_account, _collateralToken, _indexToken, _isLong, _sizeDelta, position.size, position.entryFundingRate);
// 获取抵押token的移出数量
uint256 collateralDelta = _transferIn(_collateralToken);
// 将抵押token转换成USD等值
uint256 collateralDeltaUsd = tokenToUsdMin(_collateralToken, collateralDelta);
// 增加抵押
position.collateral = position.collateral.add(collateralDeltaUsd);
_validate(position.collateral >= fee, 29);
// 抵押中扣除保证金
position.collateral = position.collateral.sub(fee);
// 从cumulativeFundingRates mapping中取数
position.entryFundingRate = getEntryFundingRate(_collateralToken, _indexToken, _isLong);
// 头寸增加
position.size = position.size.add(_sizeDelta);
// 记录最后一次增加时间
position.lastIncreasedTime = block.timestamp;
_validate(position.size > 0, 30);
// 验证头寸和抵押品的数量
_validatePosition(position.size, position.collateral);
// 校验流动性 TODO
validateLiquidation(_account, _collateralToken, _indexToken, _isLong, true);
// reserve tokens to pay profits on the position
// 根据抵押token 和 头寸,获取最大数量的USDG
uint256 reserveDelta = usdToTokenMax(_collateralToken, _sizeDelta);
// position中增加储备金
position.reserveAmount = position.reserveAmount.add(reserveDelta);
// 池子中增加储备金
_increaseReservedAmount(_collateralToken, reserveDelta);
if (_isLong) {
// guaranteedUsd stores the sum of (position.size - position.collateral) for all positions
// if a fee is charged on the collateral then guaranteedUsd should be increased by that fee amount
// since (position.size - position.collateral) would have increased by `fee`
_increaseGuaranteedUsd(_collateralToken, _sizeDelta.add(fee));
_decreaseGuaranteedUsd(_collateralToken, collateralDeltaUsd);
// treat the deposited collateral as part of the pool
// 往抵押token池子中增加 移出数量 _transferIn
_increasePoolAmount(_collateralToken, collateralDelta);
// fees need to be deducted from the pool since fees are deducted from position.collateral
// and collateral is treated as part of the pool
// 往抵押token池子中减少 根据fee计算出最小的数量
_decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, fee));
} else {
// 更新空头全局平均价格
if (globalShortSizes[_indexToken] == 0) {
globalShortAveragePrices[_indexToken] = price;
} else {
globalShortAveragePrices[_indexToken] = getNextGlobalShortAveragePrice(_indexToken, price, _sizeDelta);
}
// 增加全局空头头寸
_increaseGlobalShortSize(_indexToken, _sizeDelta);
}
emit IncreasePosition(key, _account, _collateralToken, _indexToken, collateralDeltaUsd, _sizeDelta, _isLong, price, fee);
emit UpdatePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl, price);
}
第一步 先进行参数合法性校验及规则校验(是否启用杠杆,gas费率,路由器地址,代币合法性等) 第二步 更新累积的融资利率 第三步 根据参数,得到仓位信息 第四步 得到目标token的价格(需要判断多头/空头) 第五步 设置平均价格 第六步 计算保证金费用 第七步 获取抵押token的移出数量 第八步 将抵押token转换成USD等值 第九步 仓位信息中增加抵押,并扣除保证金 第十步 更改仓位信息(最后一次时间,头寸) 第十一步 验证头寸和流动性 第十二步 根据抵押token 和 头寸,获取最大数量的USDG 第十三步 在仓位中和池子中增加储备金 第十四步 根据多头和空头分别执行不同的逻辑 多头主要是更新保证金和抵押池 空头主要是更新全局空头平均价格和全局空头头寸 第十五步 发送增加仓位,更新仓位的两个事件 reservedAmounts 储备金 guaranteedUsd 保证金 这两个资金估计大家会产生困扰,下面给大家来解释一下 reservedAmounts 代表为每个抵押代币(collateralToken)保留的资金,用于开设或维持多头和空头仓位。这部分资金不能被随意使用,因为它已经与用户的仓位绑定,主要用于保障头寸的潜在亏损。 guaranteedUsd 代表整个系统中所有仓位的 实际保证金总额,即仓位中的资金实际用于支撑用户头寸的部分。它通常是仓位大小减去抵押品的差额,或者是系统需要保证的资金。
最后了解一下清算的最终方法
function liquidatePosition(address _account, address _collateralToken, address _indexToken, bool _isLong, address _feeReceiver) external override nonReentrant {
if (inPrivateLiquidationMode) {
_validate(isLiquidator[msg.sender], 34);
}
// set includeAmmPrice to false to prevent manipulated liquidations
includeAmmPrice = false;
//更新累积融资利率
updateCumulativeFundingRate(_collateralToken, _indexToken);
bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong);
Position memory position = positions[key];
_validate(position.size > 0, 35);
(uint256 liquidationState, uint256 marginFees) = validateLiquidation(_account, _collateralToken, _indexToken, _isLong, false);
// 验证清算状态
_validate(liquidationState != 0, 36);
if (liquidationState == 2) {
// max leverage exceeded but there is collateral remaining after deducting losses so decreasePosition instead
_decreasePosition(_account, _collateralToken, _indexToken, 0, position.size, _isLong, _account);
includeAmmPrice = true;
return;
}
// 收取保证金费用,将其添加到费用储备中
uint256 feeTokens = usdToTokenMin(_collateralToken, marginFees);
feeReserves[_collateralToken] = feeReserves[_collateralToken].add(feeTokens);
emit CollectMarginFees(_collateralToken, marginFees, feeTokens);
// 减少保留金额和担保的USD
_decreaseReservedAmount(_collateralToken, position.reserveAmount);
if (_isLong) {
_decreaseGuaranteedUsd(_collateralToken, position.size.sub(position.collateral));
_decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, marginFees));
}
// 记录清算事件,包括仓位信息和当前价格。
uint256 markPrice = _isLong ? getMinPrice(_indexToken) : getMaxPrice(_indexToken);
emit LiquidatePosition(key, _account, _collateralToken, _indexToken, _isLong, position.size, position.collateral, position.reserveAmount, position.realisedPnl, markPrice);
//如果是空头,同时持仓费用 < 抵押费用 , 仓位抵押减少持仓费用, 同时池子中 增加剩余的数量
if (!_isLong && marginFees < position.collateral) {
uint256 remainingCollateral = position.collateral.sub(marginFees);
_increasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, remainingCollateral));
}
if (!_isLong) {
// 降低全局空头头寸
_decreaseGlobalShortSize(_indexToken, position.size);
}
// 删除仓位
delete positions[key];
// pay the fee receiver using the pool, we assume that in general the liquidated amount should be sufficient to cover
// the liquidation fees
// 支付清算费用给接收者:
_decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, liquidationFeeUsd));
_transferOut(_collateralToken, usdToTokenMin(_collateralToken, liquidationFeeUsd), _feeReceiver);
includeAmmPrice = true;
}
第一步 验证清算者的合法性 第二步 关闭是否包含AMM价格 第三步 更新累积融资利率 第四步 根据参数获取指定的仓位信息 第五步 判断仓位是否满足清算条件,根据liquidationState进行后续处理 第六步 如果保证金接近爆仓边缘,可能会通过降低仓位使得安全 第七步 收取保证金费用,将其添加到费用储备中 第八步 减少保留金额和担保的USD 第九步 记录清算事件,包括仓位信息和当前价格 第十步 如果是空头,仓位抵押中减少持仓费用,增加到池子里面,同时降低全局空头头寸 第十一步 删除仓位 第十二步 降低池子中的金额即清算费用,将清算费用转给清算机器人
清算这边的整个逻辑还是蛮复杂的,最好结合着测试用例一起看一下
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!