RustFund

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

`contribute`: incorrect accounting on `contribution.amount`

Summary

In the contribute function, when initializing the contribution account, its amount is incorrectly set to 0. Additionally, there is no handling for the case where a user calls contribute again to increase the funding amount for the same fund account.

// https://github.com/CodeHawks-Contests/2025-03-rustfund/blob/b5dd7b0ec01471667ae3a02520701aae405ac857/programs/rustfund/src/lib.rs#L33-L38
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
// incorrect
contribution.amount = 0;
}
// without handling case of update
// else {}

Impact

According to that the amount is set to 0, the contributor is unable to retrieve their funds by calling the refund function.

https://github.com/CodeHawks-Contests/2025-03-rustfund/blob/b5dd7b0ec01471667ae3a02520701aae405ac857/programs/rustfund/src/lib.rs#L66-L68
pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
// is set to zero
let amount = ctx.accounts.contribution.amount;
// transfer lamports with zero value
}

PoC

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Rustfund } from "../target/types/rustfund";
import { PublicKey } from '@solana/web3.js';
import { expect } from 'chai';
describe("rust fund", () => {
// provider
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
// program
const program = anchor.workspace.Rustfund as Program<Rustfund>;
// actors
const creator = provider.wallet;
// campaign information
const fundName = "firstflight Fund";
const description = "this program is for firstflight";
const goalAmount = new anchor.BN(1000000000); // 1 SOL
const contributeAmount = new anchor.BN(500000000); //0.5 SOL
const deadline = new anchor.BN(Math.floor(Date.now() / 1000) + 10); // 10 sec from now (testing)
let fundPDA: PublicKey;
let fundBump: number;
let contributionPDA: PublicKey;
let contributionBump: number;
before(async () => {
// Generate PDA for fund
[fundPDA, fundBump] = await PublicKey.findProgramAddress(
[Buffer.from(fundName), creator.publicKey.toBuffer()],
program.programId
);
// Generate PDA for contribution
[contributionPDA, contributionBump] = await PublicKey.findProgramAddress(
[fundPDA.toBuffer(), provider.wallet.publicKey.toBuffer()],
program.programId
);
});
it("incorrect accounting on `contribution.amount`", async () => {
// create campaign
await program.methods
.fundCreate(fundName, description, goalAmount)
.accounts({
fund: fundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// set deadline
await program.methods
.setDeadline(deadline)
.accounts({
fund: fundPDA,
creator: creator.publicKey,
})
.rpc();
// contributor first time contribution
await program.methods
.contribute(contributeAmount)
.accounts({
fund: fundPDA,
contributor: provider.wallet.publicKey,
contribution: contributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// fetch account state
let contributionAccount = await program.account.contribution.fetch(contributionPDA);
console.log("contribution amount first time: ", contributionAccount.amount);
// contributor second time contribution
await program.methods
.contribute(contributeAmount)
.accounts({
fund: fundPDA,
contributor: provider.wallet.publicKey,
contribution: contributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// fetch account state
contributionAccount = await program.account.contribution.fetch(contributionPDA);
console.log("contribution amount second time: ", contributionAccount.amount);
});
});

log:

rust fund
contribution amount first time: <BN: 0>
contribution amount second time: <BN: 0>
✔ incorrect accounting on `contribution.amount` (1582ms)
1 passing (2s)

Tools Used

Manual.

Recommendations

Correct accounting.

// L34
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = amount;
} else {
contribution.amount.checked_add(amount).ok_or(ErrorCode::CalculationOverflow)?;
}
Updates

Appeal created

bube Lead Judge 3 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.