Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions modules/ethflow-watcher/src/strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,23 @@ mod tests {
)
}

// ---- manifest topic-0 assertion ----

/// The `event_signature` in `module.toml` must match the
/// keccak256 of the canonical `OrderPlacement` event. A typo
/// or ABI change would silently miss all events on-chain.
#[test]
fn manifest_topic0_matches_order_placement_signature_hash() {
let manifest = include_str!("../module.toml");
let expected = format!("0x{:x}", OrderPlacement::SIGNATURE_HASH);
assert!(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manifest.contains(&expected) searches the whole file as a raw string — today it only matches because the hash appears once, but a future TOML addition (comment, second subscription, etc.) could produce a false positive. Anchoring to the field name makes the intent explicit:

Suggested change
assert!(
assert!(
manifest.contains(&format!("event_signature = \"{expected}\"")),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: manifest.contains(&expected) passes if the hash string appears anywhere in the manifest (comment, another field). In practice a 32-byte hex won't show up elsewhere, but parsing the TOML and comparing event_signature directly would make the test self-documenting and immune to that edge case.

manifest.contains(&expected),
"module.toml event_signature must equal OrderPlacement::SIGNATURE_HASH\n\
expected: {expected}\n\
manifest:\n{manifest}"
);
}

// ---- decode (invariants preserved) ----

#[test]
Expand Down Expand Up @@ -519,6 +536,72 @@ mod tests {
assert!(host.logging.contains("ethflow uid build skipped"));
}

/// 429 from the orderbook → Warn log + no marker. Rate limiting
/// should not mark the order as observed, so a later event
/// re-delivery can retry the check.
#[test]
fn placement_log_warns_on_orderbook_429_rate_limit() {
let host = MockHost::new();
let event = sample_event();
let (topics, data) = encode_log(&event);
let view = placement_log_view(ETH_FLOW_PRODUCTION.as_slice(), &topics, &data);

host.cow_api.respond_to_request(Err(SdkHostError {
domain: "cow-api".into(),
kind: HostErrorKind::RateLimited,
code: 429,
message: "Too Many Requests".into(),
data: None,
}));

on_logs(&host, &[view]).unwrap();

assert!(
host.store.snapshot().is_empty(),
"429 must not write any marker"
);
let warn_lines: Vec<_> = host
.logging
.lines()
.into_iter()
.filter(|l| l.message.contains("indexer check failed"))
.collect();
assert_eq!(warn_lines.len(), 1);
assert_eq!(warn_lines[0].level, LogLevel::Warn);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SdkHostError's Display may render the message field ("Too Many Requests") without the numeric code, in which case this assertion fails even when the behaviour is correct. Use "Too Many Requests" instead, or drop the string check and rely on the level + empty-store assertions which already capture the important invariant.

Suggested change
assert_eq!(warn_lines[0].level, LogLevel::Warn);
assert!(warn_lines[0].message.contains("Too Many Requests"));

assert!(warn_lines[0].message.contains("429"));
}

/// 200 with a malformed (non-JSON) body → the observe path still
/// marks the order as observed, because the strategy only checks
/// the HTTP status, not the body structure.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment describes what the strategy does, not why. The reason the body is irrelevant is that HTTP 200 is the orderbook's contract guarantee that the order is indexed — the response body is an implementation detail, not part of that contract.

Suggested change
/// the HTTP status, not the body structure.
/// marks the order as observed. HTTP 200 is the orderbook's contract
/// guarantee that the order is indexed; the response body is not
/// part of that contract.

#[test]
fn placement_log_marks_observed_on_malformed_json_200() {
let host = MockHost::new();
let event = sample_event();
let (topics, data) = encode_log(&event);
let view = placement_log_view(ETH_FLOW_PRODUCTION.as_slice(), &topics, &data);
let placement =
decode_order_placement(ETH_FLOW_PRODUCTION.as_slice(), &topics, &data).unwrap();
let uid = computed_uid(&placement);

// The strategy never parses the response body on 200 - it
// only cares that the orderbook returned OK.
host.cow_api.respond_to_request_for(
"GET",
format!("/api/v1/orders/{uid}"),
Ok("this is not json at all".to_string()),
);

on_logs(&host, &[view]).unwrap();

assert!(
host.store
.snapshot()
.contains_key(&format!("observed:{uid}")),
"200 with any body still means the order was indexed"
);
}

/// Strategy must never call `submit_order` - the trait still
/// exposes it for other modules (twap-monitor legitimately
/// submits), but ethflow-watcher's observe design never does.
Expand Down
17 changes: 17 additions & 0 deletions modules/twap-monitor/src/strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,23 @@ mod tests {
}
}

// ---- manifest topic-0 assertion ----

/// The `event_signature` in `module.toml` must match the
/// keccak256 of the canonical `ConditionalOrderCreated` event.
/// A typo or ABI change would silently miss all events on-chain.
#[test]
fn manifest_topic0_matches_conditional_order_created_signature_hash() {
let manifest = include_str!("../module.toml");
let expected = format!("0x{:x}", ConditionalOrderCreated::SIGNATURE_HASH);
assert!(
manifest.contains(&expected),
"module.toml event_signature must equal ConditionalOrderCreated::SIGNATURE_HASH\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same manifest.contains raw-string concern as in ethflow-watcher — anchor to the TOML field to make the assertion precise:

Suggested change
"module.toml event_signature must equal ConditionalOrderCreated::SIGNATURE_HASH\n\
assert!(
manifest.contains(&format!("event_signature = \"{expected}\"")),

expected: {expected}\n\
manifest:\n{manifest}"
);
}

// ---- existing pure tests ----

#[test]
Expand Down