ARBI.SECURITY Sep 19, 2026

Security, Plainly

The engine has not had a third-party audit yet. This is its own review, in the open: what it must never do, the tests that check it, the risks that remain, and what the owner can and cannot change.

01

Summary

Unit tests
5252 passing, 0 failing
Fuzz tests
2512 random runs each, passing
Invariants
4128 runs of 64 random calls, holding
Fork runs
4real pools, a caller with zero tokens
Audits
0no third-party audit yet

forge test, Sep 19, 2026: 58 passed, 0 failed, 0 skipped (52 unit, 2 fuzz, 4 invariant). Fork runs: an anvil fork of Robinhood Chain at block 66,657,028, same day. Every invariant below held in every run.

02

Scope

Review.ScopeLIVE
ContractArbiStocks.solOne file, no imports, no libraries.
Compilersolc 0.8.28Via IR, optimizer 400 runs, EVM version cancun (transient storage).
UpgradesnoneNo proxy, no delegatecall, no self-destruct. The code you read is the code that runs.
ChainRobinhood ChainChain id 4663, an Arbitrum Nitro chain.
Talks toUniswap v3The Uniswap v3 factory and its pools, USDG, WETH and Robinhood stock tokens. Nothing else.
HoldsnothingNo balance between transactions, no allowances, no user deposits.
StatusengineDeployed, block 67,744,893
Reviewed byits testsUnit, fuzz, invariant and fork tests, listed below. No third-party audit yet.
03

Invariants

What the engine must never do, how the code enforces it, and which tests check it. Test names are the ones in the suite.

Invariants10
IDInvariantEnforced byChecked byResult
I-01The engine holds nothing between transactions.Profit leaves inside arb. The engine never keeps a balance and never grants an allowance.EngineHoldsNothing (invariant), all fork runsHOLDS
I-02Callbacks are authenticated.Transient expectedPool is set before every swap, checked and cleared in the callback. The first callback also checks the hash of its route.4 callback tests, FakeCallbackMidCycleHOLDS
I-03Only canonical pools.factory.getPool(token0, token1, fee) must return the pool itself.Route_NonCanonicalPoolHOLDS
I-04Only USDG, WETH and stock tokens.Every other token needs the stock code hash (or one the owner allowed).Route_DisallowedToken, Owner_StockCodeGuardsHOLDS
I-05Profit or revert.The cycle must end with more than the first pool took (Unprofitable), and the caller's share must reach minProfit (MinProfit).Arb_Unprofitable, Arb_TooBigBecomesUnprofitable, Arb_MinProfitNotMetHOLDS
I-06The fee never exceeds 20%.MAX_FEE_BPS = 2000 is a constant; the constructor and setFee revert above it.FeeWithinCap (invariant), Owner_FeeCap, Arb_MaxFeeHOLDS
I-07Payouts match the reported split.fee = profit × feeBps / 10000 to the treasury, the rest to to; the Arb event reports both.PayoutsMatchReportedSplit (invariant), FeeSplitAndProfit (fuzz)HOLDS
I-08A quote is what arb realises.quote runs the same swaps and reverts before paying the first pool, so it changes nothing.QuoteMatchesArb (invariant), ProfitMatchesQuote (fuzz), Quote_NoStateChange_AndArbRealisesItHOLDS
I-09No reentry.A transient lock shared by arb, quote and quoteMany; rescue is refused while a cycle runs.5 reentrancy testsHOLDS
I-10Later hops fill completely.A partial fill after the first pool reverts (PartialFill). The first pool may fill partly; the cycle then runs on its real amounts.Arb_PartialFillLaterHopReverts, Arb_PartialFillFirstPoolUsesRealDeltasHOLDS
04

Test Coverage

Unit tests run the engine against mock Uniswap v3 pools and tokens: every revert path, the fee math, hostile tokens and hostile callers. Fuzz tests throw random fees, sizes and prices at the split and the quote. Invariant tests drive random sequences of cycles, quotes, fee changes and outside trades, and check four properties after every call.

forge.test58 passed
GroupWhat it checksTestsResult
ConstructorEvery argument stored; zero addresses, equal quote tokens, an empty code hash and a fee over the cap rejected.2PASS
Cycles that paySame quote USDG, same quote WETH, cross quote in 3 hops, 4 hops, fee at 0, fee at the cap, a partial fill of the first pool.7PASS
Cycles that revertExpired, paused, zero amount, amount too large, bad recipient, below minProfit, unprofitable, too big to pay, partial fill on a later hop.9PASS
QuotesNo state change and arb realises the quote; losing cycles quote; bad routes revert; works while paused; quoteMany mixed and malformed; one gas-hungry quoteMany entry cannot starve the rest; a quote under a state override with empty storage.8PASS
RoutesLength, repeated pool, not returning to start, start must be USDG or WETH, pool must trade the held token, non canonical pool, disallowed token.7PASS
CallbacksA direct call, a canonical pool outside a cycle, a pool that never calls back, wrongly signed deltas.4PASS
Reentrancyarb, quote and quoteMany from a token hook, a fake callback mid cycle, rescue mid cycle.5PASS
TokensA token that returns nothing on transfer works; one that returns false reverts.2PASS
OwnerOnly the owner, the fee cap, the treasury, code hash guards, renounce always reverts, two step transfer, rescue.7PASS
DonationsTokens sent to the engine stay put and a cycle never spends them.1PASS
FuzzFee split and profit; profit matches the quote. 512 runs each.2PASS
InvariantsEngine holds nothing, payouts match the split, quote matches arb, fee within the cap. 128 runs of 64 calls each.4PASS

forge 1.7.1, Sep 19, 2026: 58 passed, 0 failed, 0 skipped.

Fork Runs

The same engine against real Robinhood Chain pools on a local anvil fork. Each run pushes one real pool out of line with a large swap, quotes, then runs arb from a fresh account that holds no tokens at all. It checks the profit reached to, the fee reached the treasury, both match the quote, the pools converged and the engine's balance is zero.

fork.e2e4 passed
RunRoute (NVDA)HopsGasProfitSpread after
AUSDG 0.05% → USDG 0.30%2329,53914.625349 USDG35.1 bps
BWETH 0.05% → WETH 0.30%2357,3320.003705 WETH35.1 bps
CUSDG 0.05% → WETH 0.05% → WETH/USDG 0.01%3499,10593.684510 USDG11.0 bps
DUSDG 0.05% → WETH 0.05% → WETH 0.30% → USDG 0.30%4511,5603.624151 USDG70.3 bps

Block 66,657,028, Sep 19, 2026. Profits come from a gap the test itself opened, not from the market. Spread after is the gross spread left between the route's pools: each landed on its fee band.

05

Known Risks

What the tests cannot remove. Each one is also in the docs.

R-01 · No third-party audit yetOPEN

Only its authors and its tests have reviewed the engine. The tests are listed above, but tests only check what someone thought to check. Use it knowing that.

R-02 · Another bot takes the gap firstBY DESIGN

Cycles are public and race each other. The one that lands second finds a smaller gap: it reverts with Unprofitable or MinProfit and its caller pays gas. Mitigation: the minProfit guard and a short deadline.

R-03 · A quote is exact for one blockBY DESIGN

Any trade between the quote and the moment a cycle lands can shrink the gap. The cycle then pays less or reverts. It never pays out of your pocket, because nothing of yours is in it.

R-04 · Stock tokens can pauseEXTERNAL

Robinhood's stock token contracts can pause or restrict transfers. Swaps through a paused stock revert, so every route through it reverts too. The engine holds nothing that could be stuck.

R-05 · One owner keyBY DESIGN

One key can pause cycles, set the fee up to 20%, change the treasury, allow a new stock code hash and rescue stray tokens (the full list is below). It cannot touch a cycle's funds or yours. Ownership moves in two steps, and renouncing it is disabled: an engine without an owner could never be paused.

R-06 · A wrong code hash widens the token setOWNER

setStockCode exists for the day Robinhood ships a new token proxy. A wrong hash would let other tokens onto routes. They would still need canonical Uniswap v3 pools and a profitable cycle, and the engine would still hold nothing.

R-07 · Lookalike stock tokensBY DESIGN

The code hash check proves a token runs Robinhood's stock token proxy code, not who deployed it: anyone can deploy another copy of that proxy. That cannot hurt the engine, which holds nothing, but a route through a lookalike is the caller's own choice. The app and the keeper only build routes from the stock list in the site's config, matched by address.

R-08 · Sequencer and nodesEXTERNAL

Robinhood Chain has one sequencer. If it stops, no cycle settles. Pages read the chain through a read-only proxy; when it fails they show the last snapshot and say so.

06

The Owner's Powers

Owner.Powers
PowerFunctionLimit
Pause cyclessetPaused(bool)Stops arb only. quote and quoteMany keep working.
Set the feesetFee(uint16)0 to 2000 bps (20%) of profit. Reverts above. Today: 10%.
Set the treasurysetTreasury(address)Not the zero address, not the engine itself.
Allow a stock code hashsetStockCode(bytes32, bool)Adds or removes an extra hash. Cannot touch the built-in stock code hash, zero or the empty hash.
Rescue stray tokensrescue(token, to, amount)Tokens or ETH sent to the engine by mistake. Refused while a cycle runs.
Hand over ownershiptransferOwnership, acceptOwnershipTwo steps: nothing changes until the new owner accepts.
RenouncerenounceOwnership()Always reverts.

The Desk's Owner

The desk and the runner sit around the engine and never replace it: every cycle still goes through the engine's checks and still reverts if it does not pay. The runner has no owner at all. The desk's owner can:

  • Allow or remove a keeper, open the desk to anyone, or pause it.
  • Set the keeper bounty from 0 to 50% of each cycle's profit.
  • Sweep tokens sent to the desk by mistake, and only the part above what it owes: sweep refuses to touch anything booked to a keeper or to the owner.
  • Hand ownership over in two steps. Renouncing is disabled.

The desk and the runner were each attacked by three independent reviews before deployment. Three low severity defects in the runner were confirmed, fixed and covered by tests; none could move funds.

What The Owner Cannot Do

  • Change the factory, USDG, WETH or the stock code hash. They are immutables, fixed at deploy.
  • Upgrade the code. There is no proxy.
  • Set the fee above 20%, or take a fee from a cycle that lost.
  • Take a cycle's funds or yours. The engine pays pools only inside their own callbacks, and the split at the end.
  • Stop quotes. Pausing blocks arb only.
  • Move anyone's tokens. Callers approve nothing.
07

How It Ships

1 Check

The deploy script refuses to guess. It checks the chain id, that the site's ABI and engine code match the build, that the deployer has no pending transactions and enough balance, then prints the plan.

2 Consent

The owner types deploy. Nothing broadcasts before that, and the script will not deploy a second engine unless told to on purpose.

3 Verify

Read back, then publish. The script reads the deployed engine back, routes a quote through it, submits the source to Sourcify and only then writes the address into the site's config.

08

Report An Issue

Found something? Send a DM to @arbistocks on X with what you found and how to reproduce it. Please give us time to fix an exploitable issue before you post it in public.

DM On X