# PairpopLauncher security review

**Contract:** `PairpopLauncher` at `0x9C504EBB286560C5F2Ee1197950e806992487075` (Robinhood Chain, id 4663)
**Source:** `contracts/pairpop/src/PairpopLauncher.sol`, Solidity 0.8.26, no external dependencies, no proxy, no upgradeability
**Review date:** 2026-09-08
**Reviewer:** internal review by the build agent. This is a documented in-house review, not a third party audit by an external firm.

## Method

- Full manual line by line read of both contracts (`PairpopToken`, `PairpopLauncher`).
- Foundry suite: 24 tests, all passing, including a fork test against live chain 4663.
- Adversarial cases explicitly tested: hostile stock token that reverts, hostile token that burns unbounded gas, EOA passed as a stock token, fee recipient that rejects ETH, fee underpayment, supply bounds, empty strings, pagination overflow.
- Build clean under `forge build`, no compiler warnings in the contract sources.

## Result

No critical, high or medium severity issues found. Findings below are low or informational.

## What the contract can and cannot do

**Cannot:**
- Mint, burn, pause, blacklist or freeze any launched token. `PairpopToken` has no owner and a supply fixed at construction.
- Touch a user's tokens or ETH after the launch transaction. The launcher holds no balance between calls: the fee is forwarded and any excess is refunded inside the same transaction.
- Mark the option leg as real. `optionIllustrative` is hardcoded `true` and there is no code path, admin or otherwise, that can set it false.
- Mark an unbacked pair as backed. `stockBacked` is written by the chain after an on-chain probe, not by the frontend.

**Owner can:** change the launch fee, change the fee recipient, disable new launches, transfer ownership. Nothing else. Owner powers do not reach existing pairs, existing tokens or user balances.

## Findings

**L-1 Fee change has no timelock (low).** The owner can raise `launchFee` at any time. A pending user transaction that sends exactly the old fee would then revert with `FeeTooLow`. Impact is a failed transaction and wasted gas, not a loss of funds, and any overpayment is refunded. Accepted.

**L-2 Reentrancy surface on fee transfer (low, not exploitable).** `launch()` follows checks effects interactions: all state is written and the token is deployed before the fee transfer and refund. A malicious `feeRecipient` reentering `launch()` gains nothing, since it would have to pay its own fee and the registry is already consistent. No reentrancy guard needed.

**I-1 `pairsBy(creator)` is unbounded (informational).** A creator with a very large number of pairs could make this view exceed the RPC gas cap. View only, no state risk. `recentPairs(offset, limit)` is the paginated path the site uses.

**I-2 Unbounded strings (informational).** Name, symbol and option metadata are caller supplied strings with no length cap. The caller pays the gas.

**I-3 No rescue function (informational, deliberate).** There is nothing to rescue: the contract's ETH balance is zero outside a transaction, and it never custodies tokens.

## Honest limits of this review

- It covers the launcher and the token it deploys. It does not cover the frontend, the RPC endpoint, or Robinhood Chain itself.
- It is an internal review. If you want a third party firm's signature on it, that has not happened, and this document does not claim it has.
- The option leg remains descriptive metadata. Nothing is collateralised and nothing settles. That is a product fact, not a bug.

## Reproduce it

```
cd contracts/pairpop
forge test -vv
```
