File tree Expand file tree Collapse file tree
programs/anchor-escrow/src/contexts Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -38,6 +38,11 @@ pub struct Exchange<'info> {
3838 pub initializer_ata_b : Box < Account < ' info , TokenAccount > > ,
3939 #[ account(
4040 mut ,
41+ // Critical: bind the passed-in `initializer` and `mint_a` to the escrow state.
42+ // Without this, a taker can spoof `initializer` to redirect the taker payment to themselves
43+ // and still withdraw `mint_a` from the vault.
44+ has_one = initializer,
45+ has_one = mint_a,
4146 has_one = mint_b,
4247 constraint = taker_ata_b. amount >= escrow. taker_amount,
4348 close = initializer,
Original file line number Diff line number Diff line change @@ -124,6 +124,31 @@ describe("anchor-escrow", () => {
124124 . then ( log ) ;
125125 } ) ;
126126
127+ it ( "Exploit attempt: spoofed initializer should be rejected" , async ( ) => {
128+ // Before the fix, a taker could pass `initializer = taker` and `initializerAtaB = takerAtaB`.
129+ // That makes the taker payment a no-op (paying themselves), while still withdrawing `mintA`
130+ // from the vault — stealing the initializer's deposit.
131+ const spoofedAccounts = {
132+ ...accounts ,
133+ initializer : taker . publicKey ,
134+ initializerAtaB : takerAtaB ,
135+ } ;
136+
137+ let threw = false ;
138+ try {
139+ await program . methods . exchange ( ) . accounts ( { ...spoofedAccounts } ) . signers ( [ taker ] ) . rpc ( ) ;
140+ } catch ( e ) {
141+ threw = true ;
142+ // Anchor typically throws ConstraintHasOne on mismatch; we don't assert exact text to keep
143+ // the test stable across versions.
144+ // console.log("expected failure", e);
145+ }
146+
147+ if ( ! threw ) {
148+ throw new Error ( "Exploit succeeded: escrow exchange accepted a spoofed initializer" ) ;
149+ }
150+ } ) ;
151+
127152 it ( "Exchange" , async ( ) => {
128153 await program . methods
129154 . exchange ( )
You can’t perform that action at this time.
0 commit comments