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: 134/183
Findings: 1
Award: $4.87
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L60-L63 https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L50-L68 https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L60-L64
If multiple parties who own assets in the vaults collude, they can coordinate their actions to manipulate the asset balances and influence the calculated TVL.
tvl += vault.asset().balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) / (10**vault.oracle().decimals());
This line retrieves the asset balance of each vault and uses it to calculate the TVL. If the asset balances are manipulated by colluding parties, it directly impacts the TVL calculation.
By coordinating their actions, such as simultaneously depositing or withdrawing large amounts of assets, the colluding parties can significantly impact the calculated TVL and, consequently, the Kerosine asset price.
Consider a scenario where multiple parties collude to manipulate the asset balances in the Kerosine vaults and exploit the vulnerability in the assetPrice
function. https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L60-L64
// PartyX deposits into VaultA VaultA.deposit(2000000); // PartyY deposits into VaultB VaultB.deposit(1500000); // PartyZ deposits into VaultC VaultC.deposit(1000000);
assetPrice
function is called to calculate the Kerosine asset price.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()); }
Assume the asset prices and decimals are as follows:
The calculated TVL becomes: 3,000,000 + 2,000,000 + 1,800,000 = 6,800,000
assetPrice
function calculates the Kerosine asset price using the manipulated TVL: https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L65-L68uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator;
The root cause of the vulnerability lies in the design of the assetPrice
function in the UnboundedKerosineVault contract. The function calculates the Kerosine asset price based on the current asset balances of the Kerosine vaults without considering the possibility of manipulation by colluding parties.
The specific line of code responsible for the vulnerability is: https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L60-L64
tvl += vault.asset().balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) / (10**vault.oracle().decimals());
This line retrieves the asset balance of each vault and uses it directly in the TVL calculation. If colluding parties manipulate the asset balances by depositing large amounts of tokens, it artificially inflates the TVL and, consequently, the calculated Kerosine asset price.
Lack of proper safeguards against manipulation and the reliance on the current asset balances make the assetPrice
function vulnerable to exploitation by colluding parties.
Manual Review
Implement time-weighted average price (TWAP) or other price smoothing mechanisms to calculate the Kerosine price based on historical data over a specific time period, making it more resistant to short-term manipulations. And Introduce additional checks and validations to detect and prevent sudden or unusual changes in the asset balances of the vaults.
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]); uint assetBalance = vault.asset().balanceOf(address(vault)); uint assetDecimals = vault.asset().decimals(); uint oracleDecimals = vault.oracle().decimals(); uint twap = calculateTWAP(address(vault.asset()), assetDecimals, oracleDecimals); tvl += assetBalance * twap * 1e18 / (10**assetDecimals) / (10**oracleDecimals); } uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; } function calculateTWAP(address asset, uint assetDecimals, uint oracleDecimals) internal view returns (uint) { // Implement time-weighted average price calculation logic here // This can involve fetching historical price data and calculating the average over a specific time period // Example implementation: uint[] memory prices = fetchHistoricalPrices(asset, oracleDecimals); uint twap = 0; for (uint i = 0; i < prices.length; i++) { twap += prices[i]; } twap /= prices.length; return twap; }
Governance
#0 - c4-pre-sort
2024-04-28T19:14:46Z
JustDravee marked the issue as duplicate of #67
#1 - c4-pre-sort
2024-04-29T09:17:42Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-08T11:50:02Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-08T12:08:42Z
koolexcrypto marked the issue as satisfactory