# _execSellNftToMarket() re-enter steal funds

Missing nonReentrant on _execSellNftToMarket lets a malicious NFT contract reenter during safeTransferFrom so two liens are sold but only one payment is made, stealing funds.

- Payout: Contest reward
- Severity: high
- Program: Particle Protocol
- Credited: bin2chen
- Disclosed: 2023-08-07
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2023-05-particle-findings/issues/14
- Library page: https://bugbountylibrary.com/r/c4%3A2023-05-particle%3AH-04
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:reentrancy

**Target:** ParticleExchange NFT sell-to-market flow
**Root cause:** The balance-delta check (address(this).balance - ethBefore - wethBefore != amount) is used to confirm payment, but the function lacks a reentrancy guard and calls attacker-controlled safeTransferFrom, so a nested sell can satisfy the outer check's delta.
**Steps:**
1. Deploy a malicious ERC721 that mints two tokens and approves ParticleExchange.
2. Call supplyNft for both tokens with sellAmount=10 ether each.
3. Call sellNftToMarket on lien 1; inside safeTransferFrom for tokenId 1, reenter sellNftToMarket on lien 2.
4. In the nested call, actually transfer 10 ether to ParticleExchange so the balance delta check passes.
5. Return to the outer call; its balance delta now already equals amount, so no second payment is required.
6. Call repayWithNft for both liens to withdraw 10 ether each, netting 10 ether profit.
**How found:** Researcher noticed _execSellNftToMarket verifies payment via a balance delta rather than a per-call accounting variable, and that the function calls attacker-controlled safeTransferFrom without a nonReentrant guard, enabling a nested sell to satisfy the outer check.
**Exploitation:** Attacker supplies two fake NFTs as liens, triggers sellNftToMarket on the first, and reenters via the malicious safeTransferFrom to sell the second. Only the nested call pays; the outer call's balance-delta check passes because the nested payment already moved the balance. Both liens are then repaid, extracting the protocol's funds.
**Insight:** When a contract validates payment by comparing before/after balance deltas, any nested call that moves the same amount can satisfy the outer check; treat balance-delta checks as reentrancy-prone and look for attacker-controlled callbacks (safeTransferFrom, hooks) in the same function.
