This attack could still be executed when PriceCalculator::tokenToEur < PriceCalculator::tokenToEurAvg, but with less impact.
The main invariant of collateralised stablecoins: Issued stable coin value <= collateral value * collateral factor (or euro value < collateral value / collateralRate in terms of The Standard protocol), where 0 <= collateral factor <= 1. This invariant should be valid both for each position and for the whole protocol.
There are two cases of invariant break for a position:
collateral value * collateral factor < Issued stable coin value <= collateral value - in such cases, the position is liquidated, collateral is sold, and its value is enough to repay (burn) all issued stablecoins.
Issued stable coin value > collateral value - in such cases, the position is also liquidated, collateral is sold, but its value is not enough to repay (burn) all issued stablecoins:
X - issued stablecoins, Y - repaid and burnt during liquidation stablecoins where Y < X. X-Y is the protocol's bad debt. It means if all users close their positions, X-Y stablecoins are still issued. But this amount is backed by nothing.
When the protocol has bad debt, the fair price of a stablecoin = (Issued stablecoins - Bad debt) / Issued stablecoins and it is < 1. It means that the stablecoin tends to be unpegged.
To return the peg, the protocol must buy stablecoins from the market and burn them. It means that Bad debt is the direct protocol loss.
Key facts:
There are two types of prices in the PriceCalculator interface and they are used in the SmartVaultV3.
tokenToEurAvg is the average price for some period (currently 4h).
In general, on a falling/rising market, PriceCalculator::tokenToEur and PriceCalculator::tokenToEurAvg could be different (with the current average duration of 4h - dramatically different).
Attack example (similar to the attack described in the tweet):
Falling market (prices provided to simplify calculations)
Current BTC price (PriceCalculator::tokenToEur) 10000 euro/btc.
Average BTC price (PriceCalculator::tokenToEurAvg) 11000 euro/btc.
Current Eth price (PriceCalculator::tokenToEur) 1000 euro/eth.
Attacker deposits 1 BTC.
Attacker mints 5000 EUROs.
Attacker initiates swap of 1 BTC (see SmartVaultManagerV5::swap and SmartVaultManagerV5::calculateMinimumAmountOut):
Calculations inside of SmartVaultManagerV5::calculateMinimumAmountOut:
requiredCollateralValue = 5000 * 110 / 100 = 5500.
euroCollateral = 11000 (since it is calculated by avg prices), calculator.tokenToEur(1 BTC) = 10000.
collateralValueMinusSwapValue = 11000 - 10000 = 1000.
Result of calculateMinimumAmountOut() = calculator.eurToToken(5500 - 1000) = calculator.eurToToken(4500) = 4.5 eth.
Attacker manipulates the liquidity of the btc/eth pool and manages to get 4.5 eth from swapping 1 BTC.
Attacker has outside of the vault:
5000 minted EUROs.
1 BTC (10000 euros) - 4.5 eth (4500 euros) = 5500 euros value of profit.
Total attacker profit = 10500 euros - flash loans and Uniswap fees for manipulations.
Protocol has:
Minted = 5000 EUROs.
Collateral value = 4500 euros.
Bad debt = 500 EUROs.
So the attack could be executed when PriceCalculator::tokenToEur < PriceCalculator::tokenToEurAvg. More difference makes more profit for the attacker and more losses for the protocol.
The protocol will lose funds due to bad debt.
Manual review
In general, it is incorrect to use different types of prices in one formula (this also applies to calculations of eur/usd in the PriceCalculator::tokenToEurAvg).
Chainlink uses the VWAP algorithm (https://chain.link/education-hub/twap-vs-vwap) for price calculations, manual average calculation could be skipped and only the last price used.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.