Skip to content

fix: add settlement timeout and emergency refund for stuck directional positions#4

Open
pplmaverick wants to merge 1 commit into
circlefin:masterfrom
pplmaverick:fix/add-settlement-timeout-emergency-refund
Open

fix: add settlement timeout and emergency refund for stuck directional positions#4
pplmaverick wants to merge 1 commit into
circlefin:masterfrom
pplmaverick:fix/add-settlement-timeout-emergency-refund

Conversation

@pplmaverick

Copy link
Copy Markdown

Problem

EventBasedPredictionMarket resolves via UMA's Optimistic Oracle V2, which is a real
improvement over a single-admin-key oracle — anyone can permissionlessly call
proposePrice(), and disputes escalate to the DVM. That part works well.

However, there's no fallback if nobody ever proposes a price (e.g. a low-interest
market where the proposerReward doesn't attract a proposer, or the underlying question
becomes unresolvable). In that case:

  • receivedSettlementPrice stays false forever.
  • settle() reverts indefinitely (require(receivedSettlementPrice, ...)).
  • redeem() only works for holders of matched Long+Short pairs (1:1 burn). Anyone
    holding a one-sided directional position — which is the normal outcome of trading
    through the AMM — has no exit path at all.

I ran into this while building a live weather prediction market on Arc Testnet
(~55 Arc Testnet transactions across create/lock/settle/claim flows) using a simpler
onlyOracle-gated design, and traced the same failure mode back to this reference
implementation while evaluating a migration to it.

Fix

  • Adds settlementDeadline, set to block timestamp of initializeMarket() + SETTLEMENT_TIMEOUT
    (72 hours, currently a fixed constant — open to making this configurable per-market).
  • Adds emergencyRefund(longTokensToRedeem, shortTokensToRedeem): callable by any token
    holder once settlementDeadline has passed with no settlement price received. Pays out
    at a neutral 0.5/0.5 split per token — the same math the contract already uses for the
    OO's "Undetermined" (5e17) outcome — since the true result was never established.

Known limitation / open question for maintainers

priceDisputed() re-requests a price with a fresh timestamp but does not reset
settlementDeadline. If a dispute lands close to the original deadline, emergencyRefund()
could theoretically become callable while a legitimate DVM arbitration is still in flight.
I left this as-is and flagged it with a code comment rather than guessing at the right
behavior (extend the deadline on every dispute? cap the number of extensions?) — wanted to
raise it for discussion rather than bake in an assumption.

Also not included yet: unit tests for the new function and the timeout boundary. Happy to
add a test file (mirroring the existing Hardhat/UMA test setup) if the general approach
looks right to maintainers before I invest in test scaffolding.

Testing

Not yet covered by automated tests — see note above. Manually reasoned through the
solvency invariant: before any settlement, longToken.totalSupply() == shortToken.totalSupply() == collateralToken.balanceOf(address(this)) (since create()/redeem() always move both
sides in lockstep), so the 0.5/0.5 split in emergencyRefund() can't be under-collateralized
in the no-dispute case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant