Security and transparency
GENE's audit status, admin powers, trust assumptions, known limitations, threat model, the evidence behind the RC4 candidate and the scope notes for the external audit.
| Deployment | Pre-launch. Addresses will be listed here at launch. |
|---|---|
| Code | RC4 frozen audit candidate: tag rc4-audit-candidate, commit aca5fcd. Genome VERSION 4. |
| Internal gate | 5,920 production tests, 0 failed, 0 skipped; 53,929 attack scenarios on the production code; in every one the protocol ended at or above its no-attack baseline. Verdict: ready for external audit. |
| External audit | Next step. The code is frozen for it; the report will be linked here when it is published. |
| Bug bounty | To be announced. |
| Security contact | To be announced. |
| Admin powers | None after launch(): no owner, no upgrade path, no function that removes liquidity. Before launch, the launcher alone decides when the pool opens. |
| Outside GENE | Uniswap v4's protocol fee is controlled by Uniswap governance, not by GENE. If switched on (up to 0.1% per direction), the total fee becomes about 1.099% and that share never reaches GENE's positions. |
| Known risks | Published in full: Known limitations. |
#Audit status
- Frozen for external audit. The RC4 contracts are code-frozen at tag
rc4-audit-candidate, commitaca5fcd, since 2026-10-07. Any change would make a new candidate. - Through the full internal gate. The production suites (unit, fuzz, manipulation, adversarial, long-hold, mainnet fork, launch rehearsal, invariants) ran 5,920 tests: 0 failed, 0 skipped, on the frozen logic (the freeze changed only the
VERSIONnumber). The production attack matrix ran 53,929 attack scenarios; in every one the protocol ended at or above its no-attack baseline (0 TRANSFER, 0 SELFFUNDED). The internal verdict is "ready for external audit". The one limitation it lists, the thin-market all-time-low ratchet, is described under Known limitations. - External audit is the next step. No external report has been published yet. When one exists, it will be linked here in full.
- The code is the source of truth. Where an older design note differs from the code, the code wins.
#Scope of the audit package
| In scope | Not in scope |
|---|---|
src/GeneHook.sol (1,041 lines), src/GeneLens.sol (302), src/GeneToken.sol (24), src/Genome.sol (116) |
Uniswap v4-core and v4-periphery (vendored, pinned) |
script/Deploy.s.sol, script/Launch.s.sol, script/GeneSaltMiner.sol |
OpenZeppelin ERC20 |
The study harnesses under study-design/ (evidence tooling, not shipped) |
|
test/ref/GeneHookRef.sol (a test-only typed reference) |
#Focus areas requested of auditors
_newBlockand the all-time-low rule: any path that moves the all-time low on an empty-liquidity print, or places a protocol buy above it.- Net-flow accounting: any address-, router- or split-dependent way to fund a budget twice, or above the fee its flow paid.
- Unlock-callback actions: the ERC-6909 claims accounting and the
PlacementSkippedpath, which must consume no reserve, cap or budget. - R1: that the caller cannot steer the REGENERATION allotment, and that repeated partial calls cannot exceed the caps.
- Evolution gates and IMMUNITY's two-epoch confirmation.
- Asks above
min(spot, ATH)and GENE-side placement. - Deployment: hook permission bits from the CREATE2 address, the launch sequence and the genesis position.
#Scope notes for the external audit
These are the specific questions the external audit is asked to settle, beyond the focus areas above. Each comes from reading the frozen source. Notes 2 and 11 also draw on local experiments on an unmodified copy of the RC4 source or the frozen bytecode; those experiments are not part of the RC4 test suite. None is a confirmed finding.
| # | Question for the audit | What the code and tests show today | Code |
|---|---|---|---|
| 1 | Whether the wide-band branch of _bidRange, which returns [top, GENESIS_TICK_UPPER], is reachable in any state. It needs top + GRID <= GENESIS_TICK_UPPER, a lower edge of tick 177,480 or less. |
The invariant suite asserts atlTick >= GENESIS_TICK_UPPER (GenePermanence.t.sol:101), and top = alignUp(atlTick + GAP, GRID). So in every state the tests reach, top is 178,200 or higher and every ETH band starts below the opening price. |
GeneHook.sol:484, 498-499, 516, 782-786 |
| 2 | Whether metabolize() can revert InvariantBroken when the pool price sits at or beyond the METABOLISM or churn bid band, for example after an earlier swap in the same block or with the price held in empty ticks. |
An ETH placement that would need GENE reverts the whole call, including its harvest and its cooldown write (GeneHook.sol:284-286, 655-660). A local test on an unmodified copy of the RC4 source reaches this revert in both cases; a retry in a later block succeeded in each tested case, and no funds were lost. The RC4 grief tests buy back before the keeper runs (AtlGrief.t.sol:74, :108). | GeneHook.sol:484, 516-518, 655-660 |
| 3 | Whether any condition a caller controls can make a metabolize() call place less than it otherwise would, while the call still starts its cooldown. |
The cooldown starts on every call that does not revert. | GeneHook.sol:284-286, 465 |
| 4 | Whether it is intended that an epoch whose close is recorded only after a further untouched epoch funds no budget while still advancing the cumulative water marks. | At the first touch after such a gap, _newBlock writes the budget of the last touched epoch and moves to the current epoch; epoch e spends only the budgets of e − 1. |
GeneHook.sol:747-765, 837-873 |
| 5 | Whether any revert path exists in beforeSwap or afterSwap for the canonical pool. |
The hook has no rule that rejects a swap by size, direction or sender. From every state the invariant runs reach, a small and a very large sell succeed (GenePermanence.t.sol:167-185). | GeneHook.sol:355-390, 734-780 |
| 6 | Whether the NatSpec of MassAdded.anchorTick matches the code for kinds 1 and 4. |
The NatSpec describes the bid anchor as the all-time low or spot if lower; the code reports atlTick for kinds 1 and 4, and the documentation follows the code. This concerns a source comment, not behaviour. |
GeneHook.sol:139-140, 484-485, 516-518 |
| 7 | Whether band clamping near the maximum usable tick can place a buy band at or above the all-time low, and whether that state is reachable. | Bid, churn and IMMUNITY bands clamp their lower tick at MAX_USABLE_TICK − NEAR_WIDTH. The case needs the all-time low within about 1,200 ticks of the maximum tick. |
GeneHook.sol:157, 500, 517, 783 |
| 8 | Whether the hook's ERC-6909 claim balances equal its reserves on every path, including rounding in the REGENERATION fee split. | The invariant suite checks that claims are at least the reserves (GenePermanence.t.sol:139-151). | GeneHook.sol:707-725 |
| 9 | Whether every narrowing integer cast is bounded in every reachable state. | The casts rely on bounds from the fixed supply and the per-call and per-epoch caps. | GeneHook.sol:486-487, 753, 849-858 |
| 10 | Whether METABOLISM bids and the churn tier together can place more than BID_CAP_EPOCH in one epoch. |
_metabolizeBids limits bids against bids already placed (_caps.bid); the churn tier subtracts both tiers. The derived case needs about 1,100 ETH of buys in one epoch. No value leaves the protocol in either case. |
GeneHook.sol:478-491, 526-540; Genome.sol:33 |
| 11 | Whether REGENERATION's buyback window is reachable in practice. | regenerate() buys only while the price is no more than about 1% (REGEN_PREMIUM_TICKS, 100 ticks) above the all-time-low price; otherwise a call spends nothing and the ETH stays in regenReserve (GeneHook.sol:574-577; Genome.sol:46; invariant REGEN). In a local rehearsal on the frozen bytecode, with every GENE in circulation bought from the pool, the price stayed about 11% to 13% outside the window after every account sold everything it held, and no regenerate() call bought. RC4 tests reach a buyback after adding GENE to a seller's balance with a test cheatcode (RegenCooldown.t.sol:60; Rc4Rules.t.sol:58; SecurityCaps.t.sol:118). |
GeneHook.sol:561-591 |
#Immutability and admin powers
No party can change GENE after deployment.
| Property | Source |
|---|---|
No admin, operator or other privileged role: after launch(), no function checks the caller |
GeneHook.sol:33; invariant ROLE |
| No upgrade path: GeneHook's external ABI is exactly the reviewed 27-function set, with no fallback and no receive | GeneHook.sol:44-45; test/unit/HookAbi.t.sol; invariant ABI |
The fee cannot change: a static 1% in the PoolKey, and no call to updateDynamicLPFee |
GeneHook.sol:26-27, 239 |
Liquidity cannot be removed: the only modifyLiquidity call takes a non-negative amount |
GeneHook.sol:28-29, 594-608 |
No claims or tokens are moved to any address: no approvals, no operators, no ETH take; GENE is taken only to the hook, to burn it |
GeneHook.sol:30-31 |
| External calls go only to the PoolManager and GeneToken | GeneHook.sol:32; invariant CALL |
GeneToken: fixed supply, no privileged account, no further mint, no pause, no blacklist, no transfer fee, no transfer hook; burn destroys only the caller's own balance |
GeneToken.sol:6-24 |
| The Genome constants are compile-time constants; no function takes one as input | Genome.sol:4-7 |
The launcher. One address, fixed in the constructor, may call launch() once (GeneHook.sol:163, 252-255). Before launch it alone decides whether and when the pool opens; the whole supply sits in the hook until then, and nobody else can initialize the pool (GeneHook.sol:233, 332-334). After launch it has no power.
What GENE does not control. The Uniswap v4 PoolManager, the Uniswap protocol fee (set by Uniswap governance), and the routing lists of the Uniswap interface and other services, which affect discoverability rather than whether the pool can be traded.
The same immutability applies to the code itself: after deployment, no one can patch it.
#Trust assumptions
| Assumption | Basis |
|---|---|
| Trusted: Uniswap v4 PoolManager correctness (an assumption), Ethereum consensus, the Solidity compiler | Design assumption |
Trusted once: the launcher, for exactly one launch() call |
GeneHook.sol:163, 252-255 |
| Untrusted: everyone else, including routers, aggregators, solvers, validators and builders, wash traders and external pools | Design assumption |
The build pins Uniswap v4-core 59d3ecf5 (1.0.2) and v4-periphery ad04c9f2 |
foundry.toml:1-3 |
The EVM must support Cancun: unlockCallback uses transient storage |
foundry.toml:12; GeneHook.sol:395-418 |
| The launcher must be an address with no code; the chain does not enforce this | Operational check before deployment |
| The launch transaction goes through a private bundle or private RPC; there is no anti-snipe surcharge | Launch.s.sol:4-10 |
| The economic evidence depends on the test harness's model of third-party traders | Test harness |
| The attack-matrix maxima are observed bounds over the tested grid, not a proof over every strategy | Test harness |
#Known limitations
The audit package's own text is published verbatim at /limitations/. In brief:
- §0 The all-time-low ratchet. RC4 accepts a block start as the all-time low only where the pool has active liquidity, which closes the free permanent disable of the earlier design. A paid, liquidity-backed ratchet remains: an attacker can sell through every protocol position and hold a block start there, moving the all-time low, and with it every later protocol buy, lower. Measured cumulative attacker cost in a thin market (about 30 ETH per epoch of organic trades, falling): 0.014 / 0.057 / 0.23 / 1.35 / 2.8 ETH for a 10% / 20% / 40% / 80% / 90% move of the all-time-low price. The attacker never profits in the tests (
test/grief/AtlRatchet.t.sol), and the protocol ends with more Permanent Mass than an un-ratcheted control. Nothing in RC4 bounds how far below spot future protocol support can sit. Persistent or lagging anchors and a separate deployment reference were tested and rejected, because each produced a profitable, third-party-funded placement sandwich. - §1 Falling markets. Every METABOLISM, churn and IMMUNITY bid sits at or below the all-time-low block-start price (GeneHook.sol:484, :498-499, :516), and REGENERATION's buyback pays at most about 1% above it (GeneHook.sol:574-577). In every falling regime tested, protocol ETH reachable from spot within 5%, 10% and 25% is 0 / 0 / 0, with the all-time low between 44.6% and 86.2% below spot.
- §2 IMMUNITY in sell-offs. RC4 places 0.3-0.8 ETH by S+7, none within 25% of spot. Price-restoring round trips create no IMMUNITY budget.
- §3 REGENERATION on the genesis curve. About 0.05-0.1 ETH of book sits within its price limit per call, so the limit binds before the call cap. Under R1 the cooldown cannot be used to lock it out.
- §4 Young markets. Near launch, 150 ETH of buys funds about 0.23 ETH of bids. The part of the
revenuemetric not funded by net buying sizes the churn tier, so those fees can still be committed as Permanent Mass, within the shared epoch bid cap and the ETH reserve (GeneHook.sol:526-539). - §5 Positive "assisted" cells. 0 SELFFUNDED and 0 TRANSFER cells across 53,929 matched rows. Positive "assisted" cells exist: up to +1.03 ETH, and +116 / +14.45 ETH in the intra-block-wick families 6 and 8. In each, the protocol ends above its matched counterfactual, and third-party traders in the harness pay the attacker's extra.
- §6 Coverage. 228 cells cannot execute; the dense 50-tick pump sweep was not run; the grid maxima are observed bounds.
- §7 Carried from RC3. An epoch with no flow places no ETH in the next epoch (asks from
geneReserveare not budgeted, GeneHook.sol:541-543); with more than 8 pending epochs, ETH placement waits forevolve(); the firstmetabolize()needs a priorevolve(); epoch-dependent views update at the new epoch's first touch; a launcher with code is accepted on chain; route discovery needs a live pool.
#Threat model
Only entries valid for the RC4 code are listed. Earlier designs are superseded and not repeated here.
| ID | Threat | RC4 mechanism | Status and evidence |
|---|---|---|---|
| RC4-1 | Manufactured conditions make the protocol place Mass near a manipulated price, then the attacker trades against it (rise-then-pin and related loops) | Every METABOLISM, churn and IMMUNITY bid sits at or below the all-time low (REGENERATION's buyback at most about 1% above it); budgets count only net directional flow, once (GeneHook.sol:35-38, 837-858) | 0 SELFFUNDED and 0 TRANSFER cells in the 53,929 matched rows |
| RC4-2 | Funding a budget through wash trades, many addresses, routers or splits | Budgets depend only on curve flow, counted against cumulative water marks, valued at the epoch's cheapest price (GeneHook.sol:378-383, 834-873) | test/unit/Provenance.t.sol, test/unit/Rc4Rules.t.sol; residual +1.03 ETH assisted cells paid by third-party churn in the harness |
| RC4-3 | Intra-block prints below the all-time low | The anchor reads block-start prices only (GeneHook.sol:732-733, 770-774) | 0 positive cells for attacker-made wicks |
| RC4-4 | Free permanent disable by parking the price in empty ticks | Closed: the all-time low moves only where the pool has active liquidity; oversized placements are skipped (GeneHook.sol:771-774, 619-621) | test/grief/AtlGrief.t.sol, test/grief/PlacementSkip.t.sol |
| RC4-4b | Paid, liquidity-backed ratchet of the all-time low | No mechanism bounds it | Accepted known limitation (§0); attacker never profits in test/grief/AtlRatchet.t.sol |
| RC4-6 | REGENERATION cooldown lock-out by zero-spend or dust calls | Closed by R1: the cooldown starts only after a full-allotment buyback (GeneHook.sol:302-307) | test/unit/RegenCooldown.t.sol, test/grief/RegenCooldownAttack.t.sol |
| RC4-5 | Usefulness residuals | No near-spot support in falling markets; small IMMUNITY placements in sell-offs; small REGENERATION buys early | By design (known limitations §1-§4); REGENERATION's buyback window is scope note 11 |
| HK-1 | Third-party liquidity in the canonical pool | beforeAddLiquidity and beforeRemoveLiquidity always revert; v4 skips them only for the hook's own calls (GeneHook.sol:41-43, 337-352) |
Invariants PM2, PM3 |
| HK-2 | A rogue pool that names this hook | beforeInitialize always reverts; the hook opens its own pool inside launch() (GeneHook.sol:258, 332-334) |
Code |
| HK-3 | Swap callbacks reached with another key or by another caller | beforeSwap checks currencies, fee and tick spacing (WrongPool); both swap callbacks require the PoolManager as caller (GeneHook.sol:359-363, 372) |
Code |
| HK-4 | Spoofed unlock callback | unlockCallback requires the PoolManager as caller and an action matching the hook's own transient action slot (GeneHook.sol:395-418) |
Code |
| HK-5 | Fee changes | The fee is static in the PoolKey; no updateDynamicLPFee call exists (GeneHook.sol:26-27, 239) |
Invariants FEE1, FEE2 |
| HK-6 | Donations to the pool | The hook has no donate permission; a donation becomes fees owed to protocol positions in range, collected at a later harvest (GeneHook.sol:43-45, 669-697) | Code |
| DEP-1 | Launch-block sniping | No anti-snipe surcharge; the launch goes through a private bundle or private RPC (Launch.s.sol:4-10) | Operational |
| DEP-2 | Wrong hook permissions at deployment | The constructor reverts unless the address's low 14 bits are exactly 0x2AC0 and its first byte is not 0x91 (GeneHook.sol:226-229) |
Invariant ADDR |
#Verification
Anyone can check a deployment against the frozen source; the full procedure, including the steps that need an address after deployment, is in verify the deployment. The committed genome hash is:
0x39c130f616c32abfb40f8ab51eb54544cffd3e0d99602c71dc336bc8083c36ae
The frozen bytecode hashes to compare against include the GeneHook runtime with immutables zeroed: 23,389 bytes, sha256 17e1418e1ec5689bcdce02aeec08aeaf89aba182136a917d728cd854e084b4d4. Each gene expression carries an evidence hash that can be recomputed from stored epoch records: verify evidence hashes.
#Evidence
Counts from the RC4 internal gate.
| Evidence | Result |
|---|---|
Suites on the frozen source aca5fcd |
347 passed / 0 failed / 0 skipped: unit 115, manipulation 108, adversarial 40, fork 15, launch rehearsal 11, grief 23, invariants (default profile) 29, gas 6 |
Full regression on the R1 source 4ff8abd, which differs only in Genome.VERSION |
5,920 passed / 0 failed / 0 skipped, including long-hold 5,544 and deep-profile invariants 29 |
| Long-hold / rise-then-pin | 88 runs, 183,260 rows, each asserting attacker total at or below zero: 0 positive; worst −3.479543 ETH |
| Production attack matrix | 241 files, 4,505 forge tests; 53,929 matched attack rows plus 406 boundary rows; 0 TRANSFER, 0 SELFFUNDED; 760 NONTRANSFER cells; all 406 boundary rows have assisted at or below zero |
| R1 attack tests | 0 profitable REGENERATION sandwiches; suppression cannot reduce REGENERATION's buying. These tests reach a buyback after adding GENE with a test cheatcode (see scope note 11) |
Disabled or skipped assertions over test/ |
0 |
How a matrix cell is classified. "Assisted" is the attacker's P&L with the protocol acting, minus the matched PURE run in which the protocol only evolves. NONTRANSFER: the protocol ends no worse than in PURE, so the attacker's extra came from third parties. SELFFUNDED: only the attacker's own flow funded the budgets, and the gain is no more than the fees paid. TRANSFER: anything else; this is the blocking class.
Cases that cannot execute. Two figures are reported, in different units:
- 228 cells (known limitations §6): high-wash churn cells at 5,000 and 20,000 ETH/day that buy the pool's entire remaining GENE and fail with
PriceLimitAlreadyExceeded. They are excluded, not counted as passes. - 129 forge tests in 24 files of the production matrix fail by design: 107 with
PriceLimitAlreadyExceededand 22 withSwapAmountCannotBeZero. They have no matched PURE row and are neither passes nor classified.
Test inventory:
| Suite | Tests |
|---|---|
| unit and fuzz | 115 |
| manipulation | 108 |
| adversarial | 40 |
| long-hold / rise-then-pin, 88 contract runs | 5,544 |
| mainnet fork: real PoolManager, Universal Router 2.0 and 2.1.2, Permit2, V4Quoter, multi-hop, scripts | 15 |
| launch rehearsal on a mainnet fork | 11 |
| grief release tests, placement skip, R1 attack tests | 23 |
| stateful invariants, default and deep profiles | 29 + 29 |
| gas | 6 |
| production attack matrix | 241 files, 4,505 forge tests |
Log integrity hashes and the re-run commands ship with the audit package (tag rc4-audit-package).
#Disclosure
No security contact or bug bounty is published yet.
Sources (30)
- src/GeneHook.sol:25-45
- src/GeneHook.sol:139-140
- src/GeneHook.sol:148-157
- src/GeneHook.sol:224-273
- src/GeneHook.sol:281-309
- src/GeneHook.sol:332-418
- src/GeneHook.sol:479-560
- src/GeneHook.sol:561-591
- src/GeneHook.sol:594-627
- src/GeneHook.sol:655-660
- src/GeneHook.sol:707-725
- src/GeneHook.sol:734-873
- src/GeneHook.sol:782-786
- src/GeneHook.sol:965-973
- src/GeneToken.sol:6-24
- src/Genome.sol:33
- src/Genome.sol:46
- script/Launch.s.sol:4-10
- foundry.toml:1-3
- foundry.toml:12
- test/unit/HookAbi.t.sol
- test/invariant/GenePermanence.t.sol:96-102
- test/invariant/GenePermanence.t.sol:139-151
- test/invariant/GenePermanence.t.sol:167-185
- test/grief/AtlGrief.t.sol:74
- test/grief/AtlGrief.t.sol:108
- test/grief/AtlRatchet.t.sol
- test/unit/RegenCooldown.t.sol:60
- test/unit/Rc4Rules.t.sol:58
- test/unit/SecurityCaps.t.sol:118
Paths are relative to the GENE repository at tag rc4-audit-candidate (aca5fcd).
