Rust Fund

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

set_deadline() accepts a timestamp in the past, enabling deadline manipulation

Root + Impact

Description

set_deadline() lets the creator set the fund's deadline once. It should only accept a future timestamp, since the deadline governs whether contributions are accepted and whether refunds are allowed. The problem is that set_deadline() validates only that the deadline has not been set before (dealine_set), but not that the provided value is in the future. The creator can set a deadline already in the past, which immediately closes contributions and/or opens the refund window in a single step.

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; // @> no check that deadline > current time
Ok(())
}

Risk

Likelihood:

  • Occurs whenever the creator sets a deadline value less than or equal to the current timestamp.

Impact:

  • A past deadline immediately blocks new contributions (contribute checks deadline < now) and opens the refund window prematurely, letting the creator manipulate the fund lifecycle out of the intended order.

Proof of Concept

  1. Creator creates a fund (deadline defaults to 0, dealine_set defaults to false).

  2. Creator calls set_deadline with a timestamp in the PAST (e.g., 1).

  3. The call succeeds — no validation rejects the past value.

  4. Now contribute() rejects new contributions (deadline < now) and refund()'s
    deadline gate is already open, all triggered out of the intended order.

  5. Bonus: because dealine_set is never set to true, set_deadline can be called
    again to change the deadline arbitrarily.

it("set_deadline accepts a past timestamp and can be called repeatedly", async () => {
// Set a deadline in the past (timestamp = 1, i.e. 1970)
await program.methods.setDeadline(new BN(1))
.accounts({ fund, creator: creator.publicKey })
.signers([creator]).rpc();
let fundAcc = await program.account.fund.fetch(fund);
assert.equal(fundAcc.deadline.toNumber(), 1); // past deadline accepted
// BUG: dealine_set is never set true, so we can change it again
await program.methods.setDeadline(new BN(9999999999))
.accounts({ fund, creator: creator.publicKey })
.signers([creator]).rpc();
fundAcc = await program.account.fund.fetch(fund);
assert.equal(fundAcc.deadline.toNumber(), 9999999999); // changed again!
});

Note: demonstrates both the missing "future timestamp" check and the fact that dealine_set is never updated (so the "already set" guard never triggers). Running requires the Anchor test setup; both flaws are evident from the source.

Recommended Mitigation

Require deadline > current timestamp. Also set dealine_set = true (currently the flag is never updated, so the "already set" check never triggers — a secondary bug worth noting).

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());
}
+ require!(
+ deadline > Clock::get()?.unix_timestamp as u64,
+ ErrorCode::DeadlineNotReached
+ );
+ fund.dealine_set = true;
fund.deadline = deadline;
Ok(())
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 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!