Normal behavior: Crowdfunding has exactly two terminal outcomes. If the goal is met by the deadline the campaign succeeds and only the creator withdraws; if the deadline passes with the goal unmet it fails and only contributors refund. The two flows must be mutually exclusive and each gated on its own outcome.
The issue: No function ever records or enforces an outcome. refund checks only that the deadline has passed — it never verifies the goal was not met, so after a successful campaign (goal met at deadline) contributors can still call refund and drain the vault the creator is entitled to. Conversely withdraw (see H02) never verifies success. Neither function flips any state, so after the deadline both paths are open simultaneously and can be executed in any order — refund after success, withdraw after failure, withdraw twice, refund after the vault was withdrawn. The program has no succeeded/failed/withdrawn concept at all.
Root cause in programs/rustfund/src/lib.rs:
Likelihood:
Reason 1 — After any campaign's deadline, both entry points are callable; the outcome (goal met or not) is never consulted by either function.
Reason 2 — Front-running or simple ordering is all that is needed: on a successful campaign a contributor's refund can race the creator's withdrawal, and on a failed one the creator can withdraw before contributors refund.
Impact:
Impact 1 — On a successful campaign, a contributor can refund after the goal was met, taking SOL the creator has earned — the success payout is not protected.
Impact 2 — The two user powers promised in the spec are never made exclusive, so no state of the protocol is ever final and consistent; every terminal outcome is racy.
Anchor/TS against the shipped program (deterministic from the code):
Introduce an explicit terminal state and gate each flow on it:
(Add the referenced CampaignNotFailed variant — or an equivalent guard — 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.