Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

`set_deadline()` accepts past timestamps allowing creator to weaponize deadline against contributors

Root + Impact

Description

  • set_deadline is intended to establish a fixed future date after which contributors can request refunds if the goal was not met.

  • The function performs no timestamp validation — any u64 value is accepted, including 1 (decades in the past). Setting deadline = 1 instantly makes the campaign appear expired: new contributions are blocked, refunds become immediately available, and any goal-based withdraw guard is satisfied. This enables a creator front-run attack — collect contributions while deadline is 0, then set deadline = 1 to freeze intake and immediately withdraw.

pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.dealine_set {
return Err(ErrorCode::DeadlineAlreadySet);
}
fund.deadline = deadline; // no require!(deadline > Clock::get()...)
Ok(())
}

Risk

Likelihood:

  • A creator executing a rug-pull uses this to make all campaign state checks appear satisfied simultaneously — the deadline appears to have passed while the goal check (with a fixed withdraw) is also satisfied.

  • The window for exploitation spans from fund creation until set_deadline is first called, which can be the entire active campaign period.

Impact:

  • A creator freezes contribution intake at any time and immediately withdraws while blocking new contributors from participating.

  • Existing contributors cannot get refunds because the goal check (campaign "succeeded") is also satisfied by the manipulated deadline.

Proof of Concept

Place this test in tests/ and run anchor test. The test demonstrates that set_deadline() accepts a timestamp of 1 (decades in the past), instantly making the campaign appear expired and satisfying all deadline-dependent checks.

it("creator can set deadline in the past to immediately satisfy all checks", async () => {
// Contribute some SOL
await program.methods.contribute(new BN(500_000))
.accounts({ fund, contributor: contributor.publicKey, contribution })
.signers([contributor])
.rpc();
// Creator sets deadline to 1 (decades in the past) — no validation
await program.methods.setDeadline(new BN(1))
.accounts({ fund, creator: creator.publicKey })
.signers([creator])
.rpc();
// Deadline is now "passed" — new contributions blocked, withdraw guard satisfied
const fundAccount = await program.account.fund.fetch(fund);
assert.equal(fundAccount.deadline.toNumber(), 1);
});

Recommended Mitigation

Add a require!(deadline > now, ErrorCode::DeadlineInPast) check inside set_deadline() so only future timestamps are accepted as valid deadlines.

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());
}
+ let now: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
+ require!(deadline > now, ErrorCode::DeadlineInPast);
fund.deadline = deadline;
fund.dealine_set = true;
Ok(())
}
pub enum ErrorCode {
#[msg("Deadline already set")]
DeadlineAlreadySet,
#[msg("Deadline reached")]
DeadlineReached,
#[msg("Deadline not reached")]
DeadlineNotReached,
#[msg("Unauthorized access")]
UnauthorizedAccess,
#[msg("Calculation overflow occurred")]
CalculationOverflow,
+ #[msg("Deadline must be in the future")]
+ DeadlineInPast,
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!