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

Profit unlocking Mismatch in ScrvUSD Oracle leading to price inflation

Summary

Having a mismatch in precision constants between the Ethereum vault and the Oracle. The Ethereum vault uses MAX_BPS = 1e18 for calculations, while the Oracle uses MAX_BPS_EXTENDED = 1e12 -> causes the Oracle to overestimate the number of unlocked shares, underrepresenting the effective total supply of scrvUSD

-> result: The price per share reported by the Oracle becomes inflated

Vulnerability Details

Inconsistent precision constants used in the calculation of unlocked_shares:

The mismatch:

  • Root Cause: MAX_BPS_EXTENDED = 1e12 in the Oracle versus MAX_BPS = 1e18 on Ethereum

Code Location: ScrvusdOracleV2.vy, _obtain_price_params() function:

Uses MAX_BPS_EXTENDED = 1e12 (12 decimal precision):

MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000 // # 1e12
unlocked_shares = profit_unlocking_rate * (ts - last_profit_update) // MAX_BPS_EXTENDED

Example:

Ethereum Vault:

  • Uses MAX_BPS = 1e18 (18 decimal precision).

  • Calculates profit_unlocking_rate as:

    solidity

    profit_unlocking_rate = (locked_shares * MAX_BPS) / profit_max_unlock_time
  • Example:

    • locked_shares = 1e18 (1 token with 18 decimals).

    • profit_max_unlock_time = 604800 (7 days in seconds).

    • profit_unlocking_rate = (1e18 * 1e18) / 604800 ≈ 1.653e15.

Oracle Contract (ScrvusdOracleV2.vy):

  • Uses MAX_BPS_EXTENDED = 1e12 (12 decimal precision).

  • Calculates unlocked_shares as:

    vyper

    unlocked_shares = profit_unlocking_rate * (ts - last_profit_update) // MAX_BPS_EXTENDED
  • Using the example:

    • ts - last_profit_update = 604800 (full unlock period).

    • unlocked_shares = (1.653e15 * 604800) // 1e12 ≈ 1e21.

  • Correct calculation (if aligned with MAX_BPS = 1e18):

    • unlocked_shares = (1.653e15 * 604800) // 1e18 ≈ 1e18

-> Result: The Oracle overestimates unlocked_shares by a factor of 1e6 (since 1e18 / 1e12 = 1e6).

link:

Effect: unlocked_shares is 1e6 times larger, understating total_supply_effective and inflating the price.

@view
def _total_supply(p: PriceParams, ts: uint256) -> uint256:
# Need to account for the shares issued to the vault that have unlocked.
return p.total_supply - self._unlocked_shares(
p.full_profit_unlock_date,
p.profit_unlocking_rate,
p.last_profit_update,
p.balance_of_self,
ts, # block.timestamp
)
@view
def _raw_price(ts: uint256, parameters_ts: uint256) -> uint256:
"""
@notice Price replication from scrvUSD vault
"""
parameters: PriceParams = self._obtain_price_params(parameters_ts)
return self._total_assets(parameters) * 10**18 // self._total_supply(parameters, ts)

Link:

Impact

Overstated unlocked_shares reduces the effective total supply.

Price Inflation: The Oracle reports an artificially high price for scrvUSD due to the understated effective total supply.

Tools Used

Recommendations

Align Constants: Set MAX_BPS_EXTENDED = 1e18 in the Oracle to match Ethereum.

Or alternative: Scale profit_unlocking_rate down by 1e6 when fetched, but aligning constants is cleaner.

Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!