Overview
campaign/src/multi_asset_release.rs emits a non-standard internal topic ("ms_skip", "no_issuer") (line 127) when a milestone release skips an asset without an issuer — e.g. native XLM handled separately. Each leg uses symbol_short!(...) which fits only up to 9 ascii characters — the second symbol "no_issuer" is exactly 9, the first "ms_skip" is 7. Both fall under the budget. The bigger problem is that this internalised step-event is never documented in docs/events.md, so off-chain indexers that grep the contract's documentation see no event with topic "ms_skip" and miss the trace. The choice of a global topic namespace per-internal-step also makes it hard to distinguish from any other future event that wants the same snake_case key.
Evidence
// campaign/src/multi_asset_release.rs (in release_milestone_multi_asset's per-asset loop)
Some(addr) => addr.clone(),
None => {
env.events().publish(
(symbol_short!("ms_skip"), symbol_short!("no_issuer")),
(milestone_index, asset.asset_code.clone()),
);
continue;
}
docs/events.md (the canonical events index per closed #15) lists every other milestone / campaign event but has no entry for an ms_skip topic.
Impact
- Off-chain indexers reading
docs/events.md cannot decode skips, treating them as silent drops in the campaign progress trace.
- A future event accidentally using
("ms_skip", …) from a different module would silently collide with this internal one.
- Operators relying on
get_milestone_view cannot tell from an event log why release didn't transfer anything for one of the accepted assets.
Recommended Approach
Move skip events onto a unified, documented topic — and document them.
- Use
("campaign", "milestone_release_skipped") (consistent with the other lifecycle event topics in docs/events.md).
- Include the same payload:
(milestone_index, asset_code, reason). Encode reason as a small enum or a plain Symbol ("no_issuer", "dust_below_minimum", "zero_release_amount", "already_released") so downstream tooling can branch.
- Add a section to
docs/events.md covering the new event.
- Optionally also emit a single
milestone_release_completed summary event after the loop so dashboards know the milestone is done.
Concrete patch sketch:
env.events().publish(
("campaign", "milestone_release_skipped"),
(milestone_index, asset.asset_code.clone(), symbol_short!("no_issuer")),
);
Acceptance Criteria
Affected Files
campaign/src/multi_asset_release.rs
docs/events.md
campaign/src/test/release_milestone_tests.rs
Overview
campaign/src/multi_asset_release.rsemits a non-standard internal topic("ms_skip", "no_issuer")(line 127) when a milestone release skips an asset without an issuer — e.g. native XLM handled separately. Each leg usessymbol_short!(...)which fits only up to 9 ascii characters — the second symbol"no_issuer"is exactly 9, the first"ms_skip"is 7. Both fall under the budget. The bigger problem is that this internalised step-event is never documented indocs/events.md, so off-chain indexers that grep the contract's documentation see no event with topic "ms_skip" and miss the trace. The choice of a global topic namespace per-internal-step also makes it hard to distinguish from any other future event that wants the same snake_case key.Evidence
docs/events.md(the canonical events index per closed #15) lists every other milestone / campaign event but has no entry for anms_skiptopic.Impact
docs/events.mdcannot decode skips, treating them as silent drops in the campaign progress trace.("ms_skip", …)from a different module would silently collide with this internal one.get_milestone_viewcannot tell from an event log why release didn't transfer anything for one of the accepted assets.Recommended Approach
Move skip events onto a unified, documented topic — and document them.
("campaign", "milestone_release_skipped")(consistent with the other lifecycle event topics indocs/events.md).(milestone_index, asset_code, reason). Encodereasonas a small enum or a plainSymbol("no_issuer", "dust_below_minimum", "zero_release_amount", "already_released") so downstream tooling can branch.docs/events.mdcovering the new event.milestone_release_completedsummary event after the loop so dashboards know the milestone is done.Concrete patch sketch:
Acceptance Criteria
("campaign", "milestone_release_skipped")docs/events.mdlists the new event with topics, payload fields, and thereasonenumSome(asset)withissuer == None) and asserts the event firesmilestone_release_completed) fires after the per-asset loop with(milestone_index, total_released, asset_count, timestamp)so indexers can detect the end of release without counting per-asset eventsAffected Files
campaign/src/multi_asset_release.rsdocs/events.mdcampaign/src/test/release_milestone_tests.rs