Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Severity: low
Valid

Rent is permanently locked — `Fund` and `Contribution` accounts are never closed, so the rent-exempt SOL can never be reclaimed

Description

fund_create creates the Fund PDA with init (rent paid by the creator), and contribute creates each Contribution PDA with init_if_needed (rent paid by the contributor). No instruction ever closes either account. withdraw and refund move only the contributed lamports via raw lamport math; they never use Anchor's close = to return the rent-exempt deposit and reclaim the account.

// FundCreate: creator pays rent for the Fund PDA
#[account(init, payer = creator, space = 8 + Fund::INIT_SPACE, seeds = [...], bump)]
pub fund: Account<'info, Fund>,
// FundContribute: contributor pays rent for the Contribution PDA (init_if_needed)
#[account(init_if_needed, payer = contributor, space = 8 + Contribution::INIT_SPACE, seeds = [...], bump)]
pub contribution: Account<'info, Contribution>,
// @> No instruction has `close = <recipient>` on fund or contribution; rent is never returned.

Risk

Likelihood: Low

  • Applies to every campaign and every contributor account at end-of-life. After a campaign completes (withdraw) or a contributor is refunded, the account's rent-exempt reserve just sits there.

Impact: Low

  • The rent-exempt SOL paid at account creation (by the creator for the Fund, by each contributor for their Contribution) is permanently locked and unrecoverable. It is a small per-account amount, but it is real value stranded on-chain for the lifetime of the program, with no code path to retrieve it.

Proof of Concept

fund_create(ctx, "camp", "desc", goal)?; // creator pays ~rent for Fund PDA
contribute(ctx_a, 1_000_000_000)?; // contributor A pays ~rent for their Contribution PDA
refund(ctx_a)?; // returns A's 1 SOL, resets amount, but Contribution stays open
withdraw(ctx_creator)?; // moves contributed SOL out, but Fund PDA stays open
// @> Both PDAs remain allocated; their rent-exempt lamports are never returned to anyone.

Recommended Mitigation

Close accounts when they are no longer needed, returning rent to the payer. For example, close a Contribution on refund and the Fund on final withdrawal:

#[account(mut, close = contributor, seeds = [...], bump, has_one = contributor, has_one = fund)]
pub contribution: Account<'info, Contribution>,
// and on withdraw:
#[account(mut, close = creator, seeds = [...], bump, has_one = creator)]
pub fund: Account<'info, Fund>,
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 24 minutes ago
Submission Judgement Published
Validated
Assigned finding tags:

[L-04] Unclaimed rent from fund and contribution accounts leads to permanent SOL lockup

## Description neither the `withdraw()` nor `refund()` functions properly close their respective accounts after the funds have been transferred. This results in the rent amount (the SOL required to keep the account allocated on the Solana blockchain) remaining locked in these accounts indefinitely. ## Vulnerability Details In Solana, accounts must maintain a minimum balance to remain "rent-exempt", ensuring they aren't purged from the blockchain. When accounts are no longer needed, best practice is to close them and return this rent to the appropriate party (typically the account creator). The RustFund protocol currently lacks this account cleanup mechanism. - The withdrawal process in `withdraw()` transfers the raised funds from the fund account to the creator - Similarly, the refund process in `refund()` transfers the contribution amount back to the contributor and resets the contribution amount In both functions, the account data is updated, but the accounts themselves remain open, with their rent amount locked. After all funds have been withdrawn or refunded, these accounts serve no further purpose but continue to consume blockchain resources and lock up SOL. ### Proof of Concept 1. Alice creates a fund with a goal of 100 SOL, which requires 0.1 SOL as rent for the fund account. 2. The fund successfully raises 100 SOL from various contributors, each of whom also paid rent for their contribution accounts (approximately 0.05 SOL each). 3. Alice calls `withdraw()` to claim the 100 SOL raised funds, but the fund account itself is not closed. 4. The 0.1 SOL rent for the fund account remains locked in the account indefinitely. 5. Similarly, when contributors request refunds through the `refund()` function, their contribution accounts are updated but not closed. 6. The rent for all contribution accounts (e.g., 10 contributors × 0.05 SOL = 0.5 SOL) remains locked indefinitely. 7. Over time, as more funds are created and more contributions are made, the amount of permanently locked SOL grows. ## Impact - Economic Inefficiency, - Blockchain bloat, - Reduced user returns ## Recommendations Implement proper account closure in both the `withdraw()` and `refund()` functions.

Support

FAQs

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

Give us feedback!