Skip to content

[LOW] release_milestone_multi_asset emits undocumented ms_skip topic; should use documented milestone_release_skipped #49

Description

@Alqku

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

  • Skip events emitted through the documented topic ("campaign", "milestone_release_skipped")
  • docs/events.md lists the new event with topics, payload fields, and the reason enum
  • Off-host indexer can decode the same payload across multi-asset and single-asset release paths
  • Negative-path test exercises the skip for native XLM (Some(asset) with issuer == None) and asserts the event fires
  • Top-level summary event (milestone_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 events

Affected Files

  • campaign/src/multi_asset_release.rs
  • docs/events.md
  • campaign/src/test/release_milestone_tests.rs

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official CampaigndocumentationImprovements or additions to documentationrefactoringCode restructuring without behavioral change

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions