# Requested oracle versions, which have expired, must return this oracle version as invalid, but they return it as a normal version with previous version's price instead

Expired oracle versions committed with previous price return valid=true, so Perennial markets keep orders that should be invalidated.

- Payout: Contest reward
- Severity: high
- Program: Perennial V2 3
- Credited: bin2chen
- Disclosed: 2024-02-28
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2024-02-perennial-v2-3-judging/issues/6
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-02-perennial-v2-3%3AH-2
- Tags: domain:web3 impact:integrity kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:business-logic vuln:oracle-manipulation

**Target:** KeeperOracle / Market settlement logic
**Root cause:** KeeperOracle._commitRequested stores the previous valid version's price for expired versions, and at() derives validity solely from price != 0, so expired versions appear valid.
**Steps:**
1. Keeper fails to commit requested oracle version within timeout.
2. KeeperOracle._commitRequested sets `_prices[version.timestamp] = _prices[_global.latestVersion]` (previous price, nonzero).
3. Market._processOrderGlobal calls `oracle.at(newOrder.timestamp)`.
4. at() sets `valid = !price.isZero()` → true because price is nonzero.
5. Order is not invalidated and settles against stale price.
**How found:** Code reading of _commitRequested and at(): noticed validity is inferred from price being nonzero while expired commits copy a nonzero prior price.
**Exploitation:** When keepers cannot commit a requested version (no price, downtime), the expired version inherits the prior price and is treated as valid; markets that should invalidate orders instead settle them, allowing users to open/close positions when they shouldn't.
**Escalation:** Attacker can trigger invalid versions at will in markets that close, opening or closing positions at stale prices.
**Insight:** When validity is derived from a sentinel value (price==0), check every write path that populates that field—fallback/stale-price copies silently flip the validity flag.
