RustFund

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

Refund Won’t Work Because `contribution.amount` Is Not Updated During `contribute` Function

[H-01] Refund Won’t Work Because contribution.amount Is Not Updated During contribute Function

Summary

The contribute function does not update contribution.amount, which results in the refund function always using amount = 0. This means that contributors will never be able to successfully withdraw their funds when requesting a refund.

Vulnerability Details

Below is the implementation of the contribute function:

pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
let contribution = &mut ctx.accounts.contribution;
if fund.deadline != 0 && fund.deadline < Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineReached.into());
}
// Initialize or update contribution record
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0;
}
// Transfer SOL from contributor to fund account
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.contributor.to_account_info(),
to: fund.to_account_info(),
},
);
system_program::transfer(cpi_context, amount)?;
fund.amount_raised += amount;
Ok(())
}

Issue

  • The function never updates contribution.amount when a user contributes.

  • Because contribution.amount remains 0, any future refund attempts will also try to refund 0 lamports.

  • This makes the refund function ineffective, as no actual funds will be transferred back to the contributor.

Impact

Refunds will always fail silently because the amount to be refunded is never set correctly. Even though contributors successfully send SOL to the fund, they will never be able to withdraw it back, leading to locked or lost funds.

Proof of Concept

Modify the existing test case to track the contributor and fund account balances before and after a refund attempt. The results will show no changes in balances, proving that the refund mechanism does not work.

it("Refunds contribution", async () => {
console.log("fundBalanceBefore", await provider.connection.getBalance(fundPDA));
await new Promise(resolve => setTimeout(resolve, 15000));
const contributorBalanceBefore = await provider.connection.getBalance(otherUser.publicKey);
console.log("contributorBalanceBefore", contributorBalanceBefore);
await program.methods
.refund()
.accounts({
fund: fundPDA,
contribution: contributionPDA,
contributor: otherUser.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
}).signers([otherUser])
.rpc();
const contributorBalanceAfter = await provider.connection.getBalance(otherUser.publicKey);
const contributionAccount = await program.account.contribution.fetch(contributionPDA);
console.log("fundBalanceAfter", await provider.connection.getBalance(fundPDA));
console.log("contributorBalanceAfter", contributorBalanceAfter);
});

Expected output:

  • fundBalanceBefore and fundBalanceAfter remain unchanged.

  • contributorBalanceBefore and contributorBalanceAfter remain the same.

  • contribution.amount remains 0.

Tools Used

Manual Review

Recommendations

Update contribution.amount during the contribution process to ensure it accurately tracks the amount deposited.

pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
let contribution = &mut ctx.accounts.contribution;
if fund.deadline != 0 && fund.deadline < Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineReached.into());
}
// Initialize or update contribution record
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0;
}
// Transfer SOL from contributor to fund account
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.contributor.to_account_info(),
to: fund.to_account_info(),
},
);
system_program::transfer(cpi_context, amount)?;
fund.amount_raised += amount;
+ contribution.amount += amount; // Fix: Track the amount contributed
Ok(())
}

This change ensures that contribution.amount is updated correctly, allowing refunds to work as intended.

Updates

Appeal created

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

Contribution amount is not updated

Support

FAQs

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