Skip to content

fix(events): prevent topic collisions, add uniqueness regression test… - #1672

Merged
Jagadeeshftw merged 1 commit into
Fluxora-Org:mainfrom
Isihaq123:fix/1585-event-topic-collision-prevention
Aug 31, 2026
Merged

fix(events): prevent topic collisions, add uniqueness regression test…#1672
Jagadeeshftw merged 1 commit into
Fluxora-Org:mainfrom
Isihaq123:fix/1585-event-topic-collision-prevention

Conversation

@Isihaq123

Copy link
Copy Markdown

fix(events): prevent topic collisions, add uniqueness regression tests (#1585)

Problem

events.rs publishes ten lifecycle events through #[contractevent] structs. The SDK derives topic[0] — the primary
routing key for indexers — from each struct's name in snake_case. There was no machine-verifiable assertion that all
ten names are distinct, and the two delegate events (DelegateGranted, DelegateRevoked) had no schema coverage at all. A
silent rename or a new event with a colliding name would be invisible until an indexer started misrouting events in
production.

Design decision

topic[0] (the SDK-generated snake_case struct name) is the namespace for each event type. The versioning rule:

  • Additive / compatible: append a new field at the end of the struct. Indexers tolerating unknown trailing fields
    continue to work.
  • Breaking change: rename, remove, reorder, or re-topic an existing field, or rename the struct. Introduce a new struct
    with a V2 suffix and keep the old one for the migration window.

This rule was already stated informally in the module docs; it is now codified in a dedicated section and backed by a
test that will fail the moment the invariant is violated.

Changes

contracts/stream/src/events.rs

contracts/stream/src/test/events.rs

  • test_all_event_topic_names_are_unique (new) — emits all ten event types across a single test environment, accumulates
    topic[0] symbols after each invocation (since Events::all() only retains the most recent call), deduplicates, and
    asserts the result equals the exact expected inventory. Any struct rename or two events sharing a name fails this test
    immediately. The expected inventory is also checked for completeness, so adding a new event without registering it here
    is also a failure.
  • test_delegate_events (new) — schema snapshot for DelegateGranted and DelegateRevoked, which were previously
    uncovered. Asserts topic[0] name, topic arity (topic[0] + stream_id + grantor + delegate = 4 for both), payload field
    sets ([ops, expires_at] vs empty), and assert_ne! between the two — the direct collision check for the most
    structurally-similar pair in the contract.
  • test_golden_events and test_no_event_on_failure — unchanged.

Before / after

┌───────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────┐
│ Before │ After │
├───────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────┤
│ No assertion that event topic[0] names are unique │ test_all_event_topic_names_are_unique fails on any collision │
│ │ or inventory drift │
├───────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────┤
│ DelegateGranted and DelegateRevoked untested │ Both covered: arity, field names, and direct assert_ne! │
│ │ collision check │
├───────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────┤
│ Versioning rule documented in prose only │ Also machine-enforced: renaming a struct or swapping topics │
│ │ breaks the test │
└───────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────┘

Verification

cargo test -p fluxora-stream events -- --nocapture

All event topic[0] values (emission order): ["stream_created", "paused", "resumed",
"topped_up", "recipient_transferred", "withdrawn", "cancelled", "ttl_extended",
"delegate_granted", "delegate_revoked"]
✓ All 10 event types have unique topic[0] symbols: ["cancelled", "delegate_granted",
"delegate_revoked", "paused", "recipient_transferred", "resumed", "stream_created",
"topped_up", "ttl_extended", "withdrawn"]
test test::events::test_all_event_topic_names_are_unique ... ok
✓ DelegateGranted topic[0]=Symbol(delegate_granted), arity=4, payload fields: [ops, expires_at]
✓ DelegateRevoked topic[0]=Symbol(delegate_revoked), arity=4, no data payload
test test::events::test_delegate_events ... ok
test test::events::test_golden_events ... ok
test test::events::test_no_event_on_failure ... ok

test result: ok. 44 passed; 0 failed; 0 ignored

No collisions found in the existing event surface. All existing tests pass unchanged.

closes #1585

Fluxora-Org#1585)

- Add topic namespace and versioning rule documentation to events.rs module
  docs: topic[0] is the snake_case struct name (SDK-generated), serves as
  the unique namespace for each event type. Versioning rule: append fields
  for additive changes; introduce a V2 struct for breaking changes.

- Add test_all_event_topic_names_are_unique: emits all 10 event types,
  accumulates topic[0] symbols across invocations, deduplicates, and asserts
  they match the exact expected inventory. Any struct rename or collision
  fails this test immediately.

- Add test_delegate_events: schema snapshot for DelegateGranted and
  DelegateRevoked (previously untested). Asserts topic[0] names, arity
  (4 each: topic[0] + stream_id + grantor + delegate), payload field sets,
  and assert_ne! between the two most structurally-similar events.

- Retain test_golden_events and test_no_event_on_failure unchanged.

No collisions found: all 10 event types have distinct topic[0] symbols.
cargo test -p fluxora-stream events: 44 passed, 0 failed.
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@Isihaq123 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Jagadeeshftw
Jagadeeshftw merged commit 3876dc3 into Fluxora-Org:main Aug 31, 2026
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.

Prevent stream event topic collisions across lifecycle operations

2 participants