Chapter 11 · Security model
Security model
What no one can do to a live pool, the locks, Arc EVM rules, the invariants and the test suites.
Hookarc's security model is built on subtraction: the functions that would let anyone move liquidity, change a live pool's rules or reach into a pool from the outside do not exist. What remains is a small set of owner powers over future launches and fee routing, and a set of invariants that the test suite enforces with reverts treated as failures. On top of that sit a handful of Arc-specific EVM rules that the contracts are written to respect.
What nobody can do
- Remove liquidity. The LPLocker has no decrease or withdraw function, and the hook's
beforeRemoveLiquidityreverts unconditionally withLiquidityLocked. The seed position exists until the chain does. - Add liquidity.
beforeAddLiquidityaccepts the locker exactly once per pool (seededflips true); anyone else getsNotAuthorized. - Change a live pool. Blocks and their parameters are written once in
register(); there is no setter. The fee split is a constant. The quote and orientation are immutable pool state. - Mint or tax the token.
LaunchTokenhas no owner, no mint, no pause, no blacklist and no transfer hook.burnonly burns the caller's own balance. - Upgrade. No proxies anywhere.
- Reach a pool through the registry. The registry is read at launch and by Halt Guard; disabling a quote affects only whether new launches succeed, and Halt Guard pools react only to the feed and the token's
paused()flag.
What owners can do
| Role | Powers |
|---|---|
| Hook owner | claimProtocolFees, setFeeSink, setTrustedRouter, two-step ownership transfer |
| Factory owner | setHook (once), setLaunchesPaused, withdrawCreationFees, two-step ownership transfer |
| Registry owner | add, disableForNewLaunches, setSequencerFeed (unused on Arc), direct ownership transfer |
| Burner owner | configure (once), setMaxQuotePerCall, buybackAndBurn within the cap and once per block, two-step ownership transfer |
Pausing launches stops new pools; it does not stop swaps or claims on existing pools. Marking a router trusted only changes who the hook believes about trader attribution (for the pot and the event); an untrusted caller is labelled with tx.origin.
Locks and ordering
- Launch lock (EIP-1153). Set in
register, checked inbeforeSwap, cleared by the end of the transaction. No swap can happen in the launch transaction. - Unlock discipline. Every PoolManager interaction goes through
unlockcallbacks that checkmsg.sender == poolManager. The hook'sreceive()only accepts native value from the PoolManager. - Pot advance once per block.
potLastBlockprevents landing on N by repetition inside one block. - Fee determinism.
_buyFeesis a view of frozen config plusblock.number, called identically inbeforeSwap(to book) andafterSwap(to report). Ledgers and events cannot diverge. - Exact-input only. Removes the class of bugs where a fee has to be solved for on the output side.
Arc EVM notes
Arc is an EVM chain (Osaka) with a few rules that differ from a plain Ethereum node. The contracts and scripts honour each of them:
| Arc rule | What it means for Hookarc |
|---|---|
Native value to address(0) reverts (Zero address not allowed); burning native USDC is forbidden | Nothing ever sends value to the zero address. The burner delivers $HARC to 0x000…dEaD; LaunchToken.burn is an ERC-20 supply reduction, not a transfer. |
| Circle's blocklist is enforced at the protocol level, before the mempool and again at execution; the fee is still charged | A claim, sweep or withdrawCreationFees to a blocked address reverts and the ledger keeps the balance. Choose recipients that can receive USDC. |
PREVRANDAO is always 0 | Nothing in Hookarc uses on-chain randomness. The pot counter is deterministic by design. |
No public mempool (eth_newPendingTransactionFilter is unsupported) | A launch cannot be seen before it lands; the launch lock still guards the same block. Front-running a specific buy is not possible from the mempool, but a bot can still react to events in the next block. |
| Blob (type-3) transactions are rejected; EIP-155 is mandatory | Deploy scripts use plain EIP-1559 transactions with the chain id. |
| Sub-second blocks can share a timestamp | The app orders swaps by block number and log index, never by timestamp alone. |
The public RPC caps eth_getLogs at roughly 10,000 blocks and rate-limits aggressively | The web app's log scanner halves its range on failure and shares one in-flight scan across callers. |
Invariants
The invariant suite runs with fail_on_revert = true, so an unexpected revert in the handler counts as a failure rather than a skipped step:
| Invariant | Statement |
|---|---|
invariant_claimsCoverLedgers | For every quote, the hook's ERC-6909 claim balance is at least protocolFees + Σ creatorFees of pools in that quote. |
invariant_claimsCoverLedgersWithPot | The same, adding pot and Σ potOwed. |
invariant_hookHoldsNoLooseBalances | The hook's native balance and ERC-20 balances are zero. |
invariant_seedLiquidityLocked | The seed position's liquidity never decreases. |
invariant_burnOnlyShrinksSupply | totalSupply of a LaunchToken never increases and equals 1e9 minus what was burned. |
invariant_potCounterMonotone | potCount never decreases and pot is zero right after a win. |
Test coverage
- Unit suites covering every contract, both quote orientations (
quoteIsCurrency0true and false), 6- and 8-decimal quotes, the dust branch, partial fills, refunds and every guard error, asserted as exact wrapped hook errors. - Two invariant suites (
Solvency,BlocksInvariant) driven by a handler that launches, buys, sells, claims and sweeps at random. - Fork suites against the public Arc RPC: one wires the full system on the canonical PoolManager, launches, buys and sweeps; one reads the live USDC and cirBTC feeds and
paused()flags. Both skip when the RPC is unreachable. - Tests use
isolate = truebecause the launch lock is transient storage.
Known limits
- Not audited. No external audit has been performed. The design decision is that no mainnet marketing happens before one.
- No fairness guarantee. Anti-Snipe bounds per-block volume and taxes early buys; it does not stop bots, MEV or splitting across addresses.
- Oracle dependence at the edges. The opening price and Halt Guard depend on Chainlink and on the quote token's
paused()flag. The AMM itself never reads the oracle. - Issuer-controlled quotes. A paused USDC or cirBTC cannot be transferred; a pool quoted in it is stuck until Circle unpauses, whatever Hookarc does. A blocklisted trader cannot receive their quote back either.
- Trusted router attribution. If the owner marked a malicious router trusted, it could misattribute pot winners. Routers can be untrusted again; the pot is the only thing attribution affects.
contracts/test/invariant/Solvency.t.solclaims and balancescontracts/test/invariant/BlocksInvariant.t.solsupply and pot invariantscontracts/test/Guards.t.solevery guard error, exact-matchedcontracts/test/fork/ArcFork.t.solthe live feeds, tokens and PoolManager on Arccontracts/foundry.tomlisolate, fail_on_revert, profiles, the arc RPC alias