Normal behavior: Per the protocol, the creator may withdraw only after the campaign succeeds — the goal must be met by the deadline ("Creators can withdraw funds once their campaign succeeds"; contributors get refunds when the deadline is reached and the goal is not met). withdraw must therefore enforce: a deadline was set, the deadline has passed, and amount_raised >= goal.
The issue: withdraw performs none of those checks. It reads fund.amount_raised and moves that many lamports from the campaign vault to the creator's wallet unconditionally. A creator can call it immediately after the first contribution — with the deadline far in the future and the goal unmet — and drain the entire vault. After that, contributors have nothing left to refund: the vault is empty, so the refund path can only fail (InsufficientFunds) or, with the H01 accounting bug, silently return 0. There is also no terminal state: withdraw neither marks the campaign withdrawn nor zeroes amount_raised, so the function is callable again and the vault/ledger can further diverge.
Root cause in programs/rustfund/src/lib.rs:
The only constraint on FundWithdraw is that the signer owns the campaign (has_one = creator); nothing gates when the payout may happen.
Likelihood:
Reason 1 — The capability is unconditional: every campaign, from its first contribution onward, lets the creator pull the whole amount_raised. No state or time condition has to be arranged.
Reason 2 — The failure case is the protocol's core promise: a creator whose campaign is still live (deadline not reached, goal not met) — or a failed campaign — can take all funds, and the refund mechanism no longer has any SOL to return.
Impact:
Impact 1 — Total loss of contributors' funds on any campaign whose creator withdraws early: the vault is emptied before the success condition is ever evaluated.
Impact 2 — The refund guarantee is meaningless in practice — refunds only exist while the vault still holds the deposits, and withdraw can always be called first.
Anchor/TS against the shipped program (deterministic from the code):
Gate the payout on the success condition and make the terminal state explicit:
(Add the referenced DeadlineNotSet / GoalNotReached variants to ErrorCode.)
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.