# PythOracle `commit()` function doesn't require (nor stores) pyth price publish timestamp to be after the previous commit's publish timestamp, which makes it possible to manipulate price to unfairly liquidate users and possible stealing protocol funds

PythOracle.commit() lacks a publish-timestamp monotonicity check, letting anyone commit an out-of-order older Pyth price and unfairly liquidate a freshly opened position.

- Payout: Contest reward
- Severity: medium
- Program: Perennial
- Credited: panprog
- Disclosed: 2023-07-17
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-07-perennial-judging/issues/44
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-07-perennial%3AM-2
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:business-logic vuln:liquidation vuln:oracle-manipulation

**Target:** PythOracle.sol commit() oracle price commit
**Root cause:** commit() neither validates nor stores the Pyth publish timestamp, so an unrequested oracle version can be committed with a publish timestamp earlier than the previously committed version's, breaking the monotonic publish-time invariant that commitRequested() enforces.
**Steps:**
1. Alice requests open of 1 ETH long with $10 collateral at oracle version 100 (valid publish window 112..115).
2. Pyth publishes $980@113, $990@114, $1000@115.
3. Keeper commits requested version 100 with publish timestamp 115 (price $1000); position opens.
4. Bob calls `commit(101, updateData)` with the $980 price whose publish timestamp is 113 — no check rejects it.
5. Bob immediately liquidates Alice using the older $980 price, below her entry price.
**How found:** Code-reading comparison: noticed commitRequested() enforces publish-time ordering while commit() does not, then constructed a timeline showing a position opened at a later publish timestamp being liquidated at an earlier one.
**Exploitation:** Attacker watches for a pending/just-committed position and a Pyth update with an earlier publish timestamp but lower price within the valid window, commits it as the next unrequested oracle version via commit(), then calls liquidation on the now-undercollateralized position to collect the liquidation incentive.
**Escalation:** More complex variants let the attacker liquidate their own position to drain protocol funds; impact scales with volatility and window length.
**Insight:** When two entry points write the same oracle state, diff their validation: a permissionless fallback path (commit) often skips invariants (monotonic timestamps) enforced on the primary path (commitRequested), enabling out-of-order state injection.
