From 45ea6c0f0a14708aa4b61a59fd483d86ffc205ae Mon Sep 17 00:00:00 2001 From: mfw78 Date: Fri, 24 Jul 2026 00:36:35 +0000 Subject: [PATCH] examples: remove stop-loss module --- .github/workflows/ci.yml | 4 +- Cargo.lock | 15 - Cargo.toml | 1 - Dockerfile | 6 +- crates/nexum-runtime/src/supervisor/tests.rs | 2 +- crates/nexum-sdk/src/chain/chainlink.rs | 2 +- crates/nexum-sdk/src/config.rs | 3 +- docs/00-overview.md | 2 +- docs/05-sdk-design.md | 4 +- docs/deployment/docker.md | 2 +- docs/production.md | 5 +- docs/testing-runtime-harness.md | 2 +- engine.docker.toml | 6 +- engine.e2e.toml | 9 +- engine.m3.toml | 15 +- engine.soak.docker.toml | 4 - engine.soak.toml | 6 +- justfile | 15 +- modules/examples/stop-loss/Cargo.toml | 27 - modules/examples/stop-loss/module.toml | 72 --- modules/examples/stop-loss/src/lib.rs | 62 -- modules/examples/stop-loss/src/strategy.rs | 613 ------------------- scripts/e2e-report-gen.sh | 6 +- scripts/e2e-run.sh | 5 +- scripts/lib.sh | 3 +- 25 files changed, 35 insertions(+), 856 deletions(-) delete mode 100644 modules/examples/stop-loss/Cargo.toml delete mode 100644 modules/examples/stop-loss/module.toml delete mode 100644 modules/examples/stop-loss/src/lib.rs delete mode 100644 modules/examples/stop-loss/src/strategy.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e0f3b9f..deb34074 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: - uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2 with: tool: nextest - # Build all 18 guest wasms ONCE (17 modules + the cow adapter, + # Build all 17 guest wasms ONCE (16 modules + the cow adapter, # release/wasm32-wasip2): the single # source of truth for guest buildability and the artifacts the integration # tests load. Replaces the deleted 9-way build-module matrix, which recompiled @@ -88,7 +88,7 @@ jobs: run: | cargo build --release --target wasm32-wasip2 --locked \ -p example -p twap-monitor -p ethflow-watcher -p price-alert \ - -p balance-tracker -p stop-loss -p http-probe -p echo-venue \ + -p balance-tracker -p http-probe -p echo-venue \ -p echo-client -p echo-keeper -p clock-reader -p flaky-bomb -p flaky-venue \ -p fuel-bomb -p memory-bomb -p panic-bomb -p slow-host # Separate invocation on purpose: unifying `cow-venue/adapter` diff --git a/Cargo.lock b/Cargo.lock index 6733e983..9a1416af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5258,21 +5258,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stop-loss" -version = "0.1.0" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "cow-venue", - "cowprotocol", - "nexum-sdk", - "nexum-sdk-test", - "tracing", - "videre-sdk", - "wit-bindgen 0.59.0", -] - [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 37235e3f..27461972 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ members = [ "modules/examples/echo-venue", "modules/examples/http-probe", "modules/examples/price-alert", - "modules/examples/stop-loss", "modules/fixtures/clock-reader", "modules/fixtures/flaky-bomb", "modules/fixtures/flaky-venue", diff --git a/Dockerfile b/Dockerfile index fa19d90f..8cc4bf90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,7 +69,7 @@ COPY --from=planner /src/recipe.json recipe.json RUN cargo chef cook --release -p shepherd --recipe-path recipe.json \ && cargo chef cook --release --target wasm32-wasip2 \ -p twap-monitor -p ethflow-watcher -p price-alert \ - -p balance-tracker -p stop-loss --recipe-path recipe.json \ + -p balance-tracker --recipe-path recipe.json \ && cargo chef cook --release --target wasm32-wasip2 \ -p cow-venue --features cow-venue/adapter --recipe-path recipe.json @@ -82,14 +82,13 @@ COPY . . # is used verbatim so builds are reproducible. RUN cargo build -p shepherd --release --locked -# Five production modules plus the bundled cow venue adapter. The wasm +# Four production modules plus the bundled cow venue adapter. The wasm # artefacts land under # `target/wasm32-wasip2/release/.wasm`. RUN cargo build -p twap-monitor --target wasm32-wasip2 --release --locked \ && cargo build -p ethflow-watcher --target wasm32-wasip2 --release --locked \ && cargo build -p price-alert --target wasm32-wasip2 --release --locked \ && cargo build -p balance-tracker --target wasm32-wasip2 --release --locked \ - && cargo build -p stop-loss --target wasm32-wasip2 --release --locked \ && cargo build -p cow-venue --target wasm32-wasip2 --release --locked --features adapter # ----------------------------------------------------------------- runtime @@ -126,7 +125,6 @@ COPY --from=build /src/modules/twap-monitor/module.toml /opt/shepherd/manifes COPY --from=build /src/modules/ethflow-watcher/module.toml /opt/shepherd/manifests/ethflow-watcher.toml COPY --from=build /src/modules/examples/price-alert/module.toml /opt/shepherd/manifests/price-alert.toml COPY --from=build /src/modules/examples/balance-tracker/module.toml /opt/shepherd/manifests/balance-tracker.toml -COPY --from=build /src/modules/examples/stop-loss/module.toml /opt/shepherd/manifests/stop-loss.toml # The bundled cow venue adapter's manifests; installed via the # engine.toml [[adapters]] stanza, never compiled into the engine. diff --git a/crates/nexum-runtime/src/supervisor/tests.rs b/crates/nexum-runtime/src/supervisor/tests.rs index 947a17fc..829eae09 100644 --- a/crates/nexum-runtime/src/supervisor/tests.rs +++ b/crates/nexum-runtime/src/supervisor/tests.rs @@ -153,7 +153,7 @@ fn progress_marker_key_uses_numeric_chain_id() { /// corresponding select arm is never selected. /// /// Surfaced when wiring up `engine.m3.toml` for the M3 testnet runbook: -/// the 3 M3 example modules (price-alert, balance-tracker, stop-loss) +/// the M3 example modules (price-alert, balance-tracker) /// all subscribe to blocks only, no logs. The engine bailed within /// ~50 ms of `supervisor ready` until this fix landed. #[tokio::test] diff --git a/crates/nexum-sdk/src/chain/chainlink.rs b/crates/nexum-sdk/src/chain/chainlink.rs index 57c49b70..74e04051 100644 --- a/crates/nexum-sdk/src/chain/chainlink.rs +++ b/crates/nexum-sdk/src/chain/chainlink.rs @@ -4,7 +4,7 @@ //! latestRoundData.answer` flow against a Chainlink AggregatorV3 //! oracle. Returns `Some(answer)` on success or `None` on any host / //! decode failure (logging the failure at Warn). Used by oracle-driven -//! example modules (price-alert, stop-loss) so they consume the SDK +//! example modules (price-alert) so they consume the SDK //! instead of redefining the `AggregatorV3` ABI + read loop locally. //! //! The shape is deliberately `Option` rather than diff --git a/crates/nexum-sdk/src/config.rs b/crates/nexum-sdk/src/config.rs index fdfcfc9c..f1526293 100644 --- a/crates/nexum-sdk/src/config.rs +++ b/crates/nexum-sdk/src/config.rs @@ -6,8 +6,7 @@ //! repeatedly: required-key lookup, optional-key lookup, and decimal //! parsing for thresholds / amounts. Hoisting these here keeps the //! example modules consuming the SDK rather than re-implementing the -//! same loops around it (each copy in price-alert + stop-loss had -//! started to drift in error wording). +//! same loops around it. use alloy_primitives::{I256, U256}; use thiserror::Error; diff --git a/docs/00-overview.md b/docs/00-overview.md index 3e8427e9..c5d0a6e3 100755 --- a/docs/00-overview.md +++ b/docs/00-overview.md @@ -363,7 +363,7 @@ shepherd/ ├── modules/ │ ├── twap-monitor/ TWAP order monitoring module │ ├── ethflow-watcher/ Ethflow order monitoring module -│ └── examples/ price-alert, balance-tracker, stop-loss, http-probe reference modules +│ └── examples/ price-alert, balance-tracker, http-probe reference modules ├── wit/ │ ├── nexum-host/ Universal WIT package (chain, identity, local-store, remote-store, messaging, logging) │ └── shepherd-cow/ CoW Protocol WIT package (cow-api, shepherd) diff --git a/docs/05-sdk-design.md b/docs/05-sdk-design.md index 085bbf97..f77078e4 100755 --- a/docs/05-sdk-design.md +++ b/docs/05-sdk-design.md @@ -446,8 +446,8 @@ the venue stays orderbook-only: `sweep` slice composing the poll loop over the typed `CowClient`. The shipped CoW keepers - `modules/twap-monitor`, -`modules/ethflow-watcher`, `modules/examples/stop-loss` - are ordinary -`#[videre_sdk::keeper]` modules on this surface. +`modules/ethflow-watcher` - are ordinary `#[videre_sdk::keeper]` +modules on this surface. ## Non-Rust module and adapter authors diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index a01c4a4e..e6d1220d 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -101,7 +101,7 @@ manifest = "/opt/shepherd/manifests/twap-monitor.toml" [[modules]] path = "/opt/shepherd/modules/ethflow_watcher.wasm" manifest = "/opt/shepherd/manifests/ethflow-watcher.toml" -# Add price-alert / balance-tracker / stop-loss the same way. +# Add price-alert / balance-tracker the same way. ``` If you want compose to use this file instead of the bundled diff --git a/docs/production.md b/docs/production.md index 9d65236e..96f57755 100644 --- a/docs/production.md +++ b/docs/production.md @@ -219,8 +219,7 @@ The local-store is a single redb file at `last_dispatched_block:{chain_id}` keys; losing it on a production module forces a from-scratch resync (twap-monitor re-discovers `watch:` from the next `ConditionalOrderCreated` -log; stop-loss re-issues a `submitted:` write if the trigger -fires again). +log). ### 4.1 Cold backup (recommended for first deploy + before upgrades) @@ -461,7 +460,7 @@ defaults. | Class | Modules typical | Fuel/event | Memory cap | Notes | |---|---|---|---|---| | **Light indexer** | price-alert, balance-tracker | 200M | 16 MiB | Block-tick poll + 1-2 RPC reads. Defaults are 5× headroom. | -| **TWAP-style polling** | twap-monitor, stop-loss | 1B (default) | 64 MiB (default) | Per-block `getTradeableOrderWithSignature` calls per registered order; long ABI decode + signature work. Defaults sized for this case. | +| **TWAP-style polling** | twap-monitor | 1B (default) | 64 MiB (default) | Per-block `getTradeableOrderWithSignature` calls per registered order; long ABI decode + signature work. Defaults sized for this case. | | **Multi-chain swarm** | 5+ modules × 2+ chains | 2B | 128 MiB | More headroom for parallel dispatch overhead; modules don't share state, but the per-store wasmtime overhead is per-(module, chain). | A module that consistently traps `OutOfFuel` is a bug, not a diff --git a/docs/testing-runtime-harness.md b/docs/testing-runtime-harness.md index 1e2e4323..b8539b14 100644 --- a/docs/testing-runtime-harness.md +++ b/docs/testing-runtime-harness.md @@ -14,7 +14,7 @@ before writing a runtime test. `nexum-sdk-test::MockHost` (CoW modules: `shepherd-sdk-test::MockHost`). No wasmtime, no component boundary, no engine crate at all. This is already the dominant pattern across every shipped module (twap-monitor, - ethflow-watcher, stop-loss, price-alert, balance-tracker) - see + ethflow-watcher, price-alert, balance-tracker) - see [docs/sdk.md](sdk.md#companions-nexum-sdk-test-and-shepherd-sdk-test). **New module-logic tests belong here.** - **The engine harness (this page) is reserved for engine, host, and diff --git a/engine.docker.toml b/engine.docker.toml index 80331746..674e2863 100644 --- a/engine.docker.toml +++ b/engine.docker.toml @@ -55,7 +55,7 @@ rpc_url = "${BASE_RPC_URL}" # ---- modules ---- # -# The image bakes all five production modules at the paths below. +# The image bakes all four production modules at the paths below. # Comment out any you don't intend to run on this deployment. [[modules]] @@ -74,10 +74,6 @@ manifest = "/opt/shepherd/manifests/price-alert.toml" path = "/opt/shepherd/modules/balance_tracker.wasm" manifest = "/opt/shepherd/manifests/balance-tracker.toml" -[[modules]] -path = "/opt/shepherd/modules/stop_loss.wasm" -manifest = "/opt/shepherd/manifests/stop-loss.toml" - # ---- adapters ---- # # The bundled cow venue adapter: the venue registry resolves the `cow` diff --git a/engine.e2e.toml b/engine.e2e.toml index 31960778..3b664336 100644 --- a/engine.e2e.toml +++ b/engine.e2e.toml @@ -1,16 +1,15 @@ # E2E testnet integration config for nexum. # -# Boots all 5 production + example modules on Sepolia simultaneously +# Boots all 4 production + example modules on Sepolia simultaneously # for the 4-6 h E2E run: # # - twap-monitor (modules/twap-monitor) # - ethflow-watcher (modules/ethflow-watcher) # - price-alert (modules/examples/price-alert) # - balance-tracker (modules/examples/balance-tracker) -# - stop-loss (modules/examples/stop-loss) # # This is the integration step between the M3 single-chain runbook -# (`engine.m3.toml`, 3 modules) and the 7-day soak +# (`engine.m3.toml`, 2 modules) and the 7-day soak # (Sepolia + Arb Sepolia, all modules, no human-in-the-loop). The # E2E run validates correctness in a real-chain dispatch context; # the soak validates stability afterwards. @@ -62,10 +61,6 @@ manifest = "modules/examples/price-alert/module.toml" path = "target/wasm32-wasip2/release/balance_tracker.wasm" manifest = "modules/examples/balance-tracker/module.toml" -[[modules]] -path = "target/wasm32-wasip2/release/stop_loss.wasm" -manifest = "modules/examples/stop-loss/module.toml" - # --- adapters --------------------------------------------------------- # The cow venue adapter twap-monitor submits through (`just diff --git a/engine.m3.toml b/engine.m3.toml index fc21fefe..8889dd4b 100644 --- a/engine.m3.toml +++ b/engine.m3.toml @@ -1,16 +1,15 @@ # M3 smoke / validation config for nexum. # -# Boots the 3 M3 example modules (price-alert + balance-tracker + -# stop-loss) against Sepolia. The 3 modules exercise the full SDK -# helper surface (chain::request via Chainlink read, local-store -# diffing, pool submit through the cow adapter with PreSign). +# Boots the 2 M3 example modules (price-alert + balance-tracker) +# against Sepolia. The modules exercise the full SDK helper surface +# (chain::request via Chainlink read, local-store diffing, pool submit +# through the cow adapter with PreSign). # # Usage: # just run-m3 # # or: # cargo build -p price-alert --target wasm32-wasip2 --release # cargo build -p balance-tracker --target wasm32-wasip2 --release -# cargo build -p stop-loss --target wasm32-wasip2 --release # cargo build -p cow-venue --features adapter --target wasm32-wasip2 --release # cargo run -p shepherd -- --engine-config engine.m3.toml @@ -32,13 +31,9 @@ manifest = "modules/examples/price-alert/module.toml" path = "target/wasm32-wasip2/release/balance_tracker.wasm" manifest = "modules/examples/balance-tracker/module.toml" -[[modules]] -path = "target/wasm32-wasip2/release/stop_loss.wasm" -manifest = "modules/examples/stop-loss/module.toml" - # --- adapters --------------------------------------------------------- -# The cow venue adapter stop-loss submits through (`just +# The cow venue adapter the modules submit through (`just # build-cow-venue`). Sepolia manifest: the adapter's orderbook must # match the chain the oracle is read on. [[adapters]] diff --git a/engine.soak.docker.toml b/engine.soak.docker.toml index 7efd05cc..c3e93f39 100644 --- a/engine.soak.docker.toml +++ b/engine.soak.docker.toml @@ -43,10 +43,6 @@ manifest = "/opt/shepherd/manifests/price-alert.toml" path = "/opt/shepherd/modules/balance_tracker.wasm" manifest = "/opt/shepherd/manifests/balance-tracker.toml" -[[modules]] -path = "/opt/shepherd/modules/stop_loss.wasm" -manifest = "/opt/shepherd/manifests/stop-loss.toml" - # --- adapters ----------------------------------------------------------- # The cow venue adapter twap-monitor submits through. Sepolia diff --git a/engine.soak.toml b/engine.soak.toml index ba3e78d7..567be71b 100644 --- a/engine.soak.toml +++ b/engine.soak.toml @@ -8,7 +8,7 @@ # cargo build --release -p nexum-cli # cargo build --target wasm32-wasip2 --release \ # -p twap-monitor -p ethflow-watcher -p price-alert \ -# -p balance-tracker -p stop-loss +# -p balance-tracker # cargo build --target wasm32-wasip2 --release \ # -p cow-venue --features cow-venue/adapter # ./target/release/nexum --engine-config engine.soak.toml @@ -65,10 +65,6 @@ manifest = "modules/examples/price-alert/module.toml" path = "target/wasm32-wasip2/release/balance_tracker.wasm" manifest = "modules/examples/balance-tracker/module.toml" -[[modules]] -path = "target/wasm32-wasip2/release/stop_loss.wasm" -manifest = "modules/examples/stop-loss/module.toml" - # --- adapters --------------------------------------------------------- # The cow venue adapter twap-monitor submits through (`just diff --git a/justfile b/justfile index 019fb0a6..3d957bf4 100644 --- a/justfile +++ b/justfile @@ -46,15 +46,14 @@ build-m2: run-m2: build-m2 build-cow-venue build-engine cargo run -p shepherd -- --engine-config engine.m2.toml --pretty-logs -# Build the M3 example modules (price-alert + balance-tracker + stop-loss) -# for wasm32-wasip2. +# Build the M3 example modules (price-alert + balance-tracker) for +# wasm32-wasip2. build-m3: cargo build -p price-alert --target wasm32-wasip2 --release cargo build -p balance-tracker --target wasm32-wasip2 --release - cargo build -p stop-loss --target wasm32-wasip2 --release # Run nexum wired for the M3 smoke / validation scenario -# (Sepolia, 3 example modules). See `docs/operations/m3-testnet-runbook.md`. +# (Sepolia, 2 example modules). See `docs/operations/m3-testnet-runbook.md`. # --pretty-logs keeps the runbook-friendly human-readable formatter; # production deploys omit the flag and emit JSON. run-m3: build-m3 build-cow-venue build-engine @@ -65,11 +64,11 @@ run-m3: build-m3 build-cow-venue build-engine build-http-probe: cargo build -p http-probe --target wasm32-wasip2 --release -# Build all 5 modules required by the E2E run (twap-monitor + -# ethflow-watcher + price-alert + balance-tracker + stop-loss). +# Build all 4 modules required by the E2E run (twap-monitor + +# ethflow-watcher + price-alert + balance-tracker). build-e2e: build-m2 build-m3 -# Run the 4-6 h E2E integration scenario on Sepolia. All 5 modules +# Run the 4-6 h E2E integration scenario on Sepolia. All 4 modules # dispatched simultaneously against a live RPC; metrics scraped at # 127.0.0.1:9100/metrics. JSON logs (no --pretty-logs) so a # downstream `jq` filter can mine submitted/dropped/backoff markers @@ -108,7 +107,7 @@ ci: cargo doc --workspace --no-deps cargo build --release --target wasm32-wasip2 \ -p example -p twap-monitor -p ethflow-watcher -p price-alert \ - -p balance-tracker -p stop-loss -p http-probe -p echo-venue \ + -p balance-tracker -p http-probe -p echo-venue \ -p echo-client -p clock-reader -p flaky-bomb -p flaky-venue -p fuel-bomb \ -p memory-bomb -p panic-bomb -p slow-host cargo test --workspace --all-features --no-fail-fast diff --git a/modules/examples/stop-loss/Cargo.toml b/modules/examples/stop-loss/Cargo.toml deleted file mode 100644 index f0b92be4..00000000 --- a/modules/examples/stop-loss/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "stop-loss" -version = "0.1.0" -edition.workspace = true -license.workspace = true -repository.workspace = true -description = "Shepherd example module: stop-loss order submitter. Watches a Chainlink oracle, submits a CoW order intent through the venue registry when price drops below a configured trigger, dedups via the venue-and-body intent-id." - -[lib] -crate-type = ["cdylib"] - -[dependencies] -cow-venue = { path = "../../../crates/cow-venue", features = ["client"] } -nexum-sdk = { path = "../../../crates/nexum-sdk" } -videre-sdk = { path = "../../../crates/videre-sdk" } -cowprotocol = { version = "0.2.0", default-features = false } -alloy-primitives = { version = "1.6", default-features = false, features = ["std"] } -tracing = { version = "0.1", default-features = false } -wit-bindgen = { version = "0.59", default-features = false, features = ["macros", "realloc"] } - -[dev-dependencies] -# The chain-edge projections back the pinned-UID regression test. -cow-venue = { path = "../../../crates/cow-venue", features = ["client", "assembly"] } -nexum-sdk-test = { path = "../../../crates/nexum-sdk-test" } -# Only used by tests in `strategy.rs` to encode a synthetic oracle -# return body; the production code uses `nexum_sdk::chain::chainlink`. -alloy-sol-types = { version = "1.6", default-features = false, features = ["std"] } diff --git a/modules/examples/stop-loss/module.toml b/modules/examples/stop-loss/module.toml deleted file mode 100644 index b8387417..00000000 --- a/modules/examples/stop-loss/module.toml +++ /dev/null @@ -1,72 +0,0 @@ -# stop-loss example module: watches a Chainlink oracle and submits a -# CoW order intent through the venue registry when the price drops below the -# configured trigger. Demonstrates eth_call + the typed venue client + -# local-store dedup. - -[module] -name = "stop-loss" -version = "0.1.0" -component = "sha256:0000000000000000000000000000000000000000000000000000000000000000" - -[capabilities] -# - logging -> structured runtime logs -# - chain -> eth_call into the Chainlink aggregator -# - local-store -> submitted: / dropped: dedup markers -# - client -> videre:venue/client submit path to the cow adapter -required = ["logging", "chain", "local-store", "client"] -optional = [] - -[capabilities.http] -# All outbound HTTP is the cow adapter's; the module makes no direct -# `http` calls. -allow = [] - -# --- subscriptions ---------------------------------------------------- - -[[subscription]] -kind = "block" -chain_id = 11155111 # Sepolia - -# The one body-schema version this module encodes; install refuses the -# module unless every installed venue adapter decodes it. -[venue] -body_version = 1 - -# --- config ----------------------------------------------------------- - -[config] -# Chainlink AggregatorV3Interface address (ETH/USD on Sepolia). -oracle_address = "0x694AA1769357215DE4FAC081bf1f309aDC325306" -# Oracle's decimals (Chainlink USD pairs are 8). -decimals = "8" -# Trigger price in the oracle's native decimal units. The Sepolia -# Chainlink ETH/USD feed reports a mocked value around $1681 at the -# time of the E2E run (2026-06-18). Setting the trigger -# *above* the live price + direction=below ensures the strategy fires -# on the first block. -trigger_price = "2000.00" -# Order parameters. The owner pre-signs via GPv2Signing.setPreSignature -# (on-chain, outside this module); the cow adapter posts the unsigned -# body pre-sign on trigger. -# -# E2E run pinning: test EOA on Sepolia with 0.05 ETH -# balance. Without a pre-sign + a WETH wrap the orderbook will reject -# with TransferSimulationFailed, which classifies as retry-next-block; -# that itself is a valid terminal marker and proves the full submit -# path E2E. -owner = "0x7bF140727D27ea64b607E042f1225680B40ECa6A" -# WETH9 Sepolia (`wss://sepolia.etherscan.io/token/0xfff9976782d46cc05630d1f6ebab18b2324d6b14`). -sell_token = "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" -# COW token Sepolia (verified on-chain: name="CoW Protocol Token", -# symbol="COW", decimals=18). -buy_token = "0x0625aFB445C3B6B7B929342a04A22599fd5dBB59" -# 0.005 WETH (small enough to fit in the 0.01 WETH wrap budget the -# E2E runbook recommends; large enough that the orderbook's min- -# quote endpoint actually returns a price). -sell_amount_wei = "5000000000000000" -# 20 COW (conservative; current quote on cow.fi/sepolia at the time -# of the E2E run is ~30 COW per 0.005 WETH so a 20 COW buy_amount -# leaves room for slippage without making the order too generous). -buy_amount_wei = "20000000000000000000" -# uint32::MAX = order never expires. -valid_to_seconds = "4294967295" diff --git a/modules/examples/stop-loss/src/lib.rs b/modules/examples/stop-loss/src/lib.rs deleted file mode 100644 index fa226ed9..00000000 --- a/modules/examples/stop-loss/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! # stop-loss (example Shepherd module) -//! -//! Watches a Chainlink price oracle on every block. When the price -//! drops at or below `trigger_price`, the module submits a CoW order -//! intent through the venue registry using the parameters from -//! `module.toml::[config]` and persists a `submitted:` marker to dedup -//! re-poll attempts. The cow adapter posts the unsigned order -//! pre-sign; the owner is expected to call -//! `GPv2Signing.setPreSignature` on-chain ahead of the trigger so the -//! orderbook activates the submission. -//! -//! ## Module layout -//! -//! - `strategy.rs` holds the pure logic and unit tests against the -//! `nexum_sdk::host` trait seams and the videre `VenueTransport` -//! seam. It does not know `wit-bindgen` exists. -//! - `lib.rs` (this file) is the `#[videre_sdk::keeper]` glue: the -//! macro derives the component world from `module.toml`, emits the -//! `WitBindgenHost` adapter, and dispatches each event variant to -//! `strategy` with the typed [`CowClient`] over the module's own -//! `videre:venue/client` import. - -// wit_bindgen::generate! expands to host-import shims whose arity -// matches the WIT signatures, which can exceed clippy's -// too-many-arguments threshold. -#![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![allow(clippy::too_many_arguments)] - -mod strategy; - -use std::sync::OnceLock; - -use cow_venue::CowClient; - -static SETTINGS: OnceLock = OnceLock::new(); - -struct StopLoss; - -#[videre_sdk::keeper] -impl StopLoss { - fn init(config: Vec<(String, String)>) -> Result<(), Fault> { - install_tracing(); - let cfg = strategy::parse_config(&config)?; - tracing::info!( - "stop-loss init: owner={:#x} trigger={} sell={:#x} buy={:#x}", - cfg.owner, - cfg.trigger_price_scaled, - cfg.sell_token, - cfg.buy_token, - ); - let _ = SETTINGS.set(cfg); - Ok(()) - } - - fn on_block(block: nexum::host::types::Block) -> Result<(), Fault> { - let Some(cfg) = SETTINGS.get() else { - return Ok(()); - }; - strategy::on_block(&WitBindgenHost, &CowClient::new(), block.chain_id, cfg)?; - Ok(()) - } -} diff --git a/modules/examples/stop-loss/src/strategy.rs b/modules/examples/stop-loss/src/strategy.rs deleted file mode 100644 index 7891f40d..00000000 --- a/modules/examples/stop-loss/src/strategy.rs +++ /dev/null @@ -1,613 +0,0 @@ -//! Pure stop-loss strategy logic. Reads an oracle, optionally submits -//! a CoW order intent through the typed venue client, dedups via -//! local-store. Every interaction with the world flows through the -//! `nexum_sdk::host` trait seams and the videre [`VenueTransport`] -//! under the typed [`CowClient`], so tests drive it against -//! `nexum_sdk_test::MockHost` and a scripted transport. - -use alloy_primitives::I256; -use cow_venue::{BuyToken, CowClient, CowIntent, CowIntentBody, OrderBody, SellToken, intent_id}; -use nexum_sdk::chain::chainlink::read_latest_answer; -use nexum_sdk::config::{self, ConfigError}; -use nexum_sdk::host::{ChainHost, Fault, LocalStoreHost, LoggingHost}; -use nexum_sdk::keeper::RetryAction; -use nexum_sdk::prelude::{Address, U256, hex}; -use videre_sdk::keeper::retry_action; -use videre_sdk::{ClientError, SubmitOutcome, VenueTransport, rt}; - -/// Resolved configuration parsed from `module.toml::[config]`. -#[derive(Clone, Debug)] -pub struct Settings { - /// Chainlink AggregatorV3Interface address. - pub oracle_address: Address, - /// Trigger price scaled to the oracle's native units. - pub trigger_price_scaled: I256, - /// Order owner (= the `setPreSignature` caller and buy-token - /// receiver). - pub owner: Address, - /// Sell side of the order. - pub sell_token: Address, - /// Buy side of the order. - pub buy_token: Address, - /// Sell amount in atomic units of `sell_token`. - pub sell_amount: U256, - /// Buy amount in atomic units of `buy_token`. - pub buy_amount: U256, - /// Order expiry (Unix seconds). - pub valid_to: u32, -} - -/// React to a new block. -/// -/// Returns `Ok(())` on success and on recoverable upstream failures -/// (oracle RPC error, decode failure, venue refusal). Only host-store -/// errors bubble up via `?` so the supervisor can surface persistence -/// issues - all other faults log and let the next block re-poll. -pub fn on_block( - host: &H, - venue: &CowClient, - chain_id: u64, - settings: &Settings, -) -> Result<(), Fault> -where - H: ChainHost + LoggingHost + LocalStoreHost, - T: VenueTransport, -{ - let price = match read_latest_answer(host, chain_id, settings.oracle_address, "stop-loss") { - Some(p) => p, - None => return Ok(()), // logged inside read_latest_answer - }; - - if price > settings.trigger_price_scaled { - tracing::info!( - price = %price, - trigger = %settings.trigger_price_scaled, - "stop-loss idle", - ); - return Ok(()); - } - - // Derive the venue-and-body intent-id up-front so the dedup guard - // runs before any network work. - let intent = build_intent(settings); - let id = match intent_id(&intent) { - Ok(id) => id, - Err(e) => { - tracing::error!(error = %e, "intent body encode failed"); - return Ok(()); - } - }; - let dedup_key = format!("submitted:{id}"); - if host.get(&dedup_key)?.is_some() { - tracing::info!(intent = %id, "stop-loss already submitted, idle"); - return Ok(()); - } - let dropped_key = format!("dropped:{id}"); - if host.get(&dropped_key)?.is_some() { - tracing::info!(intent = %id, "stop-loss previously dropped, idle"); - return Ok(()); - } - - let Some(outcome) = rt::complete(venue.submit(&intent)) else { - // Guest transports never suspend; retry on the next block. - tracing::error!("stop-loss submit future suspended; retrying next block"); - return Ok(()); - }; - match outcome { - Ok(SubmitOutcome::Accepted(receipt)) => { - host.set(&dedup_key, b"")?; - tracing::warn!( - price = %price, - trigger = %settings.trigger_price_scaled, - receipt = %hex::encode_prefixed(&receipt), - "stop-loss TRIGGERED", - ); - } - Ok(SubmitOutcome::RequiresSigning(_)) => { - // The orderbook holds the order as signature-pending; the - // owner activates it with the on-chain `setPreSignature` - // call made ahead of the trigger. Journalled so the next - // block idles instead of re-posting. - host.set(&dedup_key, b"")?; - tracing::warn!( - price = %price, - trigger = %settings.trigger_price_scaled, - "stop-loss TRIGGERED (pre-sign pending on-chain activation)", - ); - } - Err(ClientError::Body(e)) => { - tracing::error!(error = %e, "intent body encode failed"); - } - Err(ClientError::Venue(fault)) => match retry_action(&fault) { - RetryAction::TryNextBlock | RetryAction::Backoff { .. } => { - tracing::warn!(error = %fault, "stop-loss retry on next block"); - } - RetryAction::Drop => { - host.set(&dropped_key, b"")?; - tracing::warn!(intent = %id, error = %fault, "stop-loss dropped"); - } - // `RetryAction` is `#[non_exhaustive]`; treat unknown - // future variants like `TryNextBlock` rather than - // silently dropping the order on an SDK bump. - _ => { - tracing::warn!( - error = %fault, - "stop-loss unknown retry-action - retry on next block", - ); - } - }, - // `ClientError` is non-exhaustive; retry on the next block. - Err(e) => tracing::error!(error = %e, "stop-loss submit failed"), - } - Ok(()) -} - -/// Assemble the order intent from settings: an unsigned order the cow -/// adapter posts pre-sign. The owner receives the buy token and the -/// app-data hash pins the canonical empty document. -fn build_intent(settings: &Settings) -> CowIntentBody { - let order = OrderBody::sell( - SellToken(settings.sell_token.into_array()), - settings.sell_amount.to_be_bytes(), - ) - .for_at_least( - BuyToken(settings.buy_token.into_array()), - settings.buy_amount.to_be_bytes(), - ) - .valid_to(settings.valid_to) - .receiver(settings.owner.into_array()) - .app_data(cowprotocol::EMPTY_APP_DATA_HASH.0) - .build(); - CowIntentBody::V1(CowIntent::Order(order)) -} - -/// Parse `module.toml::[config]` into a typed [`Settings`]. -pub fn parse_config(entries: &[(String, String)]) -> Result { - let oracle_address = config::get_required(entries, "oracle_address") - .map_err(config_err)? - .parse::
() - .map_err(|e| invalid(format!("oracle_address: {e}")))?; - let decimals = config::get_required(entries, "decimals") - .map_err(config_err)? - .parse::() - .map_err(|e| invalid(format!("decimals: {e}")))?; - if decimals > 38 { - return Err(invalid(format!( - "decimals={decimals} exceeds the I256 power-of-ten budget" - ))); - } - let trigger_price_scaled = config::scale_decimal( - config::get_required(entries, "trigger_price").map_err(config_err)?, - decimals, - "trigger_price", - ) - .map_err(config_err)?; - let owner = config::get_required(entries, "owner") - .map_err(config_err)? - .parse::
() - .map_err(|e| invalid(format!("owner: {e}")))?; - let sell_token = config::get_required(entries, "sell_token") - .map_err(config_err)? - .parse::
() - .map_err(|e| invalid(format!("sell_token: {e}")))?; - let buy_token = config::get_required(entries, "buy_token") - .map_err(config_err)? - .parse::
() - .map_err(|e| invalid(format!("buy_token: {e}")))?; - let sell_amount = config::get_required(entries, "sell_amount_wei") - .map_err(config_err)? - .parse::() - .map_err(|e| invalid(format!("sell_amount_wei: {e}")))?; - let buy_amount = config::get_required(entries, "buy_amount_wei") - .map_err(config_err)? - .parse::() - .map_err(|e| invalid(format!("buy_amount_wei: {e}")))?; - let valid_to = config::get_required(entries, "valid_to_seconds") - .map_err(config_err)? - .parse::() - .map_err(|e| invalid(format!("valid_to_seconds: {e}")))?; - Ok(Settings { - oracle_address, - trigger_price_scaled, - owner, - sell_token, - buy_token, - sell_amount, - buy_amount, - valid_to, - }) -} - -/// Lift a free-text invalid-config detail into a [`Fault::InvalidInput`]. -/// Used when the SDK helper does not own the error (e.g. an -/// `Address::from_str` failure or a `U256::from_str` overflow). -fn invalid(message: impl Into) -> Fault { - Fault::InvalidInput(message.into()) -} - -/// Project a `nexum_sdk::config::ConfigError` into a -/// [`Fault::InvalidInput`] via `Display`. -fn config_err(e: ConfigError) -> Fault { - invalid(e.to_string()) -} - -#[cfg(test)] -mod tests { - use std::cell::RefCell; - use std::collections::VecDeque; - - use alloy_primitives::hex; - use alloy_sol_types::SolCall; - use nexum_sdk::Level; - use nexum_sdk::chain::chainlink::AggregatorV3; - use nexum_sdk::chain::eth_call_params; - use nexum_sdk::host::ChainError; - use nexum_sdk_test::{MockHost, capture_tracing}; - use videre_sdk::client::sealed::SealedTransport; - use videre_sdk::{IntentStatus, Quotation, UnsignedTx, VenueFault, VenueId}; - - use super::*; - - const SEPOLIA: u64 = 11_155_111; - - /// Scripted venue transport: one submit outcome per queued entry, - /// every submit recorded. - #[derive(Default)] - struct MockVenue { - outcomes: RefCell>>, - submits: RefCell)>>, - } - - impl MockVenue { - fn enqueue_submit(&self, outcome: Result) { - self.outcomes.borrow_mut().push_back(outcome); - } - - fn submit_count(&self) -> usize { - self.submits.borrow().len() - } - } - - impl SealedTransport for &MockVenue {} - - impl VenueTransport for &MockVenue { - async fn quote(&self, _venue: &VenueId, _body: Vec) -> Result { - unreachable!("quote not exercised") - } - - async fn submit( - &self, - venue: &VenueId, - body: Vec, - ) -> Result { - self.submits.borrow_mut().push((venue.to_string(), body)); - self.outcomes.borrow_mut().pop_front().unwrap_or_else(|| { - Err(VenueFault::Unavailable( - "MockVenue: unscripted submit".into(), - )) - }) - } - - async fn status( - &self, - _venue: &VenueId, - _receipt: &[u8], - ) -> Result { - unreachable!("status not exercised") - } - - async fn cancel(&self, _venue: &VenueId, _receipt: &[u8]) -> Result<(), VenueFault> { - unreachable!("cancel not exercised") - } - } - - fn client(venue: &MockVenue) -> CowClient<&MockVenue> { - CowClient::with_transport(venue) - } - - fn settings_below(trigger_scaled: i128) -> Settings { - Settings { - oracle_address: "0x694AA1769357215DE4FAC081bf1f309aDC325306" - .parse() - .unwrap(), - trigger_price_scaled: I256::try_from(trigger_scaled).unwrap(), - owner: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" - .parse() - .unwrap(), - sell_token: "0x6810e776880C02933D47DB1b9fc05908e5386b96" - .parse() - .unwrap(), - buy_token: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14" - .parse() - .unwrap(), - sell_amount: U256::from(1_000_000_000_000_000_000_u128), - buy_amount: U256::from(300_000_000_000_000_000_u128), - valid_to: u32::MAX, - } - } - - fn oracle_response_json(answer_scaled: i128) -> String { - use alloy_primitives::aliases::U80; - let returns = AggregatorV3::latestRoundDataReturn { - roundId: U80::ZERO, - answer: I256::try_from(answer_scaled).unwrap(), - startedAt: U256::ZERO, - updatedAt: U256::ZERO, - answeredInRound: U80::ZERO, - }; - let encoded = AggregatorV3::latestRoundDataCall::abi_encode_returns(&returns); - let hex_body = hex::encode_prefixed(encoded); - format!("\"{hex_body}\"") - } - - fn program_oracle(host: &MockHost, oracle: Address, response: Result) { - let call_data = AggregatorV3::latestRoundDataCall {}.abi_encode(); - let params = eth_call_params(&oracle, &call_data); - host.chain.respond_to("eth_call", ¶ms, response); - } - - fn programmed_id(settings: &Settings) -> String { - intent_id(&build_intent(settings)).unwrap() - } - - /// Regression test pinning the orderbook UID derived from the - /// E2E run's `modules/examples/stop-loss/module.toml` config so an - /// operator can `setPreSignature(uid, true)` ahead of the run - /// without re-deriving the UID from the EIP-712 / domain- - /// separator dance. If this assertion ever flips, either: - /// (a) the module.toml has drifted from the pinned settings, or - /// (b) the EIP-712 type-hash / domain-separator changed, - /// and the runbook's `setPreSignature` step needs the new UID. - #[test] - fn e2e_settings_yield_expected_uid() { - let settings = Settings { - oracle_address: "0x694AA1769357215DE4FAC081bf1f309aDC325306" - .parse() - .unwrap(), - trigger_price_scaled: I256::try_from(200_000_000_000_i128).unwrap(), - owner: "0x7bF140727D27ea64b607E042f1225680B40ECa6A" - .parse() - .unwrap(), - sell_token: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" - .parse() - .unwrap(), - buy_token: "0x0625aFB445C3B6B7B929342a04A22599fd5dBB59" - .parse() - .unwrap(), - sell_amount: U256::from(5_000_000_000_000_000_u128), - buy_amount: U256::from(20_000_000_000_000_000_000_u128), - valid_to: u32::MAX, - }; - let CowIntentBody::V1(CowIntent::Order(body)) = build_intent(&settings) else { - panic!("stop-loss emits an unsigned order intent"); - }; - let order = cow_venue::assembly::body_to_order_data(&body); - let uid = cow_venue::assembly::order_uid( - cowprotocol::Chain::try_from(SEPOLIA).unwrap(), - &order, - settings.owner, - ); - assert_eq!( - format!("{uid}"), - "0xc2b9cb4ea1ee5a86d8049ac09d8f494bf04cca0a68407285f31e2e6379800be87bf140727d27ea64b607e042f1225680b40eca6affffffff", - ); - } - - #[test] - fn idle_when_price_above_trigger() { - let host = MockHost::new(); - let venue = MockVenue::default(); - let s = settings_below(/*trigger*/ 250_000_000_000); - program_oracle( - &host, - s.oracle_address, - Ok(oracle_response_json(300_000_000_000)), - ); - - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - - assert_eq!(venue.submit_count(), 0); - assert_eq!(host.store.len(), 0); - assert_eq!( - host.chain.call_count(), - 1, - "oracle consulted: idle because above trigger, not because unread" - ); - } - - #[test] - fn triggers_and_submits_once_then_dedups() { - let host = MockHost::new(); - let venue = MockVenue::default(); - let s = settings_below(250_000_000_000); - program_oracle( - &host, - s.oracle_address, - Ok(oracle_response_json(200_000_000_000)), - ); - venue.enqueue_submit(Ok(SubmitOutcome::Accepted(vec![0xAA; 56]))); - - // First block: submits. - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - assert_eq!(venue.submit_count(), 1); - let id = programmed_id(&s); - assert!( - host.store - .snapshot() - .contains_key(&format!("submitted:{id}")) - ); - - // Second block at the same price: dedup'd, no new submit. - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - assert_eq!(venue.submit_count(), 1); - assert_eq!( - host.chain.call_count(), - 2, - "oracle still polled each block; dedup is at the submit stage" - ); - } - - /// The adapter posts the unsigned order pre-sign and asks for the - /// on-chain activation: the intent is journalled so the next block - /// idles instead of re-posting. - #[test] - fn requires_signing_outcome_records_the_marker_and_idles() { - let host = MockHost::new(); - let venue = MockVenue::default(); - let s = settings_below(250_000_000_000); - program_oracle( - &host, - s.oracle_address, - Ok(oracle_response_json(200_000_000_000)), - ); - venue.enqueue_submit(Ok(SubmitOutcome::RequiresSigning(UnsignedTx { - chain: SEPOLIA, - to: vec![0x11; 20], - value: Vec::new(), - data: vec![0x22], - }))); - - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - - let id = programmed_id(&s); - assert!( - host.store - .snapshot() - .contains_key(&format!("submitted:{id}")) - ); - - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - assert_eq!(venue.submit_count(), 1); - } - - #[test] - fn permanent_submit_error_marks_dropped() { - let host = MockHost::new(); - let venue = MockVenue::default(); - let s = settings_below(250_000_000_000); - program_oracle( - &host, - s.oracle_address, - Ok(oracle_response_json(200_000_000_000)), - ); - - // A structured permanent refusal - `Denied` classifies as - // `Drop` in the videre retry table. - venue.enqueue_submit(Err(VenueFault::Denied("InvalidSignature: bad sig".into()))); - - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - let id = programmed_id(&s); - assert!(host.store.snapshot().contains_key(&format!("dropped:{id}"))); - assert!( - !host - .store - .snapshot() - .contains_key(&format!("submitted:{id}")) - ); - - // Second block: dropped marker idles the loop. - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - assert_eq!(venue.submit_count(), 1); // no resubmit - } - - #[test] - fn transient_submit_error_leaves_state_unchanged() { - let host = MockHost::new(); - let venue = MockVenue::default(); - let s = settings_below(250_000_000_000); - program_oracle( - &host, - s.oracle_address, - Ok(oracle_response_json(200_000_000_000)), - ); - - venue.enqueue_submit(Err(VenueFault::Unavailable("orderbook http 502".into()))); - - let (result, logs) = capture_tracing(|| on_block(&host, &client(&venue), SEPOLIA, &s)); - result.unwrap(); - - // No persistence flag - next block will retry. - assert_eq!(host.store.len(), 0); - assert_eq!(venue.submit_count(), 1, "the submit was attempted"); - logs.expect_one(|e| e.level == Level::WARN && e.message.contains("retry on next block")); - } - - #[test] - fn oracle_rpc_error_is_warn_and_continue() { - let host = MockHost::new(); - let venue = MockVenue::default(); - let s = settings_below(250_000_000_000); - program_oracle( - &host, - s.oracle_address, - Err(ChainError::Fault(Fault::Timeout)), - ); - - on_block(&host, &client(&venue), SEPOLIA, &s).unwrap(); - - assert_eq!(venue.submit_count(), 0); - assert_eq!(host.store.len(), 0); - assert!(host.logging.contains("oracle eth_call failed")); - } - - #[test] - fn parse_config_round_trips_settings() { - let entries = vec![ - ( - "oracle_address".into(), - "0x694AA1769357215DE4FAC081bf1f309aDC325306".into(), - ), - ("decimals".into(), "8".into()), - ("trigger_price".into(), "2500.00".into()), - ( - "owner".into(), - "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".into(), - ), - ( - "sell_token".into(), - "0x6810e776880C02933D47DB1b9fc05908e5386b96".into(), - ), - ( - "buy_token".into(), - "0xfff9976782d46cc05630d1f6ebab18b2324d6b14".into(), - ), - ("sell_amount_wei".into(), "1000000000000000000".into()), - ("buy_amount_wei".into(), "300000000000000000".into()), - ("valid_to_seconds".into(), "4294967295".into()), - ]; - let s = parse_config(&entries).unwrap(); - assert_eq!(s.valid_to, u32::MAX); - assert_eq!( - s.trigger_price_scaled, - I256::try_from(250_000_000_000_i64).unwrap() - ); - } - - #[test] - fn parse_config_rejects_missing_owner() { - let entries = vec![ - ( - "oracle_address".into(), - "0x694AA1769357215DE4FAC081bf1f309aDC325306".into(), - ), - ("decimals".into(), "8".into()), - ("trigger_price".into(), "1.0".into()), - ( - "sell_token".into(), - "0x6810e776880C02933D47DB1b9fc05908e5386b96".into(), - ), - ( - "buy_token".into(), - "0xfff9976782d46cc05630d1f6ebab18b2324d6b14".into(), - ), - ("sell_amount_wei".into(), "1".into()), - ("buy_amount_wei".into(), "1".into()), - ("valid_to_seconds".into(), "1".into()), - ]; - let err = parse_config(&entries).unwrap_err(); - let Fault::InvalidInput(message) = err else { - panic!("expected invalid-input fault, got {err:?}"); - }; - assert!(message.contains("owner")); - } -} diff --git a/scripts/e2e-report-gen.sh b/scripts/e2e-report-gen.sh index 29f156bf..3d9161d9 100755 --- a/scripts/e2e-report-gen.sh +++ b/scripts/e2e-report-gen.sh @@ -40,7 +40,7 @@ LOG, M_START, M_END, START_ISO, END_ISO, TEMPLATE, OUT, STATE = sys.argv[1:9] # ── Parse engine log ───────────────────────────────────────────────── blocks = [] # list of dispatched block_numbers (per module, but we just want range) -markers = {m: [] for m in ("twap-monitor","ethflow-watcher","price-alert","balance-tracker","stop-loss")} +markers = {m: [] for m in ("twap-monitor","ethflow-watcher","price-alert","balance-tracker")} errors = [] trapped = [] poisoned = [] @@ -56,8 +56,6 @@ MARKER_PATTERNS = { # balance-tracker logs each per-block diff as # "0x changed +N wei (prior=..., current=...)". "balance-tracker": ["changed +", "changed -"], - "stop-loss": ["TRIGGERED", "retry on next block", "stop-loss submitted", - "stop-loss dropped", "already submitted", "submitted:"], } def event_field(ev, key, default=None): @@ -234,7 +232,7 @@ lines.append("## 4. Per-module terminal-state markers") lines.append("") lines.append("| Module | First marker | Sample line |") lines.append("|---|---|---|") -for m in ("twap-monitor","ethflow-watcher","price-alert","balance-tracker","stop-loss"): +for m in ("twap-monitor","ethflow-watcher","price-alert","balance-tracker"): if markers[m]: first = markers[m][0] # Truncate the marker line for the table diff --git a/scripts/e2e-run.sh b/scripts/e2e-run.sh index 8ec2f4e1..b3ec7aac 100755 --- a/scripts/e2e-run.sh +++ b/scripts/e2e-run.sh @@ -6,7 +6,7 @@ # operator's RPC URL (with key) substituted in. Local file is # gitignored. # 3. Cleans data/e2e for a fresh local-store. -# 4. Builds all 5 modules + the engine. +# 4. Builds all 4 modules + the engine. # 5. Launches shepherd via nohup, redirecting stdout/stderr to # docs/operations/e2e-reports/engine-.log. JSON logs # (no --pretty-logs) so e2e-report-gen.sh can mine them with jq. @@ -44,14 +44,13 @@ render_engine_config log "cleaning local-store at $REPO_ROOT/data/e2e" rm -rf "$REPO_ROOT/data/e2e" -log "building 5 modules + engine (this can take a minute on first run)" +log "building 4 modules + engine (this can take a minute on first run)" ( cd "$REPO_ROOT" cargo build -p twap-monitor --target wasm32-wasip2 --release >/dev/null cargo build -p ethflow-watcher --target wasm32-wasip2 --release >/dev/null cargo build -p price-alert --target wasm32-wasip2 --release >/dev/null cargo build -p balance-tracker --target wasm32-wasip2 --release >/dev/null - cargo build -p stop-loss --target wasm32-wasip2 --release >/dev/null cargo build -p shepherd --release >/dev/null ) diff --git a/scripts/lib.sh b/scripts/lib.sh index 229a83df..0e773ecb 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -12,8 +12,7 @@ STATE_FILE="$SCRIPT_DIR/.state" REPORTS_DIR="$REPO_ROOT/docs/operations/e2e-reports" # Pinned identities — match docs/operations/e2e-prep.md -# section 0. If you change one, change them in lock-step and re-run -# `cargo test -p stop-loss --lib e2e_settings_yield_expected_uid`. +# section 0. If you change one, change them in lock-step. TEST_EOA="0x7bF140727D27ea64b607E042f1225680B40ECa6A" TEST_SAFE="0x14995a1118Caf95833e923faf8Dd155721cd53c2" COMPOSABLE_COW="0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74"