Emergency mode blocks escape-hatch refund paths, trapping user funds
The whenNotEmergencyOnly modifier is applied to the very refund and recovery functions designed to free user funds, so an emergency leaves users without an on-chain exit while the cold-wallet multisig coordinates.
Description
The protocol implements a multi-tiered pause system where emergencyOnlyMode acts as a circuit breaker for exceptional situations. The design correctly allows risk-reduction operations (forceUnwind, liquidatePosition, settlePosition and their finalize counterparts) to bypass emergency mode so the protocol can continue shedding risk during incidents.
However, the whenNotEmergencyOnly modifier is also applied to cancelPosition, revertOpen, openPosition, finalizeOpen, closePosition, and finalizeClose, the very functions that serve as escape hatches for positions caught mid-lifecycle.
This creates a contradiction: cancelPosition was specifically implemented as an escape hatch to prevent permanent fund locks when the admin is delayed (up to cancelDelaySeconds). revertOpen is the designated failure path to refund users when a Polymarket exchange order fails. During an active emergency such as a backend crash, database outage, or exchange API failure, the system is most likely to suffer from the exact delays and failed orders that these functions were built to resolve.
Vulnerable scenario:
- User calls
createPosition, transferring collateral plus origination fee to the vault. Position entersCreatedstate. - An incident occurs (backend crash, exchange outage) and
emergencyAdmin(hot wallet) activatesemergencyOnlyMode. - The user's position is stuck in
Created.cancelPositionis blocked bywhenNotEmergencyOnly, so neither the user nor the admin can refund the escrowed funds. - Similarly, positions in
OpenPendingcannot be refunded viarevertOpen, and positions inClosePendingcannot be completed viafinalizeClose. - The only recovery is for
globalAdmin(2-of-3 cold-wallet multisig) to disable emergency mode entirely viasetEmergencyOnlyMode(false), which could take hours to coordinate and forces a binary choice between maintaining emergency posture and returning user funds.
The asymmetry is illogical: if the protocol trusts admins to finalize liquidations, force unwinds, and settlements during emergency mode, there is no security benefit to preventing those same admins from issuing simple refunds for stuck positions.
Impact
User USDC (collateral plus origination fee) is locked in Created, OpenPending, or ClosePending states with no on-chain recovery path while emergency mode is active. Recovery requires the cold-wallet multisig to fully disable the protocol's emergency defenses, defeating the purpose of the circuit breaker during the incident window when it is needed most.
Recommendation
Exempt the refund/recovery functions from the whenNotEmergencyOnly modifier while keeping it on position-initiation functions:
- Remove
whenNotEmergencyOnlyfromcancelPosition,revertOpen, andfinalizeClose. These are fund-recovery paths that pose no new risk during emergencies (they only return escrowed funds or complete already-initiated operations). - Keep
whenNotEmergencyOnlyoncreatePosition,openPosition, andclosePosition. These initiate new lifecycle transitions that should remain blocked during emergencies.
Alternatively, create dedicated emergency-exempt recovery functions (emergencyCancelPosition, emergencyRevertOpen) that bypass the modifier while preserving the existing function signatures for non-emergency use.
Resolution
Acknowledged. Team agreed to add a self-cancel path on cancelPosition gated by the existing serverHalted flag, with tests covering (a) self-cancel reverts when the flag is unset, (b) self-cancel succeeds and refunds collateral plus origination fee when the flag is set, and (c) the flag itself is admin-gated. The remaining emergency-mode asymmetry on revertOpen and finalizeClose is accepted by design.

