Summary
Every call to transition appends a StateTransition record under DataKey::Transition(campaign_id, count) and increments TransitionCount(campaign_id). There is no cap. A campaign that is repeatedly paused and resumed (e.g., by a fraud detection loop) accumulates unlimited transition records, each consuming persistent storage rent indefinitely. The pause_count field is also incremented without limit.
Location
contracts/campaign-lifecycle/src/lib.rs, transition
let count: u32 = env.storage().persistent().get(&DataKey::TransitionCount(campaign_id)).unwrap_or(0);
env.storage().persistent().set(&DataKey::Transition(campaign_id, count), &transition);
env.storage().persistent().set(&DataKey::TransitionCount(campaign_id), &(count + 1));
// ❌ No cap — count grows forever
Fix
Cap the ring-buffer approach (similar to campaign-analytics):
const MAX_TRANSITIONS: u32 = 100;
let index = count % MAX_TRANSITIONS;
env.storage().persistent().set(&DataKey::Transition(campaign_id, index), &transition);
Or only store the N most recent transitions.
Summary
Every call to
transitionappends aStateTransitionrecord underDataKey::Transition(campaign_id, count)and incrementsTransitionCount(campaign_id). There is no cap. A campaign that is repeatedly paused and resumed (e.g., by a fraud detection loop) accumulates unlimited transition records, each consuming persistent storage rent indefinitely. Thepause_countfield is also incremented without limit.Location
contracts/campaign-lifecycle/src/lib.rs,transitionFix
Cap the ring-buffer approach (similar to
campaign-analytics):Or only store the N most recent transitions.