Skip to content

feat(twap-monitor): eth_call poll path + PollOutcome decoder (BLEU-827) - #4

Closed
brunota20 wants to merge 2 commits into
feat/twap-monitor-indexing-bleu-826from
feat/twap-monitor-polling-bleu-827
Closed

feat(twap-monitor): eth_call poll path + PollOutcome decoder (BLEU-827)#4
brunota20 wants to merge 2 commits into
feat/twap-monitor-indexing-bleu-826from
feat/twap-monitor-polling-bleu-827

Conversation

@brunota20

Copy link
Copy Markdown

Summary

`on_event(Event::Block)` walks every persisted watch, skips the ones gated by a future `next_block:` / `next_epoch:` entry, and dispatches the ready ones via `chain::request("eth_call", [{to: COMPOSABLE_COW, data}, "latest"])` to `ComposableCoW.getTradeableOrderWithSignature(owner, params, "", [])`.

  • Successful return → `<(GPv2OrderData, Bytes)>::abi_decode_params` → `PollOutcome::Ready { order, signature }`.
  • Revert payload → `decode_revert` matches the four-byte selector against the five `IConditionalOrder` errors and maps to TryNextBlock / TryOnBlock(n) / TryAtEpoch(t) / DontTryAgain.
  • Anything else (transient RPC error, unmodelled require-revert) falls back to TryNextBlock so the watch is retried instead of dropped.

A local `abi::Params` mirror is needed because sol! cannot cross crate boundaries; the wire format matches `cowprotocol::ConditionalOrderParams` so the selector is byte-equal. `Ready` boxes the order (clippy::large_enum_variant).

Storage conventions (BLEU-830 writes these): `next_block:{owner}:{params_hash}` (u64 LE block number) and `next_epoch:{owner}:{params_hash}` (u64 LE Unix seconds). The watch polls when both pass.

Host follow-up

The chain backend currently swallows alloy's `RpcError::ErrorResp.data` (it becomes the unstructured `host-error.message`). `poll_one` is already wired to consume structured revert hex via `host-error.data` (test `decode_revert_hex_strips_prefix_and_quotes` locks the path). Until the host forwards `error.data`, every revert defaults to TryNextBlock — safe but lossy. Same plumbing also benefits BLEU-829.

Stacks on #3 (BLEU-826 indexing).

Linear: BLEU-827. Ref ADR-0006.

Test plan

  • `cargo test -p twap-monitor` — 17 host tests (3 BLEU-826 regressions + 14 new).
  • `cargo clippy --target wasm32-wasip2 -p twap-monitor -- -Dwarnings`.
  • `cargo clippy -p twap-monitor --tests -- -Dwarnings`.
  • `cargo build --target wasm32-wasip2 --release -p twap-monitor` — 215 KB .wasm (was 96 KB after BLEU-826; serde_json + IConditionalOrder ABI + GPv2OrderData decode linked in).
  • Live eth_call against a Sepolia ComposableCoW deployment — pending BLEU-834 module.toml + host `error.data` forwarding for end-to-end revert classification.

`on_event(Event::Block)` walks every persisted watch, skips the
ones gated by a future `next_block:` / `next_epoch:` entry, and
dispatches the ready ones via `chain::request("eth_call",
[{to: COMPOSABLE_COW, data}, "latest"])` to
`ComposableCoW.getTradeableOrderWithSignature(owner, params,
"", [])`.

Returns:
- Successful return data → `<(GPv2OrderData, Bytes)>::abi_decode_params`
  → `PollOutcome::Ready { order, signature }`.
- Revert payload → `decode_revert` matches the four-byte selector
  against the five `IConditionalOrder` errors:
    OrderNotValid     → DontTryAgain
    PollNever         → DontTryAgain
    PollTryNextBlock  → TryNextBlock
    PollTryAtBlock(n) → TryOnBlock(n)
    PollTryAtEpoch(t) → TryAtEpoch(t)
- Anything else falls back to TryNextBlock so a flaky RPC or
  unmodelled require-revert is retried instead of dropped.

Decoder ABI: a local `abi::Params` struct mirrors the wire format
of `cowprotocol::ConditionalOrderParams` because sol! cannot cross
crate boundaries; the resulting call selector is byte-equal to the
real contract. The successful return path decodes into the
canonical `cowprotocol::GPv2OrderData` directly, so the 12-field
struct is not duplicated. `Ready` boxes the order to keep
`PollOutcome` cache-friendly (clippy::large_enum_variant).

Storage conventions (shared with BLEU-830, which writes these):
- `next_block:{owner}:{params_hash}` -> u64 LE — block number gate
- `next_epoch:{owner}:{params_hash}` -> u64 LE — Unix-seconds gate
Either / both / neither may be set; the watch polls when both pass.
`block.timestamp` is milliseconds per WIT, so we divide by 1000 to
compare against the `TryAtEpoch` (seconds) convention.

Host follow-up: the chain backend currently swallows alloy's
`RpcError::ErrorResp.data` (it becomes `host-error.message`,
unstructured). `poll_one` is wired to consume structured revert
hex via `host-error.data` once that lands — the `decode_revert_hex`
test locks the path. Until then, every revert defaults to
TryNextBlock, which is the safe choice.

Tests: 14 new (return round-trip, all five revert variants, hex
plumbing, eth_call JSON shape, watch-key round-trip, U256
saturation), keeping the 3 BLEU-826 regressions. `.wasm` grows
from 96 KB to 215 KB (serde_json + IConditionalOrder ABI + the
GPv2OrderData decode path linked in).

Linear: BLEU-827. Ref ADR-0006.
@linear-code

linear-code Bot commented Jun 15, 2026

Copy link
Copy Markdown

BLEU-827

@brunota20

Copy link
Copy Markdown
Author

Work landed via dev/m3-base advance (FF to 9e76602 = M3 epic + rust-idiomatic compliance propagation). The HEAD commit of this PR is now an ancestor of dev/m3-base. Closing as merged-by-ancestor.

@brunota20 brunota20 closed this Jun 24, 2026
brunota20 added a commit that referenced this pull request Jun 25, 2026
12 review threads addressed end-to-end. Net diff is -720 lines despite
adding ~200 lines of new helpers + tests, because the WitBindgenHost
adapter deduplication alone wipes ~400 lines.

Per-thread:

  #1 (balance-tracker architecture): refactored to match the M3
  host-trait+adapter split the other 4 modules use. Created
  `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one /
  fetch_balance / parse_balance_hex / parse_settings into it,
  converted parse_config to use SDK config helpers + typed
  HostError instead of String. Added 3 MockHost-driven tests
  covering first-seen-above-threshold, below-threshold-persist,
  and error-does-not-abort-loop.

  #2 + #3 (WitBindgenHost dedup): new
  `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro.
  Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`;
  the 4 trait impls + convert_err / sdk_err_into_wit / convert_level
  collapse to one macro invocation per module. Migrated all 5
  modules (twap-monitor, ethflow-watcher, price-alert, stop-loss,
  balance-tracker). Each module's lib.rs lost ~80 lines.

  #4 (scale_decimal + config_get dup): new `shepherd_sdk::config`
  with `get_required`, `get_optional`, `scale_decimal`, and a
  typed `ConfigError` enum (host-neutral). price-alert + stop-loss
  consume the SDK helpers; their local duplicates were deleted.
  Module-level decimal-parsing tests removed (covered by 7 SDK
  tests + 4 proptest cases now).

  #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with
  `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`.
  Encapsulates the eth_call → parse → ABI decode flow + Warn
  logging. price-alert + stop-loss now call the helper; their
  local AggregatorV3 sol! definitions + read_oracle / on_block
  oracle plumbing was deleted. SDK ships with 3 StubHost tests
  covering happy path, host error, and garbage-hex.

  #6 (WIT world capability elision): added new "Capability
  enforcement vs. the WIT world" section to ADR-0009 documenting
  that price-alert + balance-tracker compile against the
  shepherd:cow/shepherd supertype but their manifests omit
  cow-api, and that boot success depends on wasm-tools' unused-
  import elision. Flagged as load-bearing; M5 macro hardening
  path documented.

  #7 (poll-time revert classification inert): filed COW-1082 for
  the host-side fix (forward structured eth_call error data into
  HostError.data; analogous to COW-1075 for orderbook).

  #8 (classify_api_error retry-default unbounded): filed COW-1083
  for the rate-limit / max-retry follow-up on the backoff: marker.

  #9 (RetryAction::Backoff dead variant): no code change; replied
  to thread clarifying it is reserved API surface waiting on a
  richer upstream retry_hint shape (open question for mfw78).

  #10 (no proptest anywhere): added `proptest` to shepherd-sdk
  dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs`
  with 6 properties covering eth_call_params/parse_eth_call_result
  round-trip, parse_eth_call_result rejection on unquoted input,
  config::scale_decimal round-trip + sign-preservation, U256 LE
  byte round-trip, and no-panic guards for decode_revert_hex +
  gpv2_to_order_data marker dispatch.

  #11 (ethflow chain capability least-privilege): moved `chain`
  from required to optional in `modules/ethflow-watcher/module.toml`,
  mirroring the M2 mirror fix already applied.

  #12 (ADR-0009 test-count census): dropped the "145 host tests
  (twap 20, ethflow 12, ...)" breakdown; kept the qualitative
  claim. CI is now the authoritative count.

Drive-by: alloy-sol-types moved from regular to dev-dependencies in
price-alert and stop-loss now that the Chainlink ABI helper is
inside shepherd-sdk and the modules only use sol! in their test
helpers.

Validation:
- cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained.
- cargo clippy --workspace --all-targets -- -D warnings: clean.
- cargo fmt --check: clean.
- cargo build --target wasm32-wasip2 --release for all 5 modules: clean.
- Zero em-dashes in source code added.
brunota20 added a commit that referenced this pull request Jun 25, 2026
12 review threads addressed end-to-end. Net diff is -720 lines despite
adding ~200 lines of new helpers + tests, because the WitBindgenHost
adapter deduplication alone wipes ~400 lines.

Per-thread:

  #1 (balance-tracker architecture): refactored to match the M3
  host-trait+adapter split the other 4 modules use. Created
  `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one /
  fetch_balance / parse_balance_hex / parse_settings into it,
  converted parse_config to use SDK config helpers + typed
  HostError instead of String. Added 3 MockHost-driven tests
  covering first-seen-above-threshold, below-threshold-persist,
  and error-does-not-abort-loop.

  #2 + #3 (WitBindgenHost dedup): new
  `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro.
  Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`;
  the 4 trait impls + convert_err / sdk_err_into_wit / convert_level
  collapse to one macro invocation per module. Migrated all 5
  modules (twap-monitor, ethflow-watcher, price-alert, stop-loss,
  balance-tracker). Each module's lib.rs lost ~80 lines.

  #4 (scale_decimal + config_get dup): new `shepherd_sdk::config`
  with `get_required`, `get_optional`, `scale_decimal`, and a
  typed `ConfigError` enum (host-neutral). price-alert + stop-loss
  consume the SDK helpers; their local duplicates were deleted.
  Module-level decimal-parsing tests removed (covered by 7 SDK
  tests + 4 proptest cases now).

  #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with
  `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`.
  Encapsulates the eth_call → parse → ABI decode flow + Warn
  logging. price-alert + stop-loss now call the helper; their
  local AggregatorV3 sol! definitions + read_oracle / on_block
  oracle plumbing was deleted. SDK ships with 3 StubHost tests
  covering happy path, host error, and garbage-hex.

  #6 (WIT world capability elision): added new "Capability
  enforcement vs. the WIT world" section to ADR-0009 documenting
  that price-alert + balance-tracker compile against the
  shepherd:cow/shepherd supertype but their manifests omit
  cow-api, and that boot success depends on wasm-tools' unused-
  import elision. Flagged as load-bearing; M5 macro hardening
  path documented.

  #7 (poll-time revert classification inert): filed COW-1082 for
  the host-side fix (forward structured eth_call error data into
  HostError.data; analogous to COW-1075 for orderbook).

  #8 (classify_api_error retry-default unbounded): filed COW-1083
  for the rate-limit / max-retry follow-up on the backoff: marker.

  #9 (RetryAction::Backoff dead variant): no code change; replied
  to thread clarifying it is reserved API surface waiting on a
  richer upstream retry_hint shape (open question for mfw78).

  #10 (no proptest anywhere): added `proptest` to shepherd-sdk
  dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs`
  with 6 properties covering eth_call_params/parse_eth_call_result
  round-trip, parse_eth_call_result rejection on unquoted input,
  config::scale_decimal round-trip + sign-preservation, U256 LE
  byte round-trip, and no-panic guards for decode_revert_hex +
  gpv2_to_order_data marker dispatch.

  #11 (ethflow chain capability least-privilege): moved `chain`
  from required to optional in `modules/ethflow-watcher/module.toml`,
  mirroring the M2 mirror fix already applied.

  #12 (ADR-0009 test-count census): dropped the "145 host tests
  (twap 20, ethflow 12, ...)" breakdown; kept the qualitative
  claim. CI is now the authoritative count.

Drive-by: alloy-sol-types moved from regular to dev-dependencies in
price-alert and stop-loss now that the Chainlink ABI helper is
inside shepherd-sdk and the modules only use sol! in their test
helpers.

Validation:
- cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained.
- cargo clippy --workspace --all-targets -- -D warnings: clean.
- cargo fmt --check: clean.
- cargo build --target wasm32-wasip2 --release for all 5 modules: clean.
- Zero em-dashes in source code added.
brunota20 added a commit that referenced this pull request Jun 25, 2026
12 review threads addressed end-to-end. Net diff is -720 lines despite
adding ~200 lines of new helpers + tests, because the WitBindgenHost
adapter deduplication alone wipes ~400 lines.

Per-thread:

  #1 (balance-tracker architecture): refactored to match the M3
  host-trait+adapter split the other 4 modules use. Created
  `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one /
  fetch_balance / parse_balance_hex / parse_settings into it,
  converted parse_config to use SDK config helpers + typed
  HostError instead of String. Added 3 MockHost-driven tests
  covering first-seen-above-threshold, below-threshold-persist,
  and error-does-not-abort-loop.

  #2 + #3 (WitBindgenHost dedup): new
  `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro.
  Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`;
  the 4 trait impls + convert_err / sdk_err_into_wit / convert_level
  collapse to one macro invocation per module. Migrated all 5
  modules (twap-monitor, ethflow-watcher, price-alert, stop-loss,
  balance-tracker). Each module's lib.rs lost ~80 lines.

  #4 (scale_decimal + config_get dup): new `shepherd_sdk::config`
  with `get_required`, `get_optional`, `scale_decimal`, and a
  typed `ConfigError` enum (host-neutral). price-alert + stop-loss
  consume the SDK helpers; their local duplicates were deleted.
  Module-level decimal-parsing tests removed (covered by 7 SDK
  tests + 4 proptest cases now).

  #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with
  `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`.
  Encapsulates the eth_call → parse → ABI decode flow + Warn
  logging. price-alert + stop-loss now call the helper; their
  local AggregatorV3 sol! definitions + read_oracle / on_block
  oracle plumbing was deleted. SDK ships with 3 StubHost tests
  covering happy path, host error, and garbage-hex.

  #6 (WIT world capability elision): added new "Capability
  enforcement vs. the WIT world" section to ADR-0009 documenting
  that price-alert + balance-tracker compile against the
  shepherd:cow/shepherd supertype but their manifests omit
  cow-api, and that boot success depends on wasm-tools' unused-
  import elision. Flagged as load-bearing; M5 macro hardening
  path documented.

  #7 (poll-time revert classification inert): filed COW-1082 for
  the host-side fix (forward structured eth_call error data into
  HostError.data; analogous to COW-1075 for orderbook).

  #8 (classify_api_error retry-default unbounded): filed COW-1083
  for the rate-limit / max-retry follow-up on the backoff: marker.

  #9 (RetryAction::Backoff dead variant): no code change; replied
  to thread clarifying it is reserved API surface waiting on a
  richer upstream retry_hint shape (open question for mfw78).

  #10 (no proptest anywhere): added `proptest` to shepherd-sdk
  dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs`
  with 6 properties covering eth_call_params/parse_eth_call_result
  round-trip, parse_eth_call_result rejection on unquoted input,
  config::scale_decimal round-trip + sign-preservation, U256 LE
  byte round-trip, and no-panic guards for decode_revert_hex +
  gpv2_to_order_data marker dispatch.

  #11 (ethflow chain capability least-privilege): moved `chain`
  from required to optional in `modules/ethflow-watcher/module.toml`,
  mirroring the M2 mirror fix already applied.

  #12 (ADR-0009 test-count census): dropped the "145 host tests
  (twap 20, ethflow 12, ...)" breakdown; kept the qualitative
  claim. CI is now the authoritative count.

Drive-by: alloy-sol-types moved from regular to dev-dependencies in
price-alert and stop-loss now that the Chainlink ABI helper is
inside shepherd-sdk and the modules only use sol! in their test
helpers.

Validation:
- cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained.
- cargo clippy --workspace --all-targets -- -D warnings: clean.
- cargo fmt --check: clean.
- cargo build --target wasm32-wasip2 --release for all 5 modules: clean.
- Zero em-dashes in source code added.
brunota20 added a commit that referenced this pull request Jun 25, 2026
12 review threads addressed end-to-end. Net diff is -720 lines despite
adding ~200 lines of new helpers + tests, because the WitBindgenHost
adapter deduplication alone wipes ~400 lines.

Per-thread:

  #1 (balance-tracker architecture): refactored to match the M3
  host-trait+adapter split the other 4 modules use. Created
  `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one /
  fetch_balance / parse_balance_hex / parse_settings into it,
  converted parse_config to use SDK config helpers + typed
  HostError instead of String. Added 3 MockHost-driven tests
  covering first-seen-above-threshold, below-threshold-persist,
  and error-does-not-abort-loop.

  #2 + #3 (WitBindgenHost dedup): new
  `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro.
  Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`;
  the 4 trait impls + convert_err / sdk_err_into_wit / convert_level
  collapse to one macro invocation per module. Migrated all 5
  modules (twap-monitor, ethflow-watcher, price-alert, stop-loss,
  balance-tracker). Each module's lib.rs lost ~80 lines.

  #4 (scale_decimal + config_get dup): new `shepherd_sdk::config`
  with `get_required`, `get_optional`, `scale_decimal`, and a
  typed `ConfigError` enum (host-neutral). price-alert + stop-loss
  consume the SDK helpers; their local duplicates were deleted.
  Module-level decimal-parsing tests removed (covered by 7 SDK
  tests + 4 proptest cases now).

  #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with
  `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`.
  Encapsulates the eth_call → parse → ABI decode flow + Warn
  logging. price-alert + stop-loss now call the helper; their
  local AggregatorV3 sol! definitions + read_oracle / on_block
  oracle plumbing was deleted. SDK ships with 3 StubHost tests
  covering happy path, host error, and garbage-hex.

  #6 (WIT world capability elision): added new "Capability
  enforcement vs. the WIT world" section to ADR-0009 documenting
  that price-alert + balance-tracker compile against the
  shepherd:cow/shepherd supertype but their manifests omit
  cow-api, and that boot success depends on wasm-tools' unused-
  import elision. Flagged as load-bearing; M5 macro hardening
  path documented.

  #7 (poll-time revert classification inert): filed COW-1082 for
  the host-side fix (forward structured eth_call error data into
  HostError.data; analogous to COW-1075 for orderbook).

  #8 (classify_api_error retry-default unbounded): filed COW-1083
  for the rate-limit / max-retry follow-up on the backoff: marker.

  #9 (RetryAction::Backoff dead variant): no code change; replied
  to thread clarifying it is reserved API surface waiting on a
  richer upstream retry_hint shape (open question for mfw78).

  #10 (no proptest anywhere): added `proptest` to shepherd-sdk
  dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs`
  with 6 properties covering eth_call_params/parse_eth_call_result
  round-trip, parse_eth_call_result rejection on unquoted input,
  config::scale_decimal round-trip + sign-preservation, U256 LE
  byte round-trip, and no-panic guards for decode_revert_hex +
  gpv2_to_order_data marker dispatch.

  #11 (ethflow chain capability least-privilege): moved `chain`
  from required to optional in `modules/ethflow-watcher/module.toml`,
  mirroring the M2 mirror fix already applied.

  #12 (ADR-0009 test-count census): dropped the "145 host tests
  (twap 20, ethflow 12, ...)" breakdown; kept the qualitative
  claim. CI is now the authoritative count.

Drive-by: alloy-sol-types moved from regular to dev-dependencies in
price-alert and stop-loss now that the Chainlink ABI helper is
inside shepherd-sdk and the modules only use sol! in their test
helpers.

Validation:
- cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained.
- cargo clippy --workspace --all-targets -- -D warnings: clean.
- cargo fmt --check: clean.
- cargo build --target wasm32-wasip2 --release for all 5 modules: clean.
- Zero em-dashes in source code added.
brunota20 added a commit that referenced this pull request Jun 25, 2026
12 review threads addressed end-to-end. Net diff is -720 lines despite
adding ~200 lines of new helpers + tests, because the WitBindgenHost
adapter deduplication alone wipes ~400 lines.

Per-thread:

  #1 (balance-tracker architecture): refactored to match the M3
  host-trait+adapter split the other 4 modules use. Created
  `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one /
  fetch_balance / parse_balance_hex / parse_settings into it,
  converted parse_config to use SDK config helpers + typed
  HostError instead of String. Added 3 MockHost-driven tests
  covering first-seen-above-threshold, below-threshold-persist,
  and error-does-not-abort-loop.

  #2 + #3 (WitBindgenHost dedup): new
  `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro.
  Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`;
  the 4 trait impls + convert_err / sdk_err_into_wit / convert_level
  collapse to one macro invocation per module. Migrated all 5
  modules (twap-monitor, ethflow-watcher, price-alert, stop-loss,
  balance-tracker). Each module's lib.rs lost ~80 lines.

  #4 (scale_decimal + config_get dup): new `shepherd_sdk::config`
  with `get_required`, `get_optional`, `scale_decimal`, and a
  typed `ConfigError` enum (host-neutral). price-alert + stop-loss
  consume the SDK helpers; their local duplicates were deleted.
  Module-level decimal-parsing tests removed (covered by 7 SDK
  tests + 4 proptest cases now).

  #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with
  `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`.
  Encapsulates the eth_call → parse → ABI decode flow + Warn
  logging. price-alert + stop-loss now call the helper; their
  local AggregatorV3 sol! definitions + read_oracle / on_block
  oracle plumbing was deleted. SDK ships with 3 StubHost tests
  covering happy path, host error, and garbage-hex.

  #6 (WIT world capability elision): added new "Capability
  enforcement vs. the WIT world" section to ADR-0009 documenting
  that price-alert + balance-tracker compile against the
  shepherd:cow/shepherd supertype but their manifests omit
  cow-api, and that boot success depends on wasm-tools' unused-
  import elision. Flagged as load-bearing; M5 macro hardening
  path documented.

  #7 (poll-time revert classification inert): filed COW-1082 for
  the host-side fix (forward structured eth_call error data into
  HostError.data; analogous to COW-1075 for orderbook).

  #8 (classify_api_error retry-default unbounded): filed COW-1083
  for the rate-limit / max-retry follow-up on the backoff: marker.

  #9 (RetryAction::Backoff dead variant): no code change; replied
  to thread clarifying it is reserved API surface waiting on a
  richer upstream retry_hint shape (open question for mfw78).

  #10 (no proptest anywhere): added `proptest` to shepherd-sdk
  dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs`
  with 6 properties covering eth_call_params/parse_eth_call_result
  round-trip, parse_eth_call_result rejection on unquoted input,
  config::scale_decimal round-trip + sign-preservation, U256 LE
  byte round-trip, and no-panic guards for decode_revert_hex +
  gpv2_to_order_data marker dispatch.

  #11 (ethflow chain capability least-privilege): moved `chain`
  from required to optional in `modules/ethflow-watcher/module.toml`,
  mirroring the M2 mirror fix already applied.

  #12 (ADR-0009 test-count census): dropped the "145 host tests
  (twap 20, ethflow 12, ...)" breakdown; kept the qualitative
  claim. CI is now the authoritative count.

Drive-by: alloy-sol-types moved from regular to dev-dependencies in
price-alert and stop-loss now that the Chainlink ABI helper is
inside shepherd-sdk and the modules only use sol! in their test
helpers.

Validation:
- cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained.
- cargo clippy --workspace --all-targets -- -D warnings: clean.
- cargo fmt --check: clean.
- cargo build --target wasm32-wasip2 --release for all 5 modules: clean.
- Zero em-dashes in source code added.
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