DYAD - foxb868's results

The first capital efficient overcollateralized stablecoin.

General Information

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

DYAD

Findings Distribution

Researcher Performance

Rank: 134/183

Findings: 1

Award: $4.87

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

4.8719 USDC - $4.87

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
:robot:_488_group
duplicate-67

External Links

Lines of code

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

Vulnerability details

Impact

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.

#L60-L63

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.

Proof of Concept

https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L50-L68

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

Step 1: Initial State

  • Assume there are three Kerosine vaults: VaultA, VaultB, and VaultC.
  • The initial state of the vaults is as follows:
    • VaultA: Asset balance = 1,000,000 tokens
    • VaultB: Asset balance = 500,000 tokens
    • VaultC: Asset balance = 800,000 tokens
  • The total supply of Dyad tokens is 1,000,000.
  • The Kerosine denominator is set to 1,000,000.

Step 2: Colluding Parties' Manipulation

  • Three colluding parties, PartyX, PartyY, and PartyZ, own significant portions of the assets in the Kerosine vaults.
  • The colluding parties coordinate their actions to manipulate the asset balances:
    • PartyX deposits an additional 2,000,000 tokens into VaultA.
    • PartyY deposits an additional 1,500,000 tokens into VaultB.
    • PartyZ deposits an additional 1,000,000 tokens into VaultC.
// PartyX deposits into VaultA
VaultA.deposit(2000000);

// PartyY deposits into VaultB
VaultB.deposit(1500000);

// PartyZ deposits into VaultC
VaultC.deposit(1000000);

Step 3: Manipulated Asset Balances

  • After the colluding parties' manipulation, the asset balances of the vaults become:
    • VaultA: Asset balance = 3,000,000 tokens
    • VaultB: Asset balance = 2,000,000 tokens
    • VaultC: Asset balance = 1,800,000 tokens

Step 4: Kerosine Price Calculation

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:

    • VaultA: Asset price = $1, Asset decimals = 18, Oracle decimals = 18
    • VaultB: Asset price = $1, Asset decimals = 18, Oracle decimals = 18
    • VaultC: Asset price = $1, Asset decimals = 18, Oracle decimals = 18
  • The calculated TVL becomes: 3,000,000 + 2,000,000 + 1,800,000 = 6,800,000

Step 5: Manipulated Kerosine Price

uint numerator = tvl - dyad.totalSupply();
uint denominator = kerosineDenominator.denominator();
return numerator * 1e8 / denominator;
  • With the manipulated TVL of 6,800,000 and the Dyad total supply of 1,000,000, the numerator becomes: 6,800,000 - 1,000,000 = 5,800,000
  • The Kerosine denominator is 1,000,000.
  • The calculated Kerosine asset price becomes: 5,800,000 * 1e8 / 1,000,000 = 580

Step 6: Impact

  • The manipulated Kerosine asset price of 580 is significantly higher than the genuine price based on the initial asset balances.
  • The inflated price can have various consequences for the protocol and its users:
    • Users who rely on the Kerosine asset price for trading or decision-making may make suboptimal choices based on the manipulated price.
    • The colluding parties can benefit from the inflated price by selling their Kerosine tokens at a higher price or taking advantage of other opportunities that arise from the manipulated price.
    • The overall stability and reliability of the Kerosine ecosystem can be compromised, leading to a loss of trust among users and potential financial losses.

Root Cause of Impact:

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.

Tools Used

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;
}

Assessed type

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter