Problem
expire_campaign publishes the exact same event as cancel_campaign — there's no way to tell the two outcomes apart on-chain (lib.rs:632-636 vs 584-588):
// cancel_campaign
events::CampaignCancelled { campaign_id, refunded_amount: refund }.publish(&env);
// expire_campaign — identical event, different function
events::CampaignCancelled { campaign_id, refunded_amount: refund }.publish(&env);
contracts/campaign-escrow/src/events.rs defines only CampaignCancelled (lines 78-90) — there's no CampaignExpired.
Concrete failure scenario
An indexer or frontend building a campaign timeline/audit trail (or simply wanting to show "Cancelled by business" vs. "Expired past deadline — no submissions") cannot distinguish the two from the event stream alone. It would have to separately fetch completion_deadline and compare it against the event's ledger timestamp to infer which happened — defeating the purpose of having a distinct, typed event per state transition, which is exactly how every other transition in this contract is modeled.
Expected behaviour
Add a CampaignExpired { campaign_id: CampaignId, refunded_amount: i128 } event and publish it from expire_campaign instead of reusing CampaignCancelled.
Files
contracts/campaign-escrow/src/events.rs — add CampaignExpired
contracts/campaign-escrow/src/lib.rs — publish it from expire_campaign
Acceptance criteria
Problem
expire_campaignpublishes the exact same event ascancel_campaign— there's no way to tell the two outcomes apart on-chain (lib.rs:632-636vs584-588):contracts/campaign-escrow/src/events.rsdefines onlyCampaignCancelled(lines 78-90) — there's noCampaignExpired.Concrete failure scenario
An indexer or frontend building a campaign timeline/audit trail (or simply wanting to show "Cancelled by business" vs. "Expired past deadline — no submissions") cannot distinguish the two from the event stream alone. It would have to separately fetch
completion_deadlineand compare it against the event's ledger timestamp to infer which happened — defeating the purpose of having a distinct, typed event per state transition, which is exactly how every other transition in this contract is modeled.Expected behaviour
Add a
CampaignExpired { campaign_id: CampaignId, refunded_amount: i128 }event and publish it fromexpire_campaigninstead of reusingCampaignCancelled.Files
contracts/campaign-escrow/src/events.rs— addCampaignExpiredcontracts/campaign-escrow/src/lib.rs— publish it fromexpire_campaignAcceptance criteria
CampaignExpiredevent added toevents.rs, mirroringCampaignCancelled's shapeexpire_campaignpublishesCampaignExpiredinstead ofCampaignCancelledcancel_campaign's event is unchangedexpire_campaignemitsCampaignExpired, notCampaignCancelled