Skip to content
Orient
Notes

Testing against the real singleton

No Foundry on this machine, so the tests run the PoolManager’s actual deployed bytecode in process — and it disagreed with me three times.

Engineering · 4 September 2026 · 6 min


The machine this was written on has no Foundry. No forge, no anvil, no solc binary. That started as an obstacle and ended up producing a better test suite than the obvious setup would have, so it is worth writing down what replaced it.

The PoolManager in the tests is not a mock. It is the runtime bytecode of the contract deployed at 0x8366a3…0951, pulled off Robinhood Chain and executed in process.

How

Two npm packages and no binaries. solc ships as pure JavaScript, so compiling needs nothing that is not in the lockfile. @ethereumjs/vm is a real EVM implementation, so a test can put bytecode at an address and run transactions against it.

const pmCode = readFileSync('test/fixtures/poolmanager.hex', 'utf8').trim();
await vm.stateManager.putAccount(POOL_MANAGER, new Account(1n, 0n));
await vm.stateManager.putCode(POOL_MANAGER, hexToBytes(pmCode));
contracts/script/test.ts

The fixture is 24,009 bytes fetched once with eth_getCode and committed, so the suite is offline and deterministic. The hardfork has to be Cancun: v4 keeps its lock and its currency deltas in transient storage, and on an older fork the first unlock reverts in a way that looks like a bug in your own contract.

What that buys

A mock tells you what you already believe. Three things in this contract came from the real bytecode disagreeing with me.

The first is the fee subtraction. I had assumed modifyLiquidity returned principal and fees as separate deltas to settle. It returns callerDelta with the fees already folded in, and a mock built to my assumption would have passed every test while shipping a vault that let each new depositor skim the fees of everyone before them.

The second is smaller and sharper. An exact-output swap of fifty tokens against a small full-range position reverts with SafeCastOverflow() — selector 0x93dafdf1, which appears in no error list I could find and had to be recovered by hashing candidate signatures until one matched. Negative amountSpecified is exact input, and that is what a test generating fee flow wants.

The third is the storage layout the vault uses to check itself. isFlush() recomputes the singleton’s own slot for this position and compares it with the vault’s bookkeeping. Getting thepools mapping at slot 6, and positions six words into a pool’s state, is the kind of thing you either verify against the real contract or get quietly wrong forever.

PoolManager  24,009 bytes of real runtime code
pool initialised at 1:1

01  seed / release / accounting            4/4
02  fees, harvest and the cut              4/4
03  accrete is the only thing that lifts   3/3
04  release                                3/3
05  the invariant, over a random sequence  2/2

16/16 properties held
the result

What it does not buy

It is not an audit. Sixteen properties I chose, checked by tests I wrote, is exactly as good as my imagination and no better — and the failure mode of a vault is usually a case nobody thought to write down.

It is also not a full production rehearsal. The tokens in the suite are mocks, which means no fee-on-transfer, no rebasing and no reentrant callbacks on transfer. The gas numbers are an EVM implementation’s, not a node’s. And nothing here exercises a hook, because Orient refuses to deploy over a pool that has one.

The honest summary: the flow works against the contract it will actually talk to, and the accounting holds over randomised sequences. That is worth something. It is not worth as much as an audit, and the site says so in the state table rather than here, where you might not look.


NextThe colour on this site is computed