# acceptCounterOffer() May Result In Both Orders Being Filled

acceptCounterOffer cancels the original order without checking if already filled, letting a front-runner fill it so both orders execute.

- Payout: Contest reward
- Severity: high
- Program: Putty
- Credited: kirk-baird
- Disclosed: 2022-06-27
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-06-putty-findings/issues/44
- Library page: https://bugbountylibrary.com/r/c4%3A2022-06-putty%3AH-02
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:race-window technique:source-review vuln:business-logic vuln:race-condition

**Target:** PuttyV2.sol acceptCounterOffer/cancel/fillOrder
**Root cause:** cancel() marks the order cancelled but does not revert if the order was already filled, so acceptCounterOffer proceeds to fill the counter order after the original was filled by a front-runner.
**Steps:**
1. Attacker observes pending acceptCounterOffer(order, sig, originalOrder) tx.
2. Attacker front-runs with fillOrder(originalOrder, ...) to fill the original order.
3. acceptCounterOffer executes: cancel(originalOrder) is a no-op since already filled.
4. fillOrder(order, signature, ...) then fills the counter order, leaving both filled.
**How found:** Code reading of acceptCounterOffer: noticed cancel() lacks a filled-state check and only sets cancelledOrders, so it cannot prevent an already-filled order from being filled again.
**Exploitation:** Front-run the victim's acceptCounterOffer with a fillOrder on the original order; the victim's tx then also fills the counter order, doubling their leverage/exposure.
**Insight:** When a cancel-then-act function relies on a cancel flag, check whether cancel reverts on already-consumed state; front-running the cancel step can make the guard a no-op.
