code-423n4 / 2024-04-dyad-findings

8 stars 6 forks source link

Flashloan exploit inflates Kerosene price, leading to suboptimal decisions, compromised stability, and attacker profits. #1010

Closed c4-bot-10 closed 5 months ago

c4-bot-10 commented 6 months ago

Lines of code

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#L65 https://github.com/code-423n4/2024-04-dyad/blob/4a987e536576139793a1c04690336d06c93fca90/src/core/Vault.kerosine.unbounded.sol#L65

Vulnerability details

Impact

assetPrice function in the UnboundedKerosineVault contract calculates the price of the Kerosene asset based on the total value locked (TVL) in the Kerosene vaults, the total supply of Dyad, and the Kerosene denominator.

The function retrieves the list of Kerosene vaults from the KerosineManager, calculates the TVL by summing up the asset balances multiplied by their respective asset prices, subtracts the total supply of Dyad from the TVL, and divides the result by the Kerosene denominator to determine the final asset price.

Proof of Concept

The fact that the assetPrice function relies on the current state of the Kerosene vaults and the total supply of Dyad at the time of the function call. An attacker can exploit this vulnerability by using flashloans to temporarily manipulate the TVL or the Dyad supply just before the assetPrice function is called.

By inflating the TVL or deflating the Dyad supply, the attacker can artificially increase or decrease the calculated asset price.

#L65

      uint numerator   = tvl - dyad.totalSupply();

This line subtracts the total supply of Dyad from the TVL without considering the possibility of flashloan manipulations.

Let's consider a scenario where an attacker exploits the flashloan vulnerability to manipulate the Kerosene price and profit from the discrepancy.

Step 1: Initial State

Step 2: Flashloan Manipulation

// Flashloan attack contract
contract FlashloanAttack {
    UnboundedKerosineVault public kerosineVault;
    FlashloanProvider public flashloanProvider;

    constructor(address _kerosineVault, address _flashloanProvider) {
        kerosineVault = UnboundedKerosineVault(_kerosineVault);
        flashloanProvider = FlashloanProvider(_flashloanProvider);
    }

    function attack() external {
        uint256 flashloanAmount = 1000000;
        flashloanProvider.flashloan(flashloanAmount);
    }

    function executeFlashloan(uint256 amount) external {
        // Deposit flashloaned tokens into VaultA
        address vaultA = kerosineVault.kerosineManager().getVaults()[0];
        ERC20 asset = ERC20(Vault(vaultA).asset());
        asset.approve(vaultA, amount);
        Vault(vaultA).deposit(1, amount);

        // Perform additional steps to profit from the manipulated price
        // ...

        // Withdraw the flashloaned tokens from VaultA
        Vault(vaultA).withdraw(1, address(this), amount);

        // Repay the flashloan
        asset.transfer(address(flashloanProvider), amount);
    }
}

Step 3: Manipulated 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());
}
uint numerator = tvl - dyad.totalSupply();
uint denominator = kerosineDenominator.denominator();
return numerator * 1e8 / denominator;

Step 4: Exploitation

Step 5: Impact

Root Cause of Impact

In the design of the assetPrice function in the UnboundedKerosineVault contract. The function calculates the Kerosene price based on the current state of the Kerosene vaults and the total supply of Dyad, without considering the possibility of flashloan manipulations.

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#L65

      uint numerator   = tvl - dyad.totalSupply();

This line subtracts the total supply of Dyad from the TVL, which can be manipulated by an attacker using flashloans. By temporarily inflating the TVL through a flashloan deposit, the attacker can artificially increase the numerator, resulting in an inflated Kerosene price.

Tools Used

Manual Review

Recommended Mitigation Steps

Instead of relying on the current state of the Kerosene vaults and Dyad supply, use historical data or a moving average to calculate the asset price, making it more resistant to short-term manipulations.

Introduce additional checks and validations to ensure the integrity of the TVL and Dyad supply values used in the price calculation.

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

Context

c4-pre-sort commented 5 months ago

JustDravee marked the issue as duplicate of #67

c4-pre-sort commented 5 months ago

JustDravee marked the issue as sufficient quality report

c4-judge commented 5 months ago

koolexcrypto marked the issue as unsatisfactory: Invalid

koolexcrypto commented 5 months ago

Flashloan can not be repaid since deposit and withdraw not possible in the same bock

c4-judge commented 5 months ago

koolexcrypto marked the issue as not a duplicate