RustFund

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

Contributors will get a refund of 0 since 'contribution.amount' is never incremented

Summary

Contributors should be able to get a refund when the deadline has passed but the goal is not met. However, 'contribution.amount' is never incremented in 'contribute()' despite successful SOL transfers. This leads to the 'refund()' function always refunding the contributors an amount of 0.

Vulnerability Details

The 'Contribution' struct tracks the contributor, the fund he has contributed to, and the amount he has contributed.
However, the amount is never incremented by the amount sent to the contribute() function.
refund() relies on the contribution.amount to determine the refund amount. This amount will always be 0 since it is never incremented anywhere in the code.

pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
@> let amount = ctx.accounts.contribution.amount;//@audit will always be 0 since its value is not incremented in `contribute()`
if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineNotReached.into());
}
/*
...
Rest of function
...
*/
}

POC

The refund test provided by the devs did not correctly test this functionality.
In tests/rustfund.ts, replace the refund test with:

it("Refunds contribution", async () => {
// Log initial states
const fundBalanceBefore = await provider.connection.getBalance(fundPDA);
const contributorBalanceBefore = await provider.connection.getBalance(provider.wallet.publicKey);
console.log("fundBalanceBefore", fundBalanceBefore);
console.log("contributorBalanceBefore", contributorBalanceBefore);
await new Promise(resolve => setTimeout(resolve, 15000));
await program.methods
.refund()
.accounts({
fund: fundPDA,
contribution: contributionPDA,
contributor: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// Fetch and log final states
const fundBalanceAfter = await provider.connection.getBalance(fundPDA);
const contributorBalanceAfter = await provider.connection.getBalance(provider.wallet.publicKey);
const contributionAccount = await program.account.contribution.fetch(contributionPDA);
console.log("fundBalanceAfter", fundBalanceAfter);
console.log("contributionAccount", contributionAccount);
console.log("contributorBalanceAfter", contributorBalanceAfter);
// Calculate differences
const fundBalanceDiff = fundBalanceBefore - fundBalanceAfter;
const contributorBalanceDiff = contributorBalanceAfter - contributorBalanceBefore;
console.log("fundBalanceDiff", fundBalanceDiff);
console.log("contributorBalanceDiff", contributorBalanceDiff);
// Assertions
const expectedRefundAmount = 500000000; // 0.5 SOL from contribute test
expect(contributionAccount.amount.toNumber()).to.equal(0, "Contribution amount should be reset to 0");
// Expect fund balance to decrease by the refund amount (fails if no refund occurs)
expect(fundBalanceDiff).to.equal(expectedRefundAmount, "Fund balance should decrease by the contributed amount");
// Expect contributor balance to increase by the refund amount minus fees (fails if no refund occurs)
const maxFeeTolerance = 10000; // Allow up to 10,000 lamports for fees
expect(contributorBalanceDiff).to.be.closeTo(expectedRefundAmount, maxFeeTolerance, "Contributor balance should increase by the refunded amount minus fees");
});

Which fails:

4 passing (17s)
1 failing
1) rustfund
Refunds contribution:
Fund balance should decrease by the contributed amount
+ expected - actual
-0
+500000000

Impact

The contributor is not refunded the amount he is entitled to.

Tools Used

Manual review, anchor tests.

Recommendations

Increment the amount contributed in the contribution struct.

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)?;
+ contribution.amount += amount;
fund.amount_raised += amount;
Ok(())
}
Updates

Appeal created

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