# AirdropBroker: When block.timestamp == lastEpochUpdate + EPOCH_DURATION, users can exercise options in the new epoch.

AirdropBroker option expiry check uses strict <, letting users call newEpoch and exercise prior-epoch options at the new epoch's TAP price.

- Payout: Contest reward
- Severity: medium
- Program: Tapioca DAO
- Credited: cccz
- Disclosed: 2024-05-23
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-02-tapioca-findings/issues/20
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-tapioca%3AM-30
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:business-logic

**Target:** AirdropBroker option exercise / epoch boundary
**Root cause:** exerciseOption checks aoTapOption.expiry < block.timestamp, so at exactly lastEpochUpdate+EPOCH_DURATION the option is still valid while newEpoch() can already advance the epoch and reset epochTAPValuation.
**Steps:**
1. Alice holds aoTAP option with expiry = lastEpochUpdate + EPOCH_DURATION from epoch 1.
2. Wait until block.timestamp == lastEpochUpdate + EPOCH_DURATION.
3. Call `newEpoch()` (no onlyOwner) to advance to epoch 2 and refresh epochTAPValuation to current lower TAP price.
4. Call `exerciseOption(_aoTAPTokenID, paymentToken, tapAmount)`; expiry < block.timestamp is false so it passes, exercising at epoch 2 price.
**How found:** Code reading of the expiry comparison in exerciseOption against the newEpoch() timestamp guard, noticing both accept block.timestamp == lastEpochUpdate + EPOCH_DURATION and that newEpoch lacks onlyOwner.
**Exploitation:** At the exact epoch boundary timestamp, attacker calls newEpoch() then exerciseOption() in the same block/transaction, paying the newly-lowered epoch TAP valuation for options minted under the prior epoch, capturing the price difference (e.g. 1000 TAP at $2 instead of $5).
**Insight:** When two functions share a time boundary, check whether their comparisons are strict vs non-strict and whether the boundary-crossing function is permissionless; an off-by-one at an epoch/round boundary can let you reprice an asset in the same block.
