Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

withdraw and refund move raw lamports without preserving rent-exemption, risking the fund account being purged and remaining funds lost

Lamport withdrawals ignore the rent-exempt minimum, risking account purge and state loss

Description

Both withdraw (lib.rs:93-96) and refund (lib.rs:73-76) subtract lamports straight from the fund account's raw balance with checked_sub, with no floor at the rent-exempt minimum. Draining the Fund PDA below its rent-exemption threshold lets Solana purge the account at the next epoch.

**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount) // @> may drop balance below rent-exempt minimum :93-96 (and :73-76)
.ok_or(ProgramError::InsufficientFunds)?;

Risk

Likelihood:

withdraw pays out exactly amount_raised, which equals the contributed lamports and excludes the rent the creator paid at init — so a normal full withdrawal can leave the account holding only its rent, and rounding/refund interactions can push it below the exempt minimum. Refund paths can similarly draw the balance down.

Impact:

If the fund account falls below rent-exemption, the runtime can reclaim it, destroying the Fund state (goal, amount_raised, deadline, creator) and any lamports still parked there. Outstanding contributors lose the on-chain record needed to refund, and the campaign is irrecoverable. Loss of funds and state.

Proof of Concept

Withdraw the full raised amount and inspect the residual balance versus the rent minimum.

const before = await provider.connection.getBalance(fundPda);
await program.methods.withdraw().accounts({ fund, creator }).rpc();
const after = await provider.connection.getBalance(fundPda);
const minRent = await provider.connection.getMinimumBalanceForRentExemption(FUND_SPACE);
assert(after >= minRent); // can fail -> account becomes purgeable

Recommended Mitigation

Cap the withdrawable amount at balance minus the rent-exempt minimum.

+ let rent_min = Rent::get()?.minimum_balance(ctx.accounts.fund.to_account_info().data_len());
+ let available = ctx.accounts.fund.to_account_info().lamports().saturating_sub(rent_min);
+ require!(amount <= available, ProgramError::InsufficientFunds);
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!