Leverage In Defi
1.简介
来学习一下在智能合约中创建杠杆交易,通过学习mystic在cantina的未结束的审计版本,认识如何通过闪电贷或循环借贷的方式在 Mystic 平台上建立或关闭杠杆头寸,创建杠杆交易的调用序列(bundle)。
开始之前,我们先熟悉一下背景:什么是杠杆交易?简单来说,杠杆交易就是用少量的钱(叫做抵押品)去“借”更多的钱,来放大你的投资规模。比如:你有 1000 元,想买价值 2000 元 的东西。你可以用 2 倍杠杆,借 1000 元,这样总共就能控制 2000 元的资产。如果资产涨了,你赚的钱会变多;但如果跌了,你亏的也更多。
2.开仓杠杆
放大用户的初始抵押品来建立杠杆头寸。它会根据情况选择两种方式来实现:
- 闪电贷:如果Mystic平台上有足够的钱可以用,这种方式更快、更省手续费。
- 循环借贷:如果Mystic平台钱不够,就用这种方式,虽然慢一点,但也能凑够杠杆。
先来解释几个参数:
- asset: 要借入的资产,比如USDT
- collateralAsset: 你拿来当抵押品的资产,比如 ETH
- inputAsset: 你实际掏出来的资产,必须是上面两个之一(比如你给 USDT 或 ETH)
- initialCollateralAmount: 你掏出来的抵押品数量(比如 1000 个 USDT)
- targetLeverage: 你想要的杠杆倍数(比如 20000 表示 2 倍,因为这里 10000 = 1 倍)
- slippageTolerance: 滑点
为什么函数参数要分: asset, collateralAsset, inputAsset这三个参数呢?
asset 是指你在杠杆交易中想要借入的资产。比如,你可能用 ETH 作为抵押品来借入 USDT,这里的 asset 就是 USDT
collateralAsset 是你用来抵押的资产。在上面的例子中,ETH 就是 collateralAsset,因为你用它来换取借入 USDT 的资格。
inputAsset 是你实际拿出来启动交易的资产。它可以是 collateralAsset,也可以是 asset,具体取决于你手头有什么。
sequenceDiagram
User->>createOpenLeverageBundle: 我想开一个杠杆头寸
activate User
createOpenLeverageBundle->>createOpenLeverageBundle: 参数检查:抵押品数量大于0,杠杆不可太高或太低
createOpenLeverageBundle->>createOpenLeverageBundle: 计算:根据质押品和杠杆倍数,计算杠杆的资产规模
User->>createOpenLeverageBundle: 授权mysticAdapter,允许动用抵押品和要借入的资产
deactivate User
createOpenLeverageBundle->>Action: 开仓:平台有足够流动性则闪电贷开仓,否则循环借贷开仓
function createOpenLeverageBundle(address asset, address collateralAsset, address inputAsset, uint256 initialCollateralAmount, uint256 targetLeverage, uint256 slippageTolerance) external returns (Call[] memory bundle) {
require(initialCollateralAmount > 0, "Zero collateral amount");
require(targetLeverage > SLIPPAGE_SCALE, "Leverage must be > 1");
require(targetLeverage <= 1000000, "Leverage too high");
uint256 positionSize = initialCollateralAmount * targetLeverage / SLIPPAGE_SCALE;
IERC20(collateralAsset).approve(address(mysticAdapter), type(uint256).max);
IERC20(asset).approve(address(mysticAdapter), type(uint256).max);
// Check if there's enough liquidity for flashloan
if (mysticAdapter.getAvailableLiquidity(asset) > positionSize) {
return _createOpenLeverageBundleWithFlashloan(asset, collateralAsset, inputAsset, initialCollateralAmount, targetLeverage, slippageTolerance);
} else { // loop can still accomodate smaller leverages even with insufficient liqudiity in a pool, there will be a warning in the frontend though
return _createOpenLeverageBundleWithLoops(asset, collateralAsset, inputAsset, initialCollateralAmount, targetLeverage, slippageTolerance);
}
}
2.1.1闪电贷开仓原理
然后我们来讲闪电贷开仓。首先我们来看看:
- 检查你实际掏出来的资产,必须是上面两个之一(asset或collateralAsset)
- 滑点:如果没设置,则使用默认的3%
- collateralValue抵押品价值,也就是initialCollateralAmount
- 总头寸的大小positionSize:它根据抵押品的价值乘杠杆倍数得到(考虑了滑点)
- 要借入的数量borrowAmount:比如你抵押了1000,开2倍杠杆,那么借入的数量就是2000-1000=1000
- 创建交易对:用 asset 和 collateralAsset 生成一个标识(pairKey),方便平台追踪你的借贷和抵押。
function _createOpenLeverageBundleWithFlashloan(address asset, address collateralAsset, address inputAsset, uint256 initialCollateralAmount, uint256 targetLeverage, uint256 slippageTolerance) internal returns (Call[] memory bundle) {
require(inputAsset == collateralAsset || inputAsset == asset, "Input asset must be the same as collateral asset or asset");
uint256 slippage = slippageTolerance == 0 ? DEFAULT_SLIPPAGE : slippageTolerance;
uint256 collateralValue = initialCollateralAmount; //inputAsset == collateralAsset ? initialCollateralAmount : getQuote(inputAsset, collateralAsset, initialCollateralAmount);
uint256 positionSize = collateralValue * targetLeverage / SLIPPAGE_SCALE;
uint256 borrowAmount = positionSize - collateralValue; //getQuote(collateralAsset, asset, positionSize - collateralValue);
bytes32 pairKey = getPairKey(asset, collateralAsset);
[...]
}
来看具体流程。它有两个bundle,你可以把它想象成合约会执行的操作。mainBundle包含2 个步骤,启动整个流程。flashloanCallbackBundle包含5 个步骤,处理借来的钱。
- 计算最终总共要借入的钱:原本要借入的钱+手续费+1(除法四舍五入截断,避免这个情况,加上1)
- 计算最终的抵押品数量totalCollateralAmount,后续例子中说明。
Call[] memory mainBundle = new Call[](2);
Call[] memory flashloanCallbackBundle = new Call[](5);
uint256 totalBorrowAmount = borrowAmount + mysticAdapter.flashLoanFee(borrowAmount) + 1; // +1 for rounding buffer
uint256 totalCollateralAmount = 0; // this is meant to force accuracy of collateral (and reduce discrepancy due to swap output)
if (inputAsset == collateralAsset) {
// When input is collateral: direct calculation, Calculate total collateral after leverage (initial + converted borrowed)
totalCollateralAmount = collateralValue + getQuote(asset, collateralAsset, borrowAmount);
} else {
// When input is borrowing asset: calculate total borrowing first
totalCollateralAmount = getQuote(asset, collateralAsset, positionSize);
}
准备好数据之后,开始准备闪电贷bundle。假设我们要用资产A作为抵押,借入资产B,假设你提供了 1000 A,想用 2 倍杠杆,目标控制 2000 的头寸,需要借 1000 B。下面依次是5个bundle:
- 把从Mystic闪电贷借来的 B 转走:先转到swap平台(maverickAdapter),准备换成 A。
- 进行Swap:把B换成A
- 把换来的 A 转回去:从swap平台转到 Mystic 平台(mysticAdapter),准备存入。
- 存入A:把你原来的 1000 A(你提供的)加上换来的 1000 A,总共 2000 A,存进 Mystic 作为抵押品
- 借 B 还闪电贷:用 2000 A 作抵押,从 Mystic 借 1000 B(加上一点手续费,比如 1001 B),用来还闪电贷。
// Compressed callback bundle creation
flashloanCallbackBundle[0] = _createERC20TransferCall(asset, address(maverickAdapter), type(uint256).max);
flashloanCallbackBundle[1] = _createMaverickSwapCall(asset, collateralAsset, type(uint256).max, 0, slippage, false);
flashloanCallbackBundle[2] = _createERC20TransferFromCall(collateralAsset, address(this), address(mysticAdapter), type(uint256).max);
flashloanCallbackBundle[3] = _createMysticSupplyCall( collateralAsset, type(uint256).max, msg.sender);
flashloanCallbackBundle[4] = _createMysticBorrowCall(asset, totalBorrowAmount, VARIABLE_RATE_MODE, msg.sender, address(mysticAdapter));
mainBundle[0]是借1000B,然后mainBundle[1]进行那5步操作。最后更新头寸情况。
// Compressed main bundle creation
mainBundle[0] = inputAsset == collateralAsset ? _createERC20TransferFromCall(collateralAsset,msg.sender,address(this),initialCollateralAmount) : _createERC20TransferFromCall(asset,msg.sender,address(maverickAdapter),initialCollateralAmount);
mainBundle[1] = _createMysticFlashloanCall(asset,borrowAmount,false,abi.encode(flashloanCallbackBundle));
bundler.multicall(mainBundle);
updatePositionTracking(pairKey, totalBorrowAmount, totalCollateralAmount, msg.sender, true);
emit BundleCreated(msg.sender, keccak256("OPEN_LEVERAGE"), mainBundle.length);
emit LeverageOpened(msg.sender, collateralAsset, asset, initialCollateralAmount, targetLeverage, totalCollaterals[pairKey], totalBorrows[pairKey]);
return mainBundle;
2.1.2闪电贷开仓外围函数
updatePositionTracking()
:更新个人和全局的借款、抵押情况
2.1.3闪电贷开仓例子
看看整体流程图例子:
asset:借入的资产(这里是USDT)。
collateralAsset:抵押资产(这里是ETH)。
inputAsset:用户输入的资产(这里是ETH)。
initialCollateralAmount:初始抵押金额(这里是1 ETH)。
targetLeverage:目标杠杆(这里是3倍,即 SLIPPAGE_SCALE * 3 = 30000)。
if (inputAsset == collateralAsset) { // 👈👈👈👈👈👈 对应这个分支
// 本身输入的collateralValue数量ETH + 到交易所用借入borrowAmount数目的USDT换成ETH
totalCollateralAmount = collateralValue + getQuote(asset, collateralAsset, borrowAmount);
} else {
// When input is borrowing asset: calculate total borrowing first
totalCollateralAmount = getQuote(asset, collateralAsset, positionSize);
}
sequenceDiagram
User->>Mystic: 我有1ETH,帮我开3倍杠杆做多ETH,闪电贷借USDT
Mystic->>Mystic: 3倍杠杆,需要闪电贷借4_000的USDT(假设1ETH=2_000USDT)
Mystic->>Mystic: User得到成功闪电贷借到4_000的USDT
Mystic->>Maverick: 把4_000的USDT换成ETH
Maverick->>Mystic: 给你2ETH
Mystic->>Mystic: 现在3ETH可用金额。存到Mystic,借出4_000USDT
Mystic->>Mystic: 还4_000的USDT闪电贷
Mystic->>User: 最终状态:用户抵押了1ETH,一共3ETH的头寸,从Mystic借出了4_000USDT债务
另外一种情况:
asset:借入的资产(这里是USDT)。
collateralAsset:抵押资产(这里是ETH)。
inputAsset:用户输入的资产(这里是USDT)。
initialCollateralAmount:初始抵押金额(这里是1 ETH)。
targetLeverage:目标杠杆(这里是3倍,即 SLIPPAGE_SCALE * 3 = 30000)。
if (inputAsset == collateralAsset) {
// When input is collateral: direct calculation, Calculate total collateral after leverage (initial + converted borrowed)
totalCollateralAmount = collateralValue + getQuote(asset, collateralAsset, borrowAmount);
} else { // 👈👈👈👈👈👈 对应这个分支
// 本身输入用来抵押的资产 和 借入的资产一样,都是USDT,那么直接把他们都换成ETH
totalCollateralAmount = getQuote(asset, collateralAsset, positionSize);
}
sequenceDiagram
User->>Mystic: 我有2_000USDT,帮我开3倍杠杆做多ETH,闪电贷借USDT
Mystic->>Mystic: 3倍杠杆,需要闪电贷借4_000的USDT(假设1ETH=2_000USDT)
Mystic->>mysticAdapter: 哥们,流动性足够进行闪电贷吗
mysticAdapter->>Mystic: 够
Mystic->>Mystic: User得到成功闪电贷借到4_000的USDT
Mystic->>Maverick: 把2_000和4_000的USDT换成ETH
Maverick->>Mystic: 给你3ETH
Mystic->>Mystic: 现在3ETH可用。存到Mystic,借出4_000USDT
Mystic->>Mystic: 还4_000的USDT闪电贷
Mystic->>User: 最终状态:用户抵押了2_000USDT,一共3ETH的头寸,从Mystic借出了4_000USDT债务
上面2个例子,用户做多ETH。用户抵押的价值是2000$,借了4000$的债务(手动平仓的时候需要固定还4000USDT):
-
如果未来ETH暴涨到4000$,那么3*4000=12,000$,还4000USDT的债务,盈利12000-2000-4000=6000$。
-
如果未来ETH下跌,只要3个ETH的总价值下跌即将到达2000$的抵押量(比如1500$是阈值),那么就可以清算,否则会产生坏账。比如:
- 初始贷款价值比(LTV) = 债务 / 抵押价值 = 4,000 / (3 ETH * 2,000) = 4,000 / 6,000 = 66.67%。【或者换个说法:抵押率。6000/4000=150%】
- 现在贷款价值比(LTV) = 债务 / 抵押价值 = 4,000 / (3 ETH * 1,500) = 4,000 / 4,500 = 88.9%。假设88%就可以清算。【或者换个说法:抵押率。4500/4000=112.5%。】
- 1500~2000之间这个缓冲值是风险区间,由项目方自行决定。此时系统清算:把3个ETH换成4500USDT,偿还4000$的债务,剩下500$扣除清算奖励和手续费之后,再还给用户。也就是说,用户在ETH下跌的时候,本金从2000变成了不到500。
2.2.1.循环开仓原理
该函数在无法通过单次闪贷完成杠杆操作时(如底层池流动性不足),通过多次循环操作逐步构建杠杆头寸。其核心逻辑是:循环借款 → 兑换为抵押资产 → 重复抵押 → 再次借款,通过迭代放大用户的抵押仓位。
函数的开始:检查参数、初始化一些变量
function _createOpenLeverageBundleWithLoops(address asset, address collateralAsset, address inputAsset, uint256 initialCollateralAmount, uint256 targetLeverage, uint256 slippageTolerance) internal returns (Call[] memory bundle) {
// 非常耗费gas,限制在25次循环(4倍杠杆),并且极其无效,只有在池无法完成闪电贷时才使用
// 我们理解迭代不等于杠杆,但为了限制gas,我们假设迭代==循环,而不是1-ltv**(n+1)/1-ltv,其中n是迭代
require(inputAsset == collateralAsset, "Input asset must be the same as collateral asset");
uint256 slippage = slippageTolerance == 0 ? DEFAULT_SLIPPAGE : slippageTolerance;
uint256 ltv = mysticAdapter.getAssetLtv(collateralAsset);
uint8 loop = 20;
bytes32 pairKey = getPairKey(asset, collateralAsset);
require(ltv > 0, "Collateral asset has no LTV");
[...]
}
mainBundle[0]: 将用户的初始抵押资产从用户地址转移到 mysticAdapter。mainBundle[1]: 调用 mysticAdapter 的 mysticSupply 函数,将初始抵押资产存入协议。然后更新抵押情况。
Call[] memory mainBundle = new Call[](2+ loop*4);
mainBundle[0] = _createERC20TransferFromCall(inputAsset,msg.sender,address(mysticAdapter),initialCollateralAmount);
mainBundle[1] = _createMysticSupplyCall( collateralAsset, type(uint256).max, msg.sender);
uint256 newCollateral = initialCollateralAmount;
totalCollaterals[pairKey] += newCollateral;
totalCollateralsPerUser[pairKey][msg.sender] += newCollateral;
通过 for 循环(最多 20 次)执行以下步骤(假设质押质押ETH,借USDT,做多ETH):
- 借入资产:调用 _createMysticBorrowCall,从协议借入最大可能数量的 asset(借入金额受 LTV 限制)。【借USDT)
- 资产交换:调用 _createMaverickSwapCall,将借入的 asset 兑换为 collateralAsset。【将USDT转化为ETH)
- 转移抵押资产:调用 _createERC20TransferFromCall,将兑换得到的 collateralAsset 转移到 mysticAdapter。【将ETH转移到mysticAdapter】
- 存入抵押资产:调用 _createMysticSupplyCall,将新的 collateralAsset 存入协议,增加抵押。【将ETH存入Mystic】
每次循环都更新状态:新的抵押数量,新的借款数量。然后检查当前杠杆倍数是否接近目标杠杆(允许 10% 误差,即 leverage >= targetLeverage * 0.9),若满足则提前退出循环。
for (uint8 i=0; i< loop; i++){
uint256 idx = 2 + i * 4;
mainBundle[idx] = _createMysticBorrowCall(asset, type(uint256).max, VARIABLE_RATE_MODE, msg.sender, address(maverickAdapter));
mainBundle[idx+1] = _createMaverickSwapCall(asset, collateralAsset, type(uint256).max, 0, slippage, false);
mainBundle[idx+2] = _createERC20TransferFromCall(collateralAsset, address(this), address(mysticAdapter), type(uint256).max);
mainBundle[idx+3] = _createMysticSupplyCall( collateralAsset, type(uint256).max, msg.sender);
newCollateral = newCollateral * ltv / SLIPPAGE_SCALE;
uint256 newBorrow = getQuote(collateralAsset, asset, newCollateral) * ltv / SLIPPAGE_SCALE;
updatePositionTracking(pairKey, newBorrow, newCollateral, msg.sender, true);
uint leverage = (totalCollateralsPerUser[pairKey][msg.sender]) * SLIPPAGE_SCALE / (totalCollateralsPerUser[pairKey][msg.sender] - totalBorrowsPerUser[pairKey][msg.sender]);
if (leverage >= (targetLeverage * 9000) / SLIPPAGE_SCALE) break; // break if leverage gotten is in similar range as expected 10% error margin 4 -> 3.6 is fine
}
最后验证杠杆是否安全(借入金额小于抵押金额),然后执行。
require(totalBorrowsPerUser[pairKey][msg.sender] < totalCollateralsPerUser[pairKey][msg.sender], "Leverage too high");
bundler.multicall(mainBundle);
emit BundleCreated(msg.sender, keccak256("OPEN_LEVERAGE"), mainBundle.length);
emit LeverageOpened(msg.sender, collateralAsset, asset, initialCollateralAmount, targetLeverage, totalCollaterals[pairKey], totalBorrows[pairKey]);
return mainBundle;
2.2.2.循环开仓例子
- 假设:抵押资产是 ETH,借入资产是 USDT,目标是 3 倍做多 ETH,当前价格 1 ETH = 2000 USDT。我们假设 LTV(贷款价值比)为 80%(即 0.8)
- 杠杆目标:
- 初始抵押:1 ETH,价值 2000 USDT。
- 3 倍杠杆意味着总头寸价值为:3*2000= 6000 USDT。相当于总抵押的ETH为3个。因此需要借入6000-2000=4000USDT。
- 最终状态:用户抵押1ETH,3个ETH的债务,借4000USDT。在本处,大于杠杆倍数的90%就停止。
累计抵押(资产) | 新增借出 | 累计借出(负债) | 杠杆倍数 | 本轮负债可换抵押资产 | |
---|---|---|---|---|---|
第1次借贷 | 1ETH | 2000*0.8=1600u | 1600u | 1600/2000=0.8 | 1600u=0.8ETH |
第2次借贷 | 1+0.8=1.8ETH | 1600*0.8=1280u | 1600+1280=2,880 | 2880/2000=1.44 | 1280u=0.64ETH |
第3次借贷 | 1+0.8+0.64=2.44 ETH | 1280*0.8=1,024 | 1600+1280+1024=3,904 | 3904/2000=1.952 | 1024u=0.512ETH |
第4次借贷 | 1+0.8+0.64+0.512=2.952 ETH | 1024*0.8=819.2 | 1600+1280+1024+819.2=4,723.2 | 4,723.2/2000=2.362 | 819.2u=0.4096ETH |
第5次借贷 | 1+0.8+0.64+0.512+0.4096=3.362 | 819.2*0.8=655.36 | 1600+1280+1024+819.2+655.36=5,378.56 | 5,378.56 /2000=2.689 | 655.36u=0.3277ETH |
第6次借贷 | 1+0.8+0.64+0.512+0.4096+0.3277=3.689 | 655.36*0.8=524.288 | 1600+1280+1024+819.2+655.36+524.288=5,902.848 | 5,902.848/2000=2.951 | 大于目标杠杆倍数3*0.9,结束 |
用图来表示:
sequenceDiagram
User->>Mystic: 我有1ETH,帮我开3倍杠杆做多ETH,闪电贷借USDT
Mystic->>mysticAdapter: 够流动性闪电贷开仓吗
mysticAdapter->>Mystic: 不够,你自己循环开仓吧
Mystic->>Mystic: ok。1ETH的抵押,可以借出1600USDT,1600USDT再换成0.8ETH。目前杠杆倍数为1600/2000=0.8
Mystic->>Mystic: 不够3*0.9。0.8ETH的新增抵押,可以借出1280USDT,1280USDT再换成0.64ETH。目前杠杆倍数为2880/2000=1.44=0.8
Mystic->>Mystic: [...]
Mystic->>Mystic: 某一轮,使得杠杆倍数大于3*0.9或者达到20轮了
Mystic->>User: 结束了,你的杠杆情况为[...]
3.关仓杠杆
3.1.1闪电贷关仓原理
首先获取到key,然后计算debtToClose:如果传入的是uint256.max或者输入的debtToClose过大,那么都是调整为目前所持有的借款数量。
function createCloseLeverageBundle(address asset, address collateralAsset, uint256 debtToClose) external returns (Call[] memory bundle) {
bytes32 pairKey = getPairKey(asset, collateralAsset);
if(debtToClose == type(uint256).max || totalBorrowsPerUser[pairKey][msg.sender] <= debtToClose) {
debtToClose = totalBorrowsPerUser[pairKey][msg.sender];
}
require(debtToClose > 0, "no debt found");
// Check if there's enough liquidity for flashloan or if we're closing a small position
if (mysticAdapter.getAvailableLiquidity(asset) > debtToClose) {
return _createCloseLeverageBundleWithFlashloan(asset, collateralAsset, debtToClose);
} else {
return _createCloseLeverageBundleWithLoops(asset, collateralAsset, debtToClose);
}
}
然后我们来看看用闪电贷来关仓。首先计算一系列需要用到的值,比如debtToCover、collateralForRepayment等
function _createCloseLeverageBundleWithFlashloan(address asset, address collateralAsset, uint256 debtToClose) internal returns (Call[] memory bundle) {
Call[] memory mainBundle = new Call[](5);
Call[] memory flashloanCallbackBundle = new Call[](4);
bytes32 pairKey = getPairKey(asset, collateralAsset);
uint256 debtToCover = debtToClose;
uint256 totalBorrowAmount = debtToCover + mysticAdapter.flashLoanFee(debtToCover) + 1; // +1 for rounding buffer, plus new fee
uint256 collateralForRepayment = (totalCollateralsPerUser[pairKey][msg.sender] * debtToClose) / totalBorrowsPerUser[pairKey][msg.sender];
collateralForRepayment = collateralForRepayment > totalCollateralsPerUser[pairKey][msg.sender]? totalCollateralsPerUser[pairKey][msg.sender]: collateralForRepayment; //safeguard to avoid overflow
}
flashloanCallbackBundle包含闪电贷回调中的操作,顺序执行以下步骤:
- [0]:偿还指定金额的债务
- [1]:从 mysticAdapter(此项目) 提取 collateralForRepayment 数量的抵押品,发送到 maverickAdapter(swap平台)。
- [2]:在maverickAdapter平台进行swap换成asset代币
- [3]:将swap得到的asset代币转到mysticAdapter
// Compressed callback bundle creation
flashloanCallbackBundle[0] = _createMysticRepayCall(asset, debtToCover, VARIABLE_RATE_MODE, msg.sender);
flashloanCallbackBundle[1] = _createMysticWithdrawCall(collateralAsset, collateralForRepayment, msg.sender, address(maverickAdapter));
flashloanCallbackBundle[2] = _createMaverickSwapCall(collateralAsset, asset, collateralForRepayment, totalBorrowAmount , 0, false);
flashloanCallbackBundle[3] = _createERC20TransferFromCall(asset, address(this), address(mysticAdapter), type(uint256).max);
flashloanCallbackBundle包含如下步骤:
- [0]:借入 debtToCover 数量的 asset,并指定 flashloanCallbackBundle 作为回调。
- [1]:将asset转移到 maverickAdapter,准备后续swap。
- [2]:将剩余的 asset 换成 collateralAsset,
- [3]:将换得的 collateralAsset 转移到当前合约。
- [4]:将 collateralAsset 从合约转移给用户。
// Compressed main bundle creation
mainBundle[0] = _createMysticFlashloanCall(asset,debtToCover,false, abi.encode(flashloanCallbackBundle));
mainBundle[1] = _createERC20TransferCall(asset, address(maverickAdapter), type(uint256).max);
mainBundle[2] = _createMaverickSwapCall(asset, collateralAsset, type(uint256).max, 0 , 0, false);
mainBundle[3] = _createERC20TransferCall(collateralAsset, address(this), type(uint256).max);
mainBundle[4] = _createERC20TransferFromCall(collateralAsset, address(this), msg.sender, type(uint256).max);
执行调用,更新头寸
bundler.multicall(mainBundle);
updatePositionTracking(pairKey, debtToCover, collateralForRepayment, msg.sender, false);
emit BundleCreated(msg.sender, keccak256("CLOSE_LEVERAGE"), mainBundle.length);
emit LeverageClosed(msg.sender, collateralAsset, asset, collateralForRepayment, totalCollaterals[pairKey], totalBorrows[pairKey]);
return mainBundle;
3.1.2闪电贷关仓例子
用图来说明整个流程:
sequenceDiagram
User->>Mystic: 我抵押了1ETH,拥有3ETH的头寸,4_000 USDT的负债,现在我想close这个头寸
Mystic->>mysticAdapter: 哥们,流动性足够进行闪电贷吗
mysticAdapter->>Mystic: 够
Mystic->>Mystic: User闪电贷借到4_000USDT偿还债务
Mystic->>Maverick: 我有3ETH,我要换成USDT
Maverick->>Mystic: 成功把3ETH换成6_000USDT
Mystic->>Mystic: 还款4_000闪电贷,还剩2_000USDT
Mystic->>Maverick: 我有2_000USDT,帮我换成ETH
Maverick->>Mystic: 成功把2_000USDT换成1ETH
Mystic->>User: 还你1ETH,现在你的头寸更新了
另外说明一下,这是做多的行为,然后现在关仓。如果ETH涨,做多成功,那么3ETH比如可能去到价值1_000_000USDT,那么还固定的4_000UST负债,那么爆赚。
3.2.1循环关仓原理
和之前一样,先准备好一些要用到的参数
function _createCloseLeverageBundleWithLoops(address asset, address collateralAsset, uint256 debtToClose) internal returns (Call[] memory bundle) {
bytes32 pairKey = getPairKey(asset, collateralAsset);
uint256 collateralToWithdraw = (totalCollateralsPerUser[pairKey][msg.sender] * debtToClose) / totalBorrowsPerUser[pairKey][msg.sender];
uint256 leverage = (totalCollateralsPerUser[pairKey][msg.sender]) / (totalCollateralsPerUser[pairKey][msg.sender] - totalBorrowsPerUser[pairKey][msg.sender]);
uint256 ltv = mysticAdapter.getAssetLtv(collateralAsset);
uint8 numLoops = 20;
uint256 remainingDebt = debtToClose;
uint256 borrowable = mysticAdapter.getWithdrawableLiquidity(msg.sender, collateralAsset);
Call[] memory mainBundle = new Call[](numLoops * 4+1);
}
然后mainBundle有如下步骤:
- [0]:从 mysticAdapter(此平台) 提取最大数量的抵押品头寸,发送到 maverickAdapter(swap平台)。
- [1]:通过 maverickAdapter(swap平台) 将提取的抵押品 (collateralAsset) 换成借入资产 (asset)
- [2]:将换得的asset从当前合约转移到 mysticAdapter(此平台)。
- [3]:调用 mysticAdapter.mysticRepay 将asset还给用户
// For each loop iteration
for (uint8 i = 0; i < numLoops; i++) {
uint256 baseIndex = i * 4;
mainBundle[baseIndex + 0] = _createMysticWithdrawCall(collateralAsset, type(uint256).max, msg.sender, address(maverickAdapter));
mainBundle[baseIndex + 1] = _createMaverickSwapCall(collateralAsset, asset, type(uint256).max, 0, 0, false);
mainBundle[baseIndex + 2] = _createERC20TransferFromCall(asset, address(this), address(mysticAdapter), type(uint256).max);
mainBundle[baseIndex + 3] = _createMysticRepayCall(asset, type(uint256).max, VARIABLE_RATE_MODE, msg.sender);
remainingDebt = remainingDebt > borrowable? remainingDebt - borrowable:0;
borrowable = borrowable * SLIPPAGE_SCALE/ ltv;
if(remainingDebt == 0) break;
}
最后执行并更新债务
mainBundle[numLoops * 4] = _createMysticWithdrawCall(collateralAsset, type(uint256).max, msg.sender, msg.sender);
uint spentCollateral = getQuote(asset, collateralAsset, debtToClose - remainingDebt);
bundler.multicall(mainBundle);
updatePositionTracking(pairKey, debtToClose - remainingDebt, spentCollateral, msg.sender, false);
emit BundleCreated(msg.sender, keccak256("CLOSE_LEVERAGE_LOOPS"), mainBundle.length);
emit LeverageClosed(msg.sender, collateralAsset, asset, collateralToWithdraw, totalCollaterals[pairKey], totalBorrows[pairKey]);
return mainBundle;
3.2.2循环关仓例子
用户拥有 3 ETH 的借款头寸,抵押了 2000 USDT,当前价格 1 ETH = 2000 USDT,表示 3 倍做多。
累计抵押(资产) | 新增提取 | 提取等价asset | remainingDebt | |
---|---|---|---|---|
第1次借贷 | 2000u | 2000*0.8=1600u | 1600u=0.8ETH | 4000-1600=2400u |
第2次借贷 | 1600u | 1600*0.8=1,280u | 1280u=0.64ETH | 2400-1280=1,120 u |
第2次借贷 | 1280u | 1280*0.8=1,024u | 1024u=0.512ETH | 1120-1024<0,结束 |
最后累计0.8+0.64+0.512=1.952 ETH,全部给用户。并且将ETH换成对应的USDT,更新债务。
4.更新杠杆
调整你已经开好的杠杆头寸,并且只能用闪电贷的方式,不可以用循环借贷的方式。
首先一些检查和准备一些要用到的数据:
function updateLeverageBundle(
address asset,
address collateralAsset,
uint256 newTargetLeverage,
uint256 slippageTolerance
) external returns (Call[] memory bundle) {
require(newTargetLeverage > SLIPPAGE_SCALE, "Leverage must be > 1");
require(newTargetLeverage <= 1000000, "Leverage too high"); // Max 100x
bytes32 pairKey = getPairKey(asset, collateralAsset);
uint256 slippage = slippageTolerance == 0 ? DEFAULT_SLIPPAGE : slippageTolerance;
uint256 currentCollateral = totalCollateralsPerUser[pairKey][msg.sender]; // 当前的抵押
uint256 currentBorrow = totalBorrowsPerUser[pairKey][msg.sender]; // 当前的借款
require(currentCollateral > 0 && currentBorrow > 0, "No existing position");
// 当前的杠杆倍数
uint256 currentLeverage = (currentCollateral * SLIPPAGE_SCALE) / (currentCollateral - currentBorrow);
// 计算新的借款数量
uint256 newBorow = currentBorrow * (newTargetLeverage - SLIPPAGE_SCALE) * currentLeverage / (newTargetLeverage * (currentLeverage - SLIPPAGE_SCALE)); //getQuote(collateralAsset, asset, currentCollateral * (newTargetLeverage - SLIPPAGE_SCALE) / newTargetLeverage);
// 计算新借款与旧借款的diff
int256 borrowDelta = int256(newBorow) - int256(currentBorrow);
}
如果borrowDelta > 0,那么是增加杠杆倍数。计算需要额外借款的数量additionalBorrowAmount、总借款的数量totalBorrowAmount。然后开始五部曲更新债务:
- [0]:闪电贷从maverickAdapter借款
- [1]:在Maverick平台,将借款的asset换成抵押品collateralAsset
- [2]:将换到的collateralAsset发送回mysticAdapter
- [3]:将换到的collateralAsset存给用户
- [4]:用户用新的抵押量借出asset,然后还款闪电贷
// Create appropriate bundles based on the operation type
Call[] memory mainBundle;
Call[] memory flashloanCallbackBundle;
if (borrowDelta > 0) {
// INCREASE LEVERAGE CASE
uint256 additionalBorrowAmount = uint256(borrowDelta);
uint totalBorrowAmount = additionalBorrowAmount + mysticAdapter.flashLoanFee(additionalBorrowAmount) + 1;
mainBundle = new Call[](1);
flashloanCallbackBundle = new Call[](5);
flashloanCallbackBundle[0] = _createERC20TransferCall(asset, address(maverickAdapter), type(uint256).max);
flashloanCallbackBundle[1] = _createMaverickSwapCall(asset, collateralAsset, type(uint256).max, 0, slippage, false);
flashloanCallbackBundle[2] = _createERC20TransferFromCall(collateralAsset, address(this), address(mysticAdapter), type(uint256).max);
flashloanCallbackBundle[3] = _createMysticSupplyCall( collateralAsset, type(uint256).max, msg.sender);
flashloanCallbackBundle[4] = _createMysticBorrowCall(asset, totalBorrowAmount, VARIABLE_RATE_MODE, msg.sender, address(mysticAdapter));
mainBundle[0] = _createMysticFlashloanCall(asset,additionalBorrowAmount,false,abi.encode(flashloanCallbackBundle));
updatePositionTracking(pairKey, additionalBorrowAmount, 0, msg.sender, true);
如果是降低杠杆,跟之前关仓类似:
- [0]:偿还指定金额的债务
- [1]:从 mysticAdapter(此项目) 提取 collateralForRepayment 数量的抵押品,发送到 maverickAdapter(swap平台)。
- [2]:在maverickAdapter平台进行swap换成asset代币
- [3]:将swap得到的asset代币转到mysticAdapter
} else if (borrowDelta < 0) {
uint256 repayAmount = uint256(-borrowDelta);
uint256 collateralForRepayment = (totalCollateralsPerUser[pairKey][msg.sender] * repayAmount) / totalBorrowsPerUser[pairKey][msg.sender];
mainBundle = new Call[](5);
flashloanCallbackBundle = new Call[](4);
flashloanCallbackBundle[0] = _createMysticRepayCall(asset, repayAmount, VARIABLE_RATE_MODE, msg.sender);
flashloanCallbackBundle[1] = _createMysticWithdrawCall(collateralAsset, collateralForRepayment, msg.sender, address(maverickAdapter));
flashloanCallbackBundle[2] = _createMaverickSwapCall(collateralAsset, asset, collateralForRepayment, repayAmount , 0, false);
flashloanCallbackBundle[3] = _createERC20TransferFromCall(asset, address(this), address(mysticAdapter), type(uint256).max);
// Compressed main bundle creation
mainBundle[0] = _createMysticFlashloanCall(asset,repayAmount,false, abi.encode(flashloanCallbackBundle));
mainBundle[1] = _createERC20TransferCall(asset,address(maverickAdapter), type(uint256).max);
mainBundle[2] = _createMaverickSwapCall(asset, collateralAsset, type(uint256).max, 0 , 0, false);
mainBundle[3] = _createERC20TransferCall(collateralAsset,address(this), type(uint256).max);
mainBundle[4] = _createERC20TransferFromCall(collateralAsset, address(this), msg.sender, type(uint256).max);
updatePositionTracking(pairKey, repayAmount, 0, msg.sender, false);
} else {
revert("No changes to position");
}
bundler.multicall(mainBundle);
emit BundleCreated(msg.sender, keccak256("UPDATE_LEVERAGE"), mainBundle.length);
emit LeverageUpdated(msg.sender, collateralAsset, asset, currentCollateral, currentLeverage, newTargetLeverage, totalCollaterals[pairKey], totalBorrows[pairKey]);
return mainBundle;