Summary
There is no flag for setting deadline allowing contributor to request for refund at any time and fund creator to withdraw the fund at any time.
pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.dealine_set {
return Err(ErrorCode::DeadlineAlreadySet.into());
}
fund.deadline = deadline;
Ok(())
}
Vulnerability Details
The function does not set a flag (deadline_set = true
) after setting the deadline. This will cause override of the deadline multiple times. There are also no explicit checks that if deadline set in withdraw and refund functions as well which allow fund creator to withdraw fund at any time and contributor to refund their amount at any time. For example if deadline is not set in refund function then it will see deadline == 0 means that the condition effectively skips the check, allowing refunds immediately and in withdraw function there is no explicit check that if deadline passed or not so here fund creator can withdraw fund anytime causing issue to contributors.
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
**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.creator.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.creator.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}
}
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)?;
ctx.accounts.contribution.amount = 0;
Ok(())
}
Impact
If the deadline is not set then this will create impact on withdraw function and refund function as there is no deadline set for withdrawing the funds by fund creator and contributors can ask for refund any time.
Tools Used
Manual Review
Recommendations
We have added deadline set field in fund struct and added flag in the set_deadline function. We have also added checks in the withdraw function and refund function so that until deadline is not set then fund owner and contributers cannot refund and withdraw their respective funds.
pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let fund = &ctx.accounts.fund;
if !fund.deadline_set {
return Err(ErrorCode::DeadlineNotSet.into());
}
let current_time = Clock::get()?.unix_timestamp as u64;
if current_time < fund.deadline {
return Err(ErrorCode::DeadlineNotReached.into());
}
let contribution = &mut ctx.accounts.contribution;
let refund_amount = contribution.amount;
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(refund_amount)
.ok_or(ProgramError::InsufficientFunds)?;
**ctx.accounts.contributor.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.contributor.to_account_info().lamports()
.checked_add(refund_amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let fund = &ctx.accounts.fund;
if !fund.deadline_set {
return Err(ErrorCode::DeadlineNotSet.into());
}
let current_time = Clock::get()?.unix_timestamp as u64;
if current_time < fund.deadline {
return Err(ErrorCode::DeadlineNotReached.into());
}
let amount = fund.amount_raised;
**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.creator.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.creator.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}
pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.deadline_set {
return Err(ErrorCode::DeadlineAlreadySet.into());
}
fund.deadline = deadline;
fund.deadline_set = true;
Ok(())
}
#[account]
#[derive(InitSpace)]
pub struct Fund {
#[max_len(200)]
pub name: String,
#[max_len(5000)]
pub description: String,
pub goal: u64,
pub deadline: u64,
pub creator: Pubkey,
pub amount_raised: u64,
pub deadline_set: bool,
}