Skip to content

feat(twap): precise polling hints for watch towers#3

Draft
brunota20 wants to merge 1 commit into
mainfrom
feat/twap-polling-hints
Draft

feat(twap): precise polling hints for watch towers#3
brunota20 wants to merge 1 commit into
mainfrom
feat/twap-polling-hints

Conversation

@brunota20

@brunota20 brunota20 commented Jun 24, 2026

Copy link
Copy Markdown

What

TWAP.getTradeableOrder now reverts with the precise polling errors from IConditionalOrder instead of the generic OrderNotValid("not within span") (and the transitive OrderNotValid("before twap start") / "after twap finish" raised inside TWAPOrderMathLib.calculateValidTo):

  • Pre-t0 (before the first part starts): PollTryAtEpoch(t0, "before first part")
  • Post total span (every part settled / passed): PollNever("all parts settled")
  • Between parts of an in-progress TWAP (current part's span elapsed but next part not started): PollTryAtEpoch(nextPartStart, "between parts")

validate() is invoked up-front so invalid bundle configurations still revert with their original OrderNotValid(INVALID_*) reasons (no taxonomy change there). OrderNotValid(NOT_WITHIN_SPAN) is kept as a defensive fallback (should be unreachable).

Why

The PollTryAtEpoch / PollNever errors exist in IConditionalOrder specifically for watch towers to optimise polling, but TWAP.sol never used them. Watch towers (CoW DAO WatchTower + our Shepherd implementation) currently have to either poll every block (wasteful) or build out-of-band logic to know when the next part starts (e.g. our COW-1077 _twap_calldata.py helper that pins t0 = now-60).

With the precise hints, watch towers stop polling between parts entirely and remove finished TWAPs from their watch set without trial-and-error.

Backward compatibility

  • PollTryAtEpoch and PollNever are pre-existing errors already on the IConditionalOrder interface — no interface change.
  • Watch towers that handle the full error taxonomy (per the interface spec) gain efficiency immediately.
  • Watch towers that only catch OrderNotValid continue to function: they will see an unknown revert and treat it as "skip this watch this block", same defensive behavior as today (no order is silently surfaced as valid).
  • No selector changes to existing functions; no public-symbol removal.
  • The existing in-repo tests test_getTradeableOrder_FuzzRevertIfBeforeStart, _FuzzRevertIfExpired, _FuzzRevertIfOutsideSpan, the cabinet-based variants, and the test_simulate_fuzz catch block were updated to expect the new (more informative) errors — these are internal regression tests, not downstream consumers.

Tests

4 new tests in ComposableCoW.twap.t.sol covering the three new branches plus a regression for the in-window case:

  • test_twap_reverts_with_PollTryAtEpoch_before_t0
  • test_twap_reverts_with_PollNever_after_total_span
  • test_twap_reverts_with_PollTryAtEpoch_between_parts
  • test_twap_existing_within_span_behavior_unchanged

Full suite: 78/78 pass (excluding the pre-existing _invariant and test_simulate_fuzz flake — the latter is an OOG counterexample present on main before this PR, unrelated to polling-hint changes; passes with reduced fuzz runs).

Closes

Addresses the "enhanced polling interfaces" for the TWAP handler.

`TWAP.getTradeableOrder` now reverts with the precise polling errors
already defined on `IConditionalOrder` instead of the generic
`OrderNotValid("not within span")` / `"before twap start"` /
`"after twap finish"`:

- Pre-`t0`: `PollTryAtEpoch(t0, "before first part")`
- Post total span: `PollNever("all parts settled")`
- Between parts of an in-progress TWAP:
  `PollTryAtEpoch(nextPartStart, "between parts")`

`validate()` is invoked up-front so invalid configurations still revert
with their original `OrderNotValid(...)` reasons. `OrderNotValid(NOT_WITHIN_SPAN)`
is kept as a defensive fallback (should be unreachable).

Watch towers gain the ability to:
- Stop polling between parts (retry exactly at the next part's start).
- Drop finished TWAPs from their watch set without trial-and-error.
- Skip pre-`t0` polling and wake up only when needed.

Addresses M2 grant deliverable "enhanced polling interfaces" for the
TWAP handler.
@brunota20 brunota20 force-pushed the feat/twap-polling-hints branch from 35efd03 to 5b0c15d Compare June 25, 2026 12:19
@brunota20 brunota20 changed the title feat(twap): precise polling hints for watch towers (M2) feat(twap): precise polling hints for watch towers Jun 25, 2026
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