DeFiLayer 1Layer 2
14,723 OP
View results
Submission Details
Severity: medium
Invalid

`ScrvusdOracleV2` does not account for loss when obtaining price params, leading to deflated prices due to inflated total supply

Summary

The ScrvusdOracleV2 contract only simulates profit scenarios in its price calculation logic but completely fails to account for loss scenarios. Unlike the inherited VaultV3 implementation which handles both gains and losses, the oracle only processes gains, leading to an inflated total supply when actual losses occur on the strategy on Ethereum mainnet. This results in deflated price calculations that don't reflect the true state of the vault, creating exploitable arbitrage opportunities across chains.

Vulnerability Details

When obtaining the price params we only account for gains:

def _obtain_price_params(parameters_ts: uint256) -> PriceParams:
# ... [initial checks] ...
# locked shares at moment params.last_profit_update
gain: uint256 = (
params.balance_of_self * (params.total_idle + params.total_debt) // params.total_supply
)
params.total_idle += gain * number_of_periods
# functions are reduced from `VaultV3._process_report()` given assumptions with constant gain
for _: uint256 in range(number_of_periods, bound=MAX_V2_DURATION):
new_balance_of_self: uint256 = (
params.balance_of_self
* (params.total_supply - params.balance_of_self) // params.total_supply
)
params.total_supply -= (
params.balance_of_self * params.balance_of_self // params.total_supply
)
params.balance_of_self = new_balance_of_self
# ... [update timestamps] ...
return params

Now compare this with the real implementation in VaultV3's process_report()

def _process_report(strategy: address) -> (uint256, uint256):
# ... [setup] ...
# Compare reported assets vs. the current debt.
if total_assets > current_debt:
# We have a gain.
gain = unsafe_sub(total_assets, current_debt)
else:
# We have a loss.
loss = unsafe_sub(current_debt, total_assets)
# ... [fee calculation] ...
# Only need to burn shares if there is a loss or fees.
if loss + total_fees > 0:
# The amount of shares we will want to burn to offset losses and fees.
shares_to_burn = self._convert_to_shares(loss + total_fees, Rounding.ROUND_UP)
# ... [share management] ...
# Or record any reported loss
elif loss > 0:
current_debt = unsafe_sub(current_debt, loss)
if strategy != self:
self.strategies[strategy].current_debt = current_debt
self.total_debt -= loss
else:
# Add in any refunds since it is now idle.
current_debt = unsafe_add(current_debt, total_refunds)
self.total_idle = current_debt

The key differences are:

  1. VaultV3 explicitly checks for both gains and losses: if total_assets > current_debt: gain = ... else: loss = ...

  2. VaultV3 burns shares to account for losses: shares_to_burn = self._convert_to_shares(loss + total_fees, Rounding.ROUND_UP)

  3. VaultV3 reduces the debt or idle balance when losses occur: self.total_debt -= loss

In contrast, ScrvusdOracleV2 only accounts for gains by:

  1. Always calculating a gain: gain = params.balance_of_self * (params.total_idle + params.total_debt) // params.total_supply

  2. Always increasing the total idle assets: params.total_idle += gain * number_of_periods

  3. Never implementing any mechanism to reduce total supply or assets when losses occur

This discrepancy causes the oracle to simulate an ever-increasing total supply, even when the actual vault on Ethereum is experiencing losses.

Impact

  • Inflated total supply calculations when actual losses occur on Ethereum

  • Deflated price calculations due to the formula for calculating prices: price = total_assets / total_supply

  • Cross-chain price inconsistencies as the oracle fails to accurately reflect the vault's state, and querying raw_price() would yield the incorrect result on current chain and allow for arbitrage.

Tools Used

Manual review

Recommendations

Modify the _obtain_price_params() function to account for losses just like is done in VaultV3.

Updates

Lead Judging Commences

0xnevi Lead Judge
5 months ago
0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

bauchibred Submitter
5 months ago
0xnevi Lead Judge
5 months ago
0xnevi Lead Judge
5 months ago
0xnevi Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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