cow: build the cow venue adapter component with timeout transport middleware#467
cow: build the cow venue adapter component with timeout transport middleware#467mfw78 wants to merge 1 commit into
Conversation
…dleware cow-venue gains the adapter slice: CowAdapter under #[videre_sdk::venue] decodes CowIntentBody, derives the value-flow header, and speaks the orderbook REST API over scoped wasi:http. A signed order posts EIP-1271 and its receipt is the canonical 56-byte UID; an unsigned order posts pre-sign and returns requires-signing carrying the setPreSignature call. An already-held rejection is success with the client-derived UID, and errorType rejections project onto venue-error through the shipped classification table, so the retry hint survives the collapse. The chain-edge order assembly (gpv2_to_order_data, order_data_to_body, order_uid_hex, build_order_creation) moves into the new assembly slice the adapter owns; shepherd-sdk re-exports it for the legacy keeper run. videre-sdk gains TimedFetch, the per-request timeout middleware every adapter request rides. Codec vectors and header goldens are published under tests/vectors with a conformance suite, and the built component bundles into the shepherd distribution via the engine adapters stanza (example and docker configs, justfile, Dockerfile, CI wasm build).
lgahdl
left a comment
There was a problem hiding this comment.
Thorough review given the stakes here (real funds/orders flowing through this eventually) — the vast majority of this checks out clean: EIP-1271 signature attachment is correctly wired to the same decoded body throughout, the pre-sign flow's requires-signing carries the real contract address/UID/calldata with no path that marks an order locally 'live' before the host actually sends the tx, TimedFetch genuinely clamps every adapter request with no bypass found, errorType classification correctly preserves retry hints through the collapse and reuses the already-ratified table from #464 unchanged, the assembly slice move is a pure relocation with the legacy keeper and new adapter now genuinely sharing one function rather than two copies, wasi:http is scoped to exactly api.cow.fi, and no unsafe unwraps exist outside test code. A few things worth a look:
| .map_err(|e| VenueError::InvalidBody(e.to_string()))?; | ||
| let uid = match post_order(fetch, config, &creation)? { | ||
| Posted::Accepted(uid) => uid, | ||
| Posted::AlreadyHeld => assembly::order_uid(config.chain, &order, owner), |
There was a problem hiding this comment.
On Posted::AlreadyHeld, the returned UID is computed purely client-side (assembly::order_uid) with nothing from the server to cross-check it against — the CoW API's 409/already-held response carries no UID in its body by design, so this is the only lever available. This is inherent to the "already-held is success" design, not something this diff introduced a bug into, but it's worth having status_with (called right after, in the same flow) hard-fail loudly if the returned order's sell/buy token or validTo don't match the just-submitted body, rather than trusting the locally-derived UID alone all the way through. Same consideration applies to the Accepted branch just above (line 171) — nothing asserts the orderbook's actual stored order matches what was locally derived, which would catch an orderbook/library UID-derivation drift immediately instead of silently trusting the server.
| impl Refusal { | ||
| /// Collapse for call sites where already-held is not a success | ||
| /// shape (reads); the orderbook only emits it on submission. | ||
| fn into_error(self) -> VenueError { |
There was a problem hiding this comment.
already_held is correct as success for submit, but this into_error() exists specifically because status/quote reads must NOT treat it as success — nothing at the type level forces a future call site to pick the right interpretation if someone adds a new read-path call to refusal() and forgets .into_error(). Consider splitting into refusal_for_submit/refusal_for_read (or renaming) to make the caller's obligation explicit rather than relying on every future caller remembering which context they're in.
What
Adds the
adapterfeature slice tocow-venue:CowAdapterunder#[videre_sdk::venue]decodesCowIntentBody, derives the value-flowheader, and speaks the orderbook REST API over scoped
wasi:http. Asigned order posts EIP-1271 (receipt is the canonical 56-byte UID); an
unsigned order posts pre-sign and returns
requires-signingcarryingthe
setPreSignaturecall. An already-held rejection succeeds with theclient-derived UID;
errorTyperejections project ontovenue-errorthrough the classification table so the retry hint survives the
collapse.
Chain-edge order assembly (
gpv2_to_order_data,order_data_to_body,order_uid_hex,build_order_creation) moves into a newassemblyslice the adapter owns;
shepherd-sdkre-exports it for the legacykeeper
run(), otherwise unchanged.videre-sdkgainsTimedFetch,per-request timeout middleware every adapter request rides. Codec
vectors and header goldens ship under
tests/vectorswith aconformance suite. The built component bundles into the shepherd
distribution: engine adapters stanza, Docker/CI wasm build, justfile
target.
Closes #324.
Why
Routes outbound orderbook calls through typed transport wrappers
carrying a per-request timeout, resolving #41 as adapter transport
middleware rather than patching
cow_orderbook.rs. The adapter is thevidere:adapter/venue-adaptercomponent the CoW-on-videre split plancalls for; the legacy keeper path stays on
CowApiHostbut now sharesthe assembly slice. Pool-router wiring is a later port.
Testing
cargo nextest run -p cow-venue -p videre-sdk -p shepherd-sdkcargo test --doccargo build --release --target wasm32-wasip2 -p cow-venue --features adaptercargo fmt --all --check,cargo clippy --workspace --all-targets --all-features -- -D warningsAI Assistance
Implemented with Claude Code.