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: 146/183
Findings: 1
Award: $3.82
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Circolors
Also found by: 0x175, 0x486776, 0xAlix2, 0xSecuri, 0xShitgem, 0xfox, 0xlemon, 0xnilay, 3th, 4rdiii, Aamir, Al-Qa-qa, AlexCzm, Egis_Security, Evo, Honour, Infect3d, Josh4324, Limbooo, Mahmud, SBSecurity, TheSchnilch, ahmedaghadi, alix40, amaron, bbl4de, bhilare_, btk, carrotsmuggler, cinderblock, d3e4, dimulski, dinkras, ducanh2706, iamandreiski, itsabinashb, ke1caM, ljj, sashik_eth, shaflow2, steadyman, web3km, y4y
3.8221 USDC - $3.82
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134
Users are unable to withdraw their kerosine token deposited into their notes, the vault manager is compatible with several vaults such as weth vault and the wsteth vault. The implementation of the withdraw function in the vault manager does not take into account that the kerosine vault does not have an oracle function like other non kerosine vaults.
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); @> uint value = amount * _vault.assetPrice() @> * 1e18 @> / 10**_vault.oracle().decimals() @> / 10**_vault.asset().decimals(); if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
Line 146 to 149 of the withdraw function which is pointed above shows that the withdrawal function tries to call the vault.oracle().decimals()
, kerosine vaults do not have an oracle and when a kerosine vaults tries to call an oracle function the transaction will fail.
// This is a forked test address user3 = address(0x8f10ae3ffD993bFb0A71E58E1bb606439dD5b301); function test_failedWithdrawForUnboundedKerosine() public { vm.deal(user3, 100 ether); uint256 amount = 10e18; Licenser licenser = Licenser(MAINNET_VAULT_MANAGER_LICENSER); vm.prank(MAINNET_OWNER); licenser.add(address(contracts.vaultManager)); vm.prank(MAINNET_OWNER); contracts.kerosineManager.add(address(contracts.unboundedKerosineVault)); vm.startPrank(user3); uint256 id = DNft(MAINNET_DNFT).mintNft{value: 1 ether}(address(user3)); contracts.vaultManager.addKerosene(id, address(contracts.unboundedKerosineVault)); ERC20(MAINNET_KEROSENE).approve(address(contracts.vaultManager), amount); contracts.vaultManager.deposit(id, address(contracts.unboundedKerosineVault), amount); vm.roll(block.timestamp + 100); vm.expectRevert(); contracts.vaultManager.withdraw(id, address(contracts.unboundedKerosineVault), amount, user3); vm.stopPrank(); }
Proof of Code Response
https://gist.github.com/Josh4324/2c754c6aa57f7307a4a0226b091a0f39
Manual Review
Another withdraw function can be created for the unbounded kerosine vault or the current withdraw function can be adjusted to take into account the unbounded kerosine vault.
DoS
#0 - c4-pre-sort
2024-04-26T21:46:51Z
JustDravee marked the issue as duplicate of #1048
#1 - c4-pre-sort
2024-04-28T18:38:39Z
JustDravee marked the issue as duplicate of #830
#2 - c4-pre-sort
2024-04-29T08:44:44Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-11T20:04:40Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: Circolors
Also found by: 0x175, 0x486776, 0xAlix2, 0xSecuri, 0xShitgem, 0xfox, 0xlemon, 0xnilay, 3th, 4rdiii, Aamir, Al-Qa-qa, AlexCzm, Egis_Security, Evo, Honour, Infect3d, Josh4324, Limbooo, Mahmud, SBSecurity, TheSchnilch, ahmedaghadi, alix40, amaron, bbl4de, bhilare_, btk, carrotsmuggler, cinderblock, d3e4, dimulski, dinkras, ducanh2706, iamandreiski, itsabinashb, ke1caM, ljj, sashik_eth, shaflow2, steadyman, web3km, y4y
3.8221 USDC - $3.82
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L269 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.sol#L60 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L50
Users are unable to mint dyad stable coin when they add kerosine vault to their notes in addition to other non kerosine vaults. When users do not add kerosine vault to their notes, they are able to mint dyad stable-coins without any issues. This issue can be traced to the getKeroseneValue
function.
function getKeroseneValue( uint id ) public view returns (uint) { uint totalUsdValue; uint numberOfVaults = vaultsKerosene[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaultsKerosene[id].at(i)); uint usdValue; if (keroseneManager.isLicensed(address(vault))) { @> usdValue = vault.getUsdValue(id); } totalUsdValue += usdValue; } return totalUsdValue; }
function getUsdValue( uint id ) public view returns (uint) { @> return id2asset[id] * assetPrice() / 1e8; }
function assetPrice() public view override returns (uint) { uint tvl; address[] memory vaults = kerosineManager.getVaults(); uint numberOfVaults = vaults.length; for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[i]); tvl += vault.asset().balanceOf(address(vault)) @> * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) @> / (10**vault.oracle().decimals()); } uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
When the user tries to mint dyad tokens, the mintDyad
function calls the getKeroseneValue
at a point in order to calculate the totalUsdValue for all kerosene vault. The problem is that after the keroseneManager checks that the vault is licensed, the function vault.getUsdValue(id) was called in order to get the usdValue of the vault which then calls the assetPrice() function. This function makes another call to the vault oracle but kerosene vaults do not have an oracle function.
Only non kerosine vaults have an oracle function because it is needed to get the current price of the underlying assets. The transaction will revert when any kerosine vault tries to call the oracle function which does not exists in kerosine vaults contract.
// This is a forked test address user3 = address(0x8f10ae3ffD993bFb0A71E58E1bb606439dD5b301); function test_MintFailed() public { vm.deal(user3, 100 ether); uint256 amount = 10e18; uint256 mintAmount = 2000 ether; Licenser licenser = Licenser(MAINNET_VAULT_MANAGER_LICENSER); vm.prank(MAINNET_OWNER); licenser.add(address(contracts.vaultManager)); vm.prank(MAINNET_OWNER); contracts.kerosineManager.add(address(contracts.unboundedKerosineVault)); vm.startPrank(user3); uint256 id = DNft(MAINNET_DNFT).mintNft{value: 1 ether}(address(user3)); contracts.vaultManager.addKerosene(id, address(contracts.unboundedKerosineVault)); ERC20(MAINNET_KEROSENE).approve(address(contracts.vaultManager), amount); contracts.vaultManager.deposit(id, address(contracts.unboundedKerosineVault), amount); contracts.vaultManager.add(id, address(contracts.ethVault)); ERC20(MAINNET_WETH).approve(address(contracts.vaultManager), amount); contracts.vaultManager.deposit(id, address(contracts.ethVault), 1 ether); vm.expectRevert(); contracts.vaultManager.mintDyad(id, mintAmount, user3); vm.stopPrank(); }
Proof of Code Response https://gist.github.com/Josh4324/67c843755a4520bc58c41ad6a61bcf89
Manual Review
The assetPrice()
function should be take the kerosine vaults into consideration in the calculation of the asset price.
DoS
#0 - c4-pre-sort
2024-04-27T18:31:44Z
JustDravee marked the issue as duplicate of #1048
#1 - c4-pre-sort
2024-04-28T18:38:41Z
JustDravee marked the issue as duplicate of #830
#2 - c4-pre-sort
2024-04-29T08:44:46Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-11T20:04:47Z
koolexcrypto marked the issue as satisfactory