The refund function does not verify that the campaign actually failed. A contributor is allowed to call refund and reclaim their contribution even if the funding goal was reached (i.e., the campaign succeeded). The contract misses a success check, enabling contributors to retrieve funds that should remain with the campaign creator after a successful raise.
In a correct design, the refund mechanism should only be active if the campaign failed (goal not met by deadline). RustFund’s refund handler, however, does not check that condition.
RustFund’s implementation omits the fund.amount_raised < fund.goal part (and possibly the time check is incomplete as well). As a result, after the deadline passes, any contributor can call refund() regardless of whether the goal was met. If the campaign was actually successful (goal reached), this is a protocol violation. In practice, suppose the goal was 100 SOL and contributors raised 100+ SOL by the deadline (a success). Normally, no refunds should be allowed; the creator should withdraw the funds. But in RustFund, once the deadline is reached, the code likely only checks the deadline and then proceeds to refund without confirming failure. Thus a contributor who contributed, say, 10 SOL can call refund and the program will execute it as if the campaign failed, returning their 10 SOL.
This issue can undermine the finality of successful campaigns. If exploited, it could result in a situation where after a supposedly successful fundraising:
Contributors withdraw their funds back, leaving the campaign under-funded or even empty.
The creator, expecting to collect the funds, might end up with nothing or far less than the goal, even though the goal was reached.
From the creator’s perspective, this is equivalent to the contributors “rug-pulling” the campaign at the last moment. It destroys the trust that once a campaign is successful, the funds are secured for the project. Consider a scenario: A malicious contributor (or a group of them) could intentionally contribute a large amount to push a campaign over the goal, making it appear successful, and then immediately call refunds after the deadline to yank their money out. This leaves other honest contributors and the creator in a lurch – the campaign is marked as “successful”, but the funds vanish back to the contributors due to this loophole. This could sabotage projects or be used to falsely signal success and then revert it. It’s worth noting that due to untracked contributions, a contributor’s actual ability to retrieve funds is currently hampered (since their recorded amount is 0, they would get 0 back). However, is still a serious design flaw on its own. If the contribution tracking bug were fixed, would directly allow refunding real amounts after success. The mere ability to call the refund instruction without being blocked is indicative of the missing check. In our tests, we observe that a refund transaction does not revert in a success scenario (proving no proper check), although due to contr.amount=0 the refunded amount is zero. This still leaves the protocol logic broken and could be exploited in unintended ways if combined with partial fixes or different contribution flows.
Analyzing the refund function code and comparing it with the requirements description (crowdfunding specification).
Manual analysis of conditional operators inside a function.
Add a condition that would allow the refund to be executed only if the campaign has not reached the goal (for example, if fund.amount_raised >= fund.goal { return Err(ErrorCode::UnauthorizedAccess.into()); }).
Update the documentation and conduct testing to ensure that refunds are only possible if the campaign fails.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.