fix(tokens/token-fundraiser): bind check_contributions to the recorded mint - #685
fix(tokens/token-fundraiser): bind check_contributions to the recorded mint#685moviendome wants to merge 3 commits into
Conversation
…d mint contribute.rs and refund.rs both constrain the fundraiser account with has_one = mint_to_raise, so the mint passed in the transaction must equal the one recorded at initialize. checker.rs omits that constraint: its mint_to_raise, and the vault derived from it, are whatever the caller supplies. The goal check then runs against an unrelated token, and the close = maker on the same account destroys the campaign state that every contributor's refund depends on. Add the same has_one the two sibling instructions already carry, and a regression test that funds a vault for a substitute mint to the goal and asserts check_contributions rejects it and leaves the campaign intact.
Greptile SummaryThe PR binds
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (3): Last reviewed commit: "test: assert the mint rejection with rej..." | Re-trigger Greptile |
| await provider.sendAndConfirm(fundTx, [maker]); | ||
|
|
||
| // Call the payout instruction with the fake mint and its funded vault. | ||
| let rejected = false; |
There was a problem hiding this comment.
try catch for "finding an error", isn't the proper way to do it, you should actually assert that the error is present, else your assertion is too vague
There was a problem hiding this comment.
Thanks for the feedback!
Added chai-as-promised and the test now asserts the call rejects with the specific AnchorError (ConstraintHasOne) instead of catching anything.
Followed the pattern already used in tokens/token-2022/transfer-hook/counter
Per review: a try/catch that flags any error is too vague. Follow the token-2022/transfer-hook pattern — assert the call rejects with the specific AnchorError (ConstraintHasOne) via chai-as-promised.
Summary
In
tokens/token-fundraiser(anchor), thecheck_contributionsinstructiondoes not bind the mint to the one recorded when the campaign was created.
contributeandrefundboth constrain the fundraiser account withhas_one = mint_to_raise, so the mint account passed in the transaction has toequal the
mint_to_raisestored atinitialize.checker.rsis the oneinstruction of the three without that constraint:
Its
mint_to_raise— and thevaultderived from it viaassociated_token::mint = mint_to_raise— are therefore whatever the callersupplies, kept self-consistent with each other but not tied to the campaign.
The
vault.amount >= amount_to_raisegoal check then measures an unrelatedtoken account, and because the same instruction carries
close = maker, a callmade with a substitute mint also closes the
Fundraiseraccount — the staterefundneeds in order to return real contributors' deposits.Fix
Add the same
has_one = mint_to_raisethe two sibling instructions alreadycarry:
Test
Adds
tests/checker-mint-binding.test.ts, a self-contained litesvm spec that:check_contributionswith that different mint.It asserts the call is rejected and the campaign account still exists. Without
the constraint the call succeeds and the campaign is closed; with it, Anchor
rejects the mismatch (
ConstraintHasOne) before the handler runs.Verified red→green against the fix.