From 95812c52a818569cb03fb78a6c10198cf76122f4 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Thu, 6 Aug 2026 18:23:57 -0300 Subject: [PATCH] test(twap-monitor): cover a removal arriving before its create module.toml already documents the risk: the ConditionalOrderCreated and ConditionalOrderRemoved subscriptions merge in arrival order, not chain order. removal_of_an_unindexed_watch_is_a_no_op already proves a removal with no known create is silently dropped, and poll_invalid_drops_watch_and_gates already proves the poll-drop self-heal path - but only for one of the five recognised legacy IConditionalOrder revert reasons (OrderNotValid). Neither test chains the two together, and neither exercises the unrecognised-selector fallback path that SingleOrderNotAuthed() - the actual, parameterless custom error the deployed ComposableCow.sol reverts with for a removed order - would take through LegacyRevertAdapter::classify. This test runs the full sequence for real: a removed log for an unindexed watch (no-op), its create arriving after (watch persists with no memory of the earlier removal), then an on_block dispatch where the mocked eth_call returns keccak256("SingleOrderNotAuthed()")[..4] as the revert data. Confirms the watch is gone afterward and nothing was submitted to the venue. --- modules/twap-monitor/src/keeper.rs | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/modules/twap-monitor/src/keeper.rs b/modules/twap-monitor/src/keeper.rs index c56b7979..2b99141d 100644 --- a/modules/twap-monitor/src/keeper.rs +++ b/modules/twap-monitor/src/keeper.rs @@ -1195,6 +1195,64 @@ mod tests { }); } + #[test] + fn removal_before_its_create_self_heals_via_the_first_poll() { + // The exact race module.toml's own comment warns about: the two + // log streams merge in arrival order, not chain order, so a + // removal can reach `on_chain_logs` before the create it + // cancels. `removal_of_an_unindexed_watch_is_a_no_op` already + // confirms the removal alone is silently dropped in that case - + // this chains the rest of the story: the create that follows + // persists the watch anyway, with no memory of the earlier + // removal, and the very first poll must still tear it down. + // + // The revert used here is `SingleOrderNotAuthed()` - the actual + // selector the deployed `ComposableCow._auth` reverts with for a + // removed order (nullislabs/composable-cow/src/ComposableCow.sol), + // not one of the five legacy `IConditionalOrder` reasons + // `poll_invalid_drops_watch_and_gates` already covers. + // `LegacyRevertAdapter::classify`'s unrecognised-selector + // fallback is what has to catch this one. + let host = MockHost::new(); + let venue = MockVenue::default(); + let owner = address!("0011223344556677889900AABBCCDDEEFF001122"); + let params = sample_params(); + let hash = keccak256(params.abi_encode()); + let key = watch_key(&owner, &hash); + + on_chain_logs(&host, &[make_removed_log(owner, hash, at(2, 0))]).unwrap(); + assert_eq!( + host.store.len(), + 0, + "removal of an unindexed watch is a no-op" + ); + + on_chain_logs(&host, &[make_log(owner, ¶ms, at(1, 0))]).unwrap(); + assert!( + host.store.snapshot().contains_key(&key), + "the create persists the watch with no memory of the earlier removal", + ); + + let selector = alloy_primitives::keccak256(b"SingleOrderNotAuthed()")[..4].to_vec(); + host.chain.respond_to( + "eth_call", + programmed_eth_call_params(owner, ¶ms), + Err(ChainError::Rpc(nexum_sdk::host::RpcError { + code: 3, + message: "execution reverted".into(), + data: Some(selector.into()), + })), + ); + + dispatch(&host, &venue, sample_block(1_000)).unwrap(); + + assert!( + !host.store.snapshot().contains_key(&key), + "the first poll against a chain-dead order must self-heal the stale watch", + ); + assert_eq!(venue.submit_count(), 0, "a dead order is never submitted"); + } + /// The supervisor builds log filters from this manifest's chain-log /// `event_signature` pins, so a drift from a decoder topic-0 /// subscribes to one topic and decodes another. Compares the two