# Create a short call order with non empty floor makes the option impossible to exercise and withdraw

Short call orders with non-empty floorTokens can never be exercised or withdrawn, locking maker's ERC721/ERC20 and taker's premium forever.

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

**Target:** PuttyV2.sol options contract (exercise/withdraw/fillOrder)
**Root cause:** fillOrder does not enforce empty order.floorTokens for short calls, but exercise/withdraw pass floorAssetTokenIds of mismatched length into _transferFloorsIn/_transferFloorsOut, causing index-out-of-bounds or length-check reverts.
**Steps:**
1. Maker creates short call order with non-empty floorTokens array and fills it; taker receives long call NFT and pays premium.
2. Taker calls `exercise` with empty `floorAssetTokenIds`: passes the isCall check (requires length 0) but `_transferFloorsIn` indexes `floorTokenIds[i]` on empty array -> `Index out of bounds`.
3. Taker calls `exercise` with non-empty `floorAssetTokenIds`: reverts on `require(floorAssetTokenIds.length == 0, "Invalid floor tokenIds length")`.
4. After expiry maker calls `withdraw`, which calls `_transferFloorsOut(order.floorTokens, positionFloorAssetTokenIds[floorPositionId])` with empty ids -> same out-of-bounds revert, locking collateral.
**How found:** Code reading of exercise/withdraw paths: noticed isCall branch requires empty floorAssetTokenIds while _transferFloorsIn indexes floorTokens, and fillOrder never validates floorTokens emptiness for short calls; confirmed with Foundry PoC.
**Exploitation:** Griefing: attacker (C) creates a short call with floorTokens matching tokens already held by Putty from another pair (A/B). Taker D fills and exercises against those resident floor tokens, invalidating pair A/B so A cannot exercise and B cannot withdraw, locking NFTs and premium.
**Escalation:** STYJ's scenario chains the bug with floor tokens already custodied by the contract to grief an unrelated options pair, raising severity to high.
**Insight:** When a struct field is only meaningful for some order types, check every code path that consumes it (fill, exercise, withdraw) for length/emptiness invariants; mismatched array lengths between stored order data and caller-supplied ids are a reliable revert/lock vector.
