The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: medium
Valid

Using average price to calculate value of token can be abused by attacker to get profit

Summary

When price of accepted token drop gradually for a long time, attack can abusing it to make profit by abusing difference between real price and price returned from function

Vulnerability Details

For checking value of token list in the vault, function euroCollateral() use average price of token in 4 hours to calculate price:

function euroCollateral() private view returns (uint256 euros) {
    ITokenManager.Token[] memory acceptedTokens = getTokenManager().getAcceptedTokens();
    for (uint256 i = 0; i < acceptedTokens.length; i++) {
        ITokenManager.Token memory token = acceptedTokens[i];
        euros += calculator.tokenToEurAvg(token, getAssetBalance(token.symbol, token.addr));  // <---
    }
}

Function tokenToEurAvg():

function tokenToEurAvg(ITokenManager.Token memory _token, uint256 _tokenValue) external view returns (uint256) {
    Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
    uint256 scaledCollateral = _tokenValue * 10 ** getTokenScaleDiff(_token.symbol, _token.addr);
    uint256 collateralUsd = scaledCollateral * avgPrice(4, tokenUsdClFeed);    // <---- using avgPrice() function with average price = 4 hours
    (, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
    return collateralUsd / uint256(eurUsdPrice);
}

Consider scenario for attacker to exploit:

  • Price of WBTC gradually reduce from 40000 to 30000 in 4 hours, price returned from function tokenToEurAvg() is 35000

  • Attacker deposit WBTC to the vault, minting token, get profit by price differences and leaving the vault under collateralized later
    The profit will depend on collateralRate and minting fee, but with current settings of variable (collateral rate ~ 120%, minting fee ~ 0-5%), this exploit can be used in good condition

function mint():

function mint(address _to, uint256 _amount) external onlyOwner ifNotLiquidated {
    uint256 fee = _amount * ISmartVaultManagerV3(manager).mintFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
    require(fullyCollateralised(_amount + fee), UNDER_COLL);  //  <--- using `fullyCollateralised()` to check
    minted = minted + _amount + fee;
    EUROs.mint(_to, _amount);
    EUROs.mint(ISmartVaultManagerV3(manager).protocol(), fee);
    emit EUROsMinted(_to, _amount, fee);
}

function fullyCollateralised():

function fullyCollateralised(uint256 _amount) private view returns (bool) {
    return minted + _amount <= maxMintable(); // <--- using maxMintable() to check
}

function maxMintable():

function maxMintable() private view returns (uint256) {
    return euroCollateral() * ISmartVaultManagerV3(manager).HUNDRED_PC() / ISmartVaultManagerV3(manager).collateralRate(); // <-- using euroCollateral() to check, which using average price, not actual price
}

Impact

Attacker can get profit by mint more EUROs token than he should, which later can break peg stability of the token because value of collateral is not equal to total token minted.

Tools Used

Manual review

Recommendations

Using tokenToEur() function to calculate total value of token in the vault instead of tokenToEurAvg(), and settings minting fee higher than deviation threshold of the chainlink feed to make attack non profitable

Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Bad-debt

greatlake Submitter
over 1 year ago
hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

avg-spot-price

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.