RustFund

First Flight #36
Beginner FriendlyRust
100 EXP
View results
Submission Details
Severity: medium
Valid

Direct SOL Transfers & Multiple Withdrawals

Summary:


This report highlights two vulnerabilities in the contract:

  1. Direct SOL Transfers Not Tracked – The contract tracks contributions using amount_raised, but users can send SOL directly to the fund’s address, bypassing the tracking mechanism. This creates accounting discrepancies and allows withdrawals to exceed intended limits.

  2. Multiple Withdrawals Possible – The withdraw The function does not update the campaign’s state after a successful withdrawal, allowing creators to drain funds multiple times.

These issues lead to uncontrolled fund withdrawals, incorrect refunds, and financial inconsistencies, violating the protocol’s guarantees of security and transparency.

Vulnerability Details

1. Direct SOL Transfers Not Tracked

Issue

  • amount_raised only tracks contributions made via the contribute function.

  • Users can send SOL directly to the fund account, inflating its actual balance (lamports()) without updating amount_raised.

  • This leads to:

    • Withdrawals exceeding tracked contributions.

    • Refund shortfalls if lamports() is lower than expected.

Code Snippet

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
// Transfers `amount` SOL to the creator
// ❌ Does not check actual SOL balance (`lamports()`)
}

Example Attack

  1. Campaign raises 100 SOL via contribute, setting amount_raised = 100.

  2. A donor sends 20 SOL directly to the campaign account (now lamports() = 120).

  3. The creator calls withdraw(), receiving 100 SOL, leaving 20 SOL unaccounted for.

  4. The extra 20 SOL remains accessible but untracked, violating transparency guarantees.

2. Multiple Withdrawals Possible

Issue

  • The withdraw the function does not reset amount_raised after a withdrawal.

  • No mechanism (e.g., a closed flag) exists to prevent repeated withdrawals.

  • The creator can call withdraw multiple times, draining the contract.

Code Snippet

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
// Transfers `amount` SOL to the creator
// ❌ Does not reset `amount_raised` or mark fund as closed
}

Example Attack

  1. The campaign successfully raises 100 SOL (amount_raised = 100).

  2. The creator calls withdraw(), receiving 100 SOL.

  3. Since amount_raised is not reset, the creator calls withdraw() again.

  4. Result: The creator withdraws another 100 SOL, exceeding the actual campaign balance.

Recommendations & Fixes

1. Use lamports() for Withdrawals

Ensure that withdrawals do not exceed the actual fund balance.

Updated Code:

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let fund_balance = ctx.accounts.fund.to_account_info().lamports();
let withdrawable = fund_balance.min(ctx.accounts.fund.amount_raised);
// Transfer `withdrawable` SOL to the creator
// ...
}

2. Mark Campaign as Closed After Withdrawal

Introduce a closed flag to prevent multiple withdrawals.

Updated Struct:

#[account]
pub struct Fund {
pub closed: bool, // ✅ New flag to track withdrawal state
}

Updated Function:

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let fund = &mut ctx.accounts.fund;
require!(!fund.closed, ErrorCode::AlreadyClosed); // Prevent multiple withdrawals
let amount = fund.amount_raised;
fund.amount_raised = 0; // ✅ Reset amount_raised after withdrawal
fund.closed = true; // ✅ Mark fund as closed
// Proceed with transfer...
}

3. Block Direct Transfers to Fund Accounts

Prevent direct SOL transfers by using Program-Derived Addresses (PDAs).

Updated Account Definition:

#[account(
init,
payer = creator,
seeds = [b"fund", name.as_bytes(), creator.key().as_ref()],
bump,
)]
pub fund: Account<'info, Fund>,

4. Implement Explicit Error Codes

Define an error for multiple withdrawals.

#[error_code]
pub enum ErrorCode {
#[msg("Funds have already been withdrawn from this campaign")]
AlreadyClosed,
}

Conclusion

These vulnerabilities compromise the protocol’s financial integrity and trust model. By tracking all SOL movements and implementing proper withdrawal restrictions, we can restore alignment with the protocol’s security guarantees.

Updates

Appeal created

bube Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`amount_raised` is not reset to 0 in `withdraw` function

Support

FAQs

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