Summary
contribution.amount is meant to keep track of contributor's
contributions but never gets updated and stays zero.
Vulnerability Details
In the function pub fn contribution (line 25 - 52)
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());
}
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0;
}
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(())
}
the value for contribution.amount only gets initialized(line 37) but never updated.
Impact
This vulnerability affects contributor when they want a refund. Since amount is zero the contributor will never be able to claim a refund.
Tools Used
Manual code review.
Recommendations
Add this to update contribution.amount after line 48
pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
contribution.amount = contribution.amount.checked_add(amount).ok_or(ErrorCode::CalculationOverflow)?;
}
POC
Add this to ./tests/rustfund.ts
it("Test contribution dot amount", async () => {
[contributionPDA, contributionBump] = await PublicKey.findProgramAddress(
[fundPDA.toBuffer(), otherUser.publicKey.toBuffer()],
program.programId
);
const airdropSig = await provider.connection.requestAirdrop(
otherUser.publicKey,
2 * anchor.web3.LAMPORTS_PER_SOL
);
await provider.connection.confirmTransaction(airdropSig);
await program.methods
.contribute(contribution)
.accounts({
fund: fundPDA,
contributor: otherUser.publicKey,
contribution: contributionPDA,
system_Program: anchor.web3.SystemProgram.programId,
})
.signers([otherUser])
.rpc();
await program.methods
.contribute(contribution)
.accounts({
fund: fundPDA,
contributor: otherUser.publicKey,
contribution: contributionPDA,
system_Program: anchor.web3.SystemProgram.programId,
})
.signers([otherUser])
.rpc();
const fund = await program.account.fund.fetch(fundPDA);
const contributionAccount = await program.account.contribution.fetch(contributionPDA);
console.log("fundBalanceAfter", await provider.connection.getBalance(fundPDA));
console.log("contributorAmount", contributionAccount.amount.toNumber());
});
Will output
fundBalanceAfter 1037590960
contributorAmount 0
✔ Test contribution dot amount (1259ms)
The test demonstrate that contribution.amount never gets updated.