<!-- canonical: https://0xsimao.com/findings/evro-finance-ii-wsteth-price-eur-oracle -->

# WSTETHPriceFeed returns USD price Instead of EUR on oracle failure

Crit/High · Sherlock · EUR stablecoin · 26th December 2025

Finding H-2 of the Evro Collateral Onboarding security review.

- Report: /reports/evro-collateral-onboarding
- Source: https://github.com/0xsimao/audits/blob/main/Sherlock/private-audits/2025-12-26-evro-finance-ii.pdf

---

## Summary
When the stETH-USD oracle fails, `WSTETHPriceFeed` switches to `ETHUSDxCanonical` mode which returns prices in USD instead of EUR, causing incorrect price calculations.

## Vulnerability Detail
The `WSTETHPriceFeed` has a fallback mechanism when the stETH-USD oracle fails. It calls `_shutDownAndSwitchToETHUSDxCanonical()` which calculates the price using ETH-USD and the canonical wstETH rate. However, the `_fetchPriceETHUSDxCanonical()` function in `CompositePriceFeed` never converts the USD price to EUR before returning.

This means after the stETH-USD oracle failure:
1. All prices returned are in USD (~1.05x higher than EUR)
2. The `lastGoodPrice` gets corrupted with a USD value
3. All subsequent operations use the wrong price denomination

## Impact
After stETH-USD oracle failure, all wstETH collateral operations will use incorrect prices. Users could be incorrectly liquidated or be able to borrow too much. The corrupted `lastGoodPrice` persists even after further fallbacks.

## Code Snippet
https://github.com/sherlock-audit/2025-12-evro-finance-dec-26th/blob/main/evro/contracts/src/PriceFeeds/WSTETHPriceFeed.sol#L70-L72

```solidity
// If the STETH-USD feed is down, shut down and try to substitute it with the ETH-USD price
if (stEthUsdOracleDown) {
    return (_shutDownAndSwitchToETHUSDxCanonical(address(stEthUsdOracle.aggregator), ethUsdPrice), true);
}
```

https://github.com/sherlock-audit/2025-12-evro-finance-dec-26th/blob/main/evro/contracts/src/PriceFeeds/CompositePriceFeed.sol#L76-L95

```solidity
function _fetchPriceETHUSDxCanonical(uint256 _ethUsdPrice) internal returns (uint256) {
    assert(priceSource == PriceSource.ETHUSDxCanonical);
    (uint256 lstRate, bool exchangeRateIsDown) = _getCanonicalRate();

    if (exchangeRateIsDown) {
        priceSource = PriceSource.lastGoodPrice;
        return lastGoodPrice;
    }

    // Calculate the canonical LST-USD price: USD_per_LST = USD_per_ETH * underlying_per_LST
    uint256 lstUsdCanonicalPrice = _ethUsdPrice * lstRate / 1e18;  // @audit - USD, not EUR!

    uint256 bestPrice = LiquityMath._min(lstUsdCanonicalPrice, lastGoodPrice);

    lastGoodPrice = bestPrice;  // @audit - corrupts lastGoodPrice with USD value

    return bestPrice;  // @audit - returns USD instead of EUR
}
```

## Tool Used
Manual Review

## Recommendation
Convert the USD price to EUR in the `_fetchPriceETHUSDxCanonical()` function or override it in `WSTETHPriceFeed` to include EUR conversion:

```solidity
function _fetchPriceETHUSDxCanonical(uint256 _ethUsdPrice) internal override returns (uint256) {
    (uint256 lstRate, bool exchangeRateIsDown) = _getCanonicalRate();
    (uint256 eurUsdPrice, bool eurUsdOracleDown) = _getOracleAnswer(eurUsdOracle);

    if (exchangeRateIsDown || eurUsdOracleDown) {
        priceSource = PriceSource.lastGoodPrice;
        return lastGoodPrice;
    }

    uint256 lstUsdCanonicalPrice = _ethUsdPrice * lstRate / 1e18;
    uint256 lstEurCanonicalPrice = FixedPointMathLib.divWad(lstUsdCanonicalPrice, eurUsdPrice);

    uint256 bestPrice = LiquityMath._min(lstEurCanonicalPrice, lastGoodPrice);
    lastGoodPrice = bestPrice;

    return bestPrice;
}
```

Disclosed by 0xSimao (https://0xsimao.com/).

---

Related findings:

- [getDepositFeeInBNB() assumes a stablecoin price of 1 USD, which may not be true if it depegs](https://0xsimao.com/findings/clip-finance-i-deposit-fee-price-depegs): Clip Finance Strategies
- [Missing priceFeedDisabled event in RedStonePriceFeedBase](https://0xsimao.com/findings/felix-price-disabled-red-stone): Felix Price Feeds
- [Attacker can trigger temporary shutdown due to RedStonePriceFeedBase missing gas check](https://0xsimao.com/findings/felix-temporary-shutdown-price-gas): Felix Price Feeds
- [PriceFeed: First owner isn't set as valid keeper](https://0xsimao.com/findings/nftperp-i-price-isn-valid-keeper): Nftperp Exchange
