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: 113/183
Findings: 2
Award: $7.54
๐ 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
When calling the withdraw function, VaultManagerV2
calculates the value of the assets being withdrawn based on the asset's price and the oracle's decimals. This logic assumes that all vaults have an oracle variable, which is not the case for KerosineVault
contracts such as UnboundedKerosineVault
. Kerosene vaults do not define an oracle as prices are calculated deterministically, and thus the call to _vault.oracle().decimals()
will fail and revert the transaction.
The following POC demonstrates the issue.
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/Test.sol"; import {VaultManagerV2} from "../../src/core/VaultManagerV2.sol"; import {DNft} from "../../src/core/DNft.sol"; import {Dyad} from "../../src/core/Dyad.sol"; import {Licenser} from "../../src/core/Licenser.sol"; import {KerosineManager} from "../../src/core/KerosineManager.sol"; import {UnboundedKerosineVault} from "../../src/core/Vault.kerosine.unbounded.sol"; import {Kerosine} from "../../src/staking/Kerosine.sol"; contract POC is Test { VaultManagerV2 public vaultManager; uint public id; UnboundedKerosineVault public unboundedKerosineVault; function setUp() public { Licenser vaultLicenser = new Licenser(); Dyad dyad = new Dyad(vaultLicenser); DNft dNft = new DNft(); Kerosine kerosene = new Kerosine(); KerosineManager kerosineManager = new KerosineManager(); vaultManager = new VaultManagerV2( dNft, dyad, vaultLicenser ); unboundedKerosineVault = new UnboundedKerosineVault( vaultManager, kerosene, dyad, kerosineManager ); kerosineManager.add(address(unboundedKerosineVault)); vaultManager.setKeroseneManager(kerosineManager); id = dNft.mintNft{value: 0.1 ether}(address(this)); kerosene.approve(address(vaultManager), 5 ether); vaultManager.addKerosene(id, address(unboundedKerosineVault)); vaultManager.deposit(id, address(unboundedKerosineVault), 5 ether); } function testWithdrawReverts() public { vm.roll(2); vaultManager.withdraw(id, address(unboundedKerosineVault), 5 ether, address(this)); } function onERC721Received( address, address, uint256, bytes calldata ) external pure returns (bytes4) { return 0x150b7a02; } receive() external payable {} }
Result:
Ran 1 test for test/POC.t.sol:POC [FAIL. Reason: EvmError: Revert] testWithdrawReverts() (gas: 32979) Traces: [32979] POC::testWithdrawReverts() โโ [0] VM::roll(2) โ โโ โ () โโ [20729] VaultManagerV2::withdraw(0, UnboundedKerosineVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 5000000000000000000 [5e18], POC: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) โ โโ [2557] DNft::ownerOf(0) [staticcall] โ โ โโ โ POC: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496] โ โโ [2623] Dyad::mintedDyad(VaultManagerV2: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], 0) [staticcall] โ โ โโ โ 0 โ โโ [261] UnboundedKerosineVault::asset() [staticcall] โ โ โโ โ Kerosine: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9] โ โโ [249] Kerosine::decimals() [staticcall] โ โ โโ โ 18 โ โโ [214] UnboundedKerosineVault::oracle() [staticcall] โ โ โโ โ EvmError: Revert โ โโ โ EvmError: Revert โโ โ EvmError: Revert Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 6.01ms (367.01ยตs CPU time) Ran 1 test suite in 18.83ms (6.01ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/POC.t.sol:POC [FAIL. Reason: EvmError: Revert] testWithdrawReverts() (gas: 32979) Encountered a total of 1 failing tests, 0 tests succeeded
Foundry
Add a separate function for withdrawing kerosene that doesn't call oracle
and doesn't do any nonKeroseneValue
check as it is not needed to withdraw kerosene.
function withdrawKerosene( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
DoS
#0 - c4-pre-sort
2024-04-26T20:50:47Z
JustDravee marked the issue as primary issue
#1 - c4-pre-sort
2024-04-26T20:50:56Z
JustDravee marked the issue as high quality report
#2 - c4-pre-sort
2024-04-28T18:39:35Z
JustDravee marked the issue as duplicate of #830
#3 - c4-judge
2024-05-11T20:05:26Z
koolexcrypto marked the issue as satisfactory
๐ Selected for report: carrotsmuggler
Also found by: 0xAlix2, 0xSecuri, 0xleadwizard, 0xlemon, 0xtankr, 3th, Aamir, Abdessamed, Bauchibred, Circolors, Egis_Security, Evo, Hueber, Mahmud, SBSecurity, TheSavageTeddy, TheSchnilch, Tychai0s, alix40, bbl4de, btk, d3e4, ducanh2706, falconhoof, itsabinashb, ke1caM, lian886, n4nika, oakcobalt, pontifex, sashik_eth, steadyman, tchkvsky, zhuying
3.7207 USDC - $3.72
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/script/deploy/Deploy.V2.s.sol#L64-L65 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L88 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L269 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L56
In VaultManagerV2
contract, the addKerosene
function is meant to be used to add kerosenevaults into the vaultsKerosene
mapping. However, kerosenevaults
(bounded/unbounded) won't be added as there is a check to see if that vault has been added into keroseneManager
(the isLicensed
call) and per the deployment script(Deploy.V2.s.sol
), the two vaults added to the kerosenemanager
are normal WETH
and WSTETH
vaults not kerosene vaults even though a bounded and an unbounded keroseneVault was created, therefore in the getKeroseneValue
function which is supposed to calculate the USD value of kerosene stored in the keroseneVaults, it won't accurately compute the kerosenevalue
but exogeneous
value(i.e: non-kerosene collateral value).
The current implementation leads to an inaccurate representation of kerosene and exogenous(non-kerosene collateral) vaults in the VaultManagerV2, affecting the overall system's integrity as there will be an inaccurate representation the Collateralization Ratio which is a core aspect of the system. and the correct calculation of asset prices in kerosene vaults.
VaultManagerV2.sol#addKerosene
DeployV2.sol#run
](Deployment script adds non-kerosene vaults to keroseneManager: DeployV2.sol#run)Manual Review
only add kerosene vaults into keroseneManager
and add normal exogeneous vaults into the vaultManagerV2. If this is implemented, the UnboundedKerosineVault
contract should use vaultManager.getVaults
in the assetPrice
function to get the totalValueLocked(tvl) in the exogenous vaults instead of keroseneManager.getVaults
and thus there will be a need for a new enumerableSet state variable that stores all vaults added to the vaultmangerV2, vaults should be added to it when the add function is called(there won't be duplicates since it is a set) and then the vaultManager.getVaults
just returns the vaults stored in that variable.
DeployV2.s.sol:
VaultWstEth wstEth = new VaultWstEth( vaultManager, ERC20 (MAINNET_WSTETH), IAggregatorV3(MAINNET_CHAINLINK_STETH) ); KerosineManager kerosineManager = new KerosineManager(); - kerosineManager.add(address(ethVault)); - kerosineManager.add(address(wstEth)); vaultManager.setKeroseneManager(kerosineManager); kerosineManager.transferOwnership(MAINNET_OWNER); UnboundedKerosineVault unboundedKerosineVault = new UnboundedKerosineVault( vaultManager, Kerosine(MAINNET_KEROSENE), Dyad (MAINNET_DYAD), kerosineManager ); BoundedKerosineVault boundedKerosineVault = new BoundedKerosineVault( vaultManager, Kerosine(MAINNET_KEROSENE), kerosineManager ); + kerosineManager.add(address(unboundedKerosineVault)); + kerosineManager.add(address(boundedKerosineVault));
Vault.kerosine.unbounded.sol:
function assetPrice() public view override returns (uint) { uint tvl; - address[] memory vaults = kerosineManager.getVaults(); + address[] memory vaults = vaultManager.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; }
VaultManagerV2.sol:
contract VaultManagerV2 is IVaultManager, Initializable { using EnumerableSet for EnumerableSet.AddressSet; using FixedPointMathLib for uint; using SafeTransferLib for ERC20; ... mapping (uint => EnumerableSet.AddressSet) internal vaults; mapping (uint => EnumerableSet.AddressSet) internal vaultsKerosene; EnumerableSet.AddressSet internal exogeneusVaults; ... function add( uint id, address vault ) external isDNftOwner(id) { if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults(); if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); if (!vaults[id].add(vault)) revert VaultAlreadyAdded(); exogeneusVaults.add(vault); emit Added(id, vault); } ... function getVaults() external view returns (address[] memory) { return exogeneousVaults.values(); }
Context
#0 - c4-pre-sort
2024-04-29T05:16:08Z
JustDravee marked the issue as duplicate of #70
#1 - c4-pre-sort
2024-04-29T09:37:01Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T20:01:04Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:36:27Z
koolexcrypto changed the severity to 2 (Med Risk)