Skip to content

fix(campaigns): guard withdraw_reserve against non-withdrawn campaigns (#443) - #727

Open
mayborn005 wants to merge 1 commit into
Iris-IV:mainfrom
mayborn005:fix/443-withdraw-reserve-funds-withdrawn-guard
Open

fix(campaigns): guard withdraw_reserve against non-withdrawn campaigns (#443)#727
mayborn005 wants to merge 1 commit into
Iris-IV:mainfrom
mayborn005:fix/443-withdraw-reserve-funds-withdrawn-guard

Conversation

@mayborn005

Copy link
Copy Markdown

Summary

withdraw_reserve in src/campaigns/withdraw.rs loaded the Campaign, required the creator's auth, and released the reserve — but never asserted campaign.funds_withdrawn == true.

A CampaignReserve is only ever created inside withdraw_funds, after funds_withdrawn is set to true. So a reserve present on a non-withdrawn campaign represents an invariant violation (e.g. a reserve seeded through a future migration or an admin grant). Without this guard, such a reserve could be drained.

Fix

Add a defense-in-depth check right after the campaign is loaded, mirroring the existing is_verified re-check in withdraw_funds:

if !campaign.funds_withdrawn {
    return Err(Error::ValidationFailed);
}

Tests

Added test_withdraw_reserve_rejects_non_withdrawn_campaign, which seeds a CampaignReserve (release window already open) onto a campaign whose funds_withdrawn is still false and asserts withdraw_reserve returns Error::ValidationFailed.

Note: cargo test --features testutils currently fails to compile on main due to pre-existing, unrelated errors in src/admin.rs and src/lib.rs (DataKey/Symbol not in scope, Campaign.tags field missing). Those are outside the scope of this change — no error references the two files touched here.

Fixes #443

withdraw_reserve loaded the campaign and required the creator's auth but
never asserted campaign.funds_withdrawn == true. A CampaignReserve is
only ever created inside withdraw_funds after funds_withdrawn is set, so
a reserve present on a non-withdrawn campaign is an invariant violation
(e.g. one seeded via migration or an admin grant) and must not be
drainable.

Add a defense-in-depth check that returns Error::ValidationFailed when
funds_withdrawn is false, plus a regression test that seeds a reserve on
a non-withdrawn campaign and asserts the drain is rejected.

Fixes Iris-IV#443
@drips-wave

drips-wave Bot commented Jul 31, 2026

Copy link
Copy Markdown

@mayborn005 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

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.

[Bug] withdraw_reserve has no guard confirming funds_withdrawn==true — defense-in-depth missing

1 participant