Summary
purchase_license executes both token transfers (creator payment at line 224, fee at line 229) before writing the License record at lines 244-250 and updating the listing.sale_count / listing.status at lines 260-266. A re-entrant callback during either transfer would find the license not yet recorded and the listing still Active — a second purchase attempt would pass the expiry check and double-charge the buyer.
Location
contracts/creative-marketplace/src/lib.rs, purchase_license
// ❌ Interaction before Effect
token_client.transfer(&buyer, &listing.creator, &creator_amount); // line 224
token_client.transfer(&buyer, &admin, &fee); // line 229
// License not yet written ↓
env.storage().persistent().set(&DataKey::License(listing_id, buyer), &license); // line 244
listing.sale_count += 1; // line 252
Fix
Write the License record and update listing before the token transfers:
env.storage().persistent().set(&DataKey::License(...), &license);
listing.sale_count += 1;
env.storage().persistent().set(&DataKey::Listing(...), &listing);
// extend_ttl calls...
// then token transfers last
token_client.transfer(&buyer, &listing.creator, &creator_amount);
if fee > 0 { token_client.transfer(&buyer, &admin, &fee); }
Summary
purchase_licenseexecutes both token transfers (creator payment at line 224, fee at line 229) before writing theLicenserecord at lines 244-250 and updating thelisting.sale_count/listing.statusat lines 260-266. A re-entrant callback during either transfer would find the license not yet recorded and the listing stillActive— a second purchase attempt would pass the expiry check and double-charge the buyer.Location
contracts/creative-marketplace/src/lib.rs,purchase_licenseFix
Write the
Licenserecord and updatelistingbefore the token transfers: