# Undermining the fairness of the protocol in swapSource() and possibilities for stealing a jackpot

retry() can front-run swapSource() because maxFailedAttemptsReachedAt uses strict equality, producing two VRF requests and letting an attacker pick a favorable random number.

- Payout: Contest reward
- Severity: medium
- Program: Wenwin
- Credited: alexxander
- Disclosed: 2023-04-12
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-03-wenwin-findings/issues/522
- Library page: https://bugbountylibrary.com/r/c4%3A2023-03-wenwin%3AM-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:race-window vuln:business-logic vuln:oracle-manipulation vuln:race-condition

**Target:** RNSourceController.sol / Lottery.sol randomness source swap
**Root cause:** retry() only sets maxFailedAttemptsReachedAt when failedAttempts == maxFailedAttempts (strict equality), and swapSource() checks maxFailedAttemptsReachedAt + maxRequestDelay rather than lastRequestTimestamp, so a front-run retry() does not block swapSource().
**Steps:**
1. Call `retry()` until failedSequentialAttempts reaches maxFailedAttempts so maxFailedAttemptsReachedAt is set.
2. Wait maxRequestDelay; admin calls `swapSource(newSource)`.
3. Attacker front-runs with `retry()`; failedAttempts > maxFailedAttempts so maxFailedAttemptsReachedAt is not updated and lastRequestTimestamp is bumped.
4. swapSource() still passes its checks and calls requestRandomNumberFromSource(), yielding two concurrent VRF requests.
5. Attacker inspects both VRF callbacks in mempool and front-runs the one favoring them.
**How found:** Reading retry() and swapSource() against Chainlink's 'do not re-request randomness' guidance; noticed strict equality on failedAttempts == maxFailedAttempts and that swapSource() ignores lastRequestTimestamp, implying a front-run retry() cannot invalidate swapSource().
**Exploitation:** Attacker monitors mempool for swapSource(), front-runs retry() to force two Chainlink VRF requests, then front-runs the favorable random-number callback. With short drawPeriod, attacker can additionally use block-stuffing/suppression to hold one callback, register a ticket matching the suppressed RN, and front-run fulfillRandomWords() to win the jackpot.
**Escalation:** Chained with suppression/block-stuffing to convert the double-RN into a guaranteed jackpot win on short drawPeriod lotteries.
**Insight:** When a guard uses strict equality to timestamp a threshold, later increments silently skip the update; check whether sibling functions gate on that timestamp vs a fresher one (lastRequestTimestamp) to find bypassable cooldowns.
