# withdrawTo Does Not Sync Before Checking A Position's Margin Requirements

Perennial's withdrawTo checked margin requirements using a stale oracle price because it never settled the account first, letting users withdraw from undercollateralized positions.

- Payout: Audit contest
- Severity: high
- Program: Perennial
- Found by: leastwood
- Disclosed: 2022-01-24
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2021-12-perennial-findings/issues/74
- Library page: https://bugbountylibrary.com/r/c4%3A2021-12-perennial%3AH-02
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:oracle-manipulation

**Target:** Collateral.withdrawTo / maintenanceInvariant margin check
**Root cause:** withdrawTo applies maintenanceInvariant without first calling settleForAccount, so AccountPosition.maintenance computes margin from provider.priceAtVersion(currentVersion()) which may be an outdated oracle price.
**Steps:**
1. Observe withdrawTo in Collateral.sol applies collateralInvariant and maintenanceInvariant modifiers but never syncs/settles the account.
2. maintenanceInvariant calls product.maintenance(account) and maintenanceNext(account).
3. maintenanceInternal reads provider.priceAtVersion(provider.currentVersion()) — the last synced oracle version, not a fresh price.
4. Let the oracle go unsynced so the stored price is stale/favorable, then call `withdrawTo(account, product, amount)` on an undercollateralized position.
5. Margin check passes against the stale price and collateral is pushed out via token.push.
**How found:** Manual code review of the withdrawTo modifier chain; noticed maintenanceInvariant depends on oracle price via AccountPosition.maintenance and that no settle/sync call precedes the check, so the price used could be stale.
**Exploitation:** A user with an undercollateralized position waits until the oracle has not synced (stale price favorable to them), then calls withdrawTo; the maintenanceInvariant modifier evaluates maintenance against the outdated price and permits the withdrawal, extracting collateral that current prices would not allow.
**Insight:** In perp/lending protocols, any balance-changing entrypoint that runs a margin/solvency invariant must first accrue/settle the account to the latest oracle version; audit modifier ordering for missing settle calls, since a stale price silently weakens the check.
