Pre-launch. RC4 code-frozen for external audit (rc4-audit-candidate, aca5fcd). Report pending. Status, updated 2026-10-08
GENESpecimen 001

Documentation · Reference

Storage layout

GeneHook's fixed storage layout, slot by slot and byte by byte, verified with forge at the frozen RC4 commit.

Verified commit. This layout is the output of forge inspect src/GeneHook.sol:GeneHook storageLayout --json on a detached checkout of tag rc4-audit-candidate, commit aca5fcd3bbc9ecb1026de58bea431d406d8ecfff (verified with git rev-parse HEAD; working tree clean), forge 1.5.1-stable (b0a9dd9c), the toolchain recorded at the freeze.

Citation keys. H = src/GeneHook.sol, L = src/GeneLens.sol, G = src/Genome.sol, all at aca5fcd. Constants are linked to Parameters.

Permanence. GeneHook has no proxy and no upgrade path (H:25-33), so this layout is fixed for the lifetime of a deployment. GeneLens hard-codes the same slot numbers (L:23-34) and test/unit/LensLayout.t.sol checks its decoders (L:17-19).

#How to read a slot

GeneHook exposes one general-purpose read function:

function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values)

It performs one sload per requested slot and nothing else: no external calls, no writes (H:950-963). The same words are available through eth_getStorageAt(hook, slot, blockTag).

Rules for every reader:

  1. Byte offsets count from the least significant byte. A field at offset o bytes with width w bytes is (word >> (8*o)) & ((1 << (8*w)) - 1).
  2. Signed fields must be sign-extended. For an int24 field: take the 24-bit value v; if v >= 0x800000, the value is v - 0x1000000. The same rule applies at 56 bits (int56) and 64 bits (int64). GeneLens does this with int24(uint24(w >> off)) (L:276-281) and int64(uint64(...)) (L:197-199).
  3. Read every slot of one view at one block tag. All values in one panel must come from the same block.
  4. Values describe the last touched block. Slot 0 changes only when a swap, metabolize(), regenerate() or evolve() touches a new block (H:364, H:728-730). Slot 1 also changes on every swap: afterSwap updates revenueGwei, netGeneMicro and blockVolumeGwei (H:376-388). Until a new epoch's first touch, the epoch-dependent fields still describe the previous epoch (H:747-765).

Not in storage:

  • Immutables poolManager, token, launcher, genomeHash, poolId, GENE_ID (H:161-166) live in the runtime bytecode. Read the first five through their getters; GENE_ID has no getter and equals uint256(uint160(token())) (H:235).
  • The action guard ACTION_SLOT (H:155) is a transient-storage slot (tload/tstore, H:400-417). extsload uses sload and cannot see it; it is zero outside the hook's own unlock.

#Slot map

Slot Offset (bytes) Type Name Meaning Declared
0 0 struct BlockState (one word) _bs Block-start price references; see Slot 0 H:98-105, H:169
1 0 struct EpochAcc (one word) _acc Open epoch's accumulators; see Slot 1 H:110-117, H:170
2 0 uint40 launchBlock Block number of launch() H:172, set H:265
2 5 bool launched true after launch() (public getter launched()) H:173, set H:255
2 6 uint32 evolveCursor Next closed epoch to fold into the gene state machines H:174, H:318-329
2 10 uint8 immuneStressBits IMMUNITY deployment-stress register: bit 0 = most recently processed closed epoch met revenue >= IMM_DEPLOY_REVENUE and pathTicks >= IMM_PATH_TICKS H:175-177, H:882-883
2 11 uint40 lastMetabolizeBlock Block of the last non-reverting metabolize() H:178, H:284-286
2 16 uint40 lastRegenerateBlock Block of the last regenerate() that spent its whole allotment H:179, H:302-307
3 0 uint128 ethReserve Harvested ETH fees not yet placed (wei) H:181
3 16 uint128 regenReserve REGENERATION's ETH share (wei) H:182, H:709-712
4 0 uint128 geneReserve Harvested GENE fees not yet placed (GENE-wei) H:183
4 16 uint128 ethCommitted Cumulative ETH principal placed into bid Mass, kinds 1, 2 and 4 (monotonic) H:184, H:624
5 0 uint128 geneCommitted Cumulative GENE principal placed into ask Mass, kind 3 (monotonic; genesis excluded) H:185, H:625
6 0 uint256 massLiquidity Sum of liquidity units ever added by the protocol, genesis included (monotonic) H:186, H:432, H:665
7 0 struct Caps (one word) _caps Per-epoch spend counters; see spend counters H:119-123, H:188
8 0 struct Caps2 (one word) _caps2 Per-epoch spend counters; see spend counters H:125-128, H:189
9 0 mapping(uint32 => EpochRecord) _records Permanent closed-epoch records; see Slot 9 H:72-81, H:191
10-17 0 GeneStatus[4] (two words each) _genes Gene state machines; see Slots 10 to 17 H:83-90, H:192
18 0 Position[] (dynamic) _positions Permanent Mass registry; see Slot 18 H:92-95, H:194
19 0 mapping(bytes32 => uint256) _positionSlot Registry index + 1, keyed by keccak256(abi.encode(lower, upper)) H:195, H:699-705
20 0 uint256 harvestCursor Round-robin cursor over registry positions 1..n-1 H:196, H:677-686
21 0 mapping(uint32 => Budgets) _budgets Net-flow budgets created when epoch e closed; see Slot 21 H:200-204, H:211
22 0 struct Caps3 (one word) _caps3 Churn-tier spend counter; see spend counters H:206-209, H:212
23 0 struct CumFlow (one word) _cum Cumulative net-flow provenance; see Slot 23 H:213-221

Unused bytes: slot 0 bytes 19-31; slot 1 bytes 28-31; slot 2 bytes 21-31; slot 5 bytes 16-31; slots 7 and 8 bytes 28-31; slot 22 bytes 16-31; slot 23 bytes 24-31 (from the offsets and widths above).

#Packed structures

#Slot 0: BlockState

Byte offset Bits Type Field Meaning Source
0 0-23 int24 lastStartTick Block-start tick of the last touched block H:99, H:776
3 24-55 uint32 epoch Epoch of the last touch: (block.number - launchBlock) / EPOCH_BLOCKS H:100, H:747, H:763
7 56-79 int24 epochMaxTick Highest (cheapest-GENE) block-start tick of the open epoch H:101, H:764, H:767
10 80-103 int24 epochMinTick Lowest (most expensive) block-start tick of the open epoch H:102, H:765, H:768
13 104-127 int24 athTick All-time-high GENE price: lowest block-start tick ever H:103, H:770
16 128-151 int24 atlTick All-time-low GENE price: highest block-start tick at which the pool had active liquidity. Seeded at GENESIS_TICK_UPPER; only increases H:104, H:266, H:774

GeneLens decoder: L:275-282. All six fields are seeded to GENESIS_TICK_UPPER at launch (H:257, H:266; G:18).

#Slot 1: EpochAcc

Byte offset Bits Type Field Units Source
0 0-39 uint40 lastBlock block number of the last touch H:111, H:775
5 40-87 uint48 revenueGwei ETH-side LP fees on buys this epoch, gwei, saturating H:112, H:385-387
11 88-143 int56 netGeneMicro Signed net GENE out of the curve this epoch, 1e12-wei units, saturating; positive = net buying H:113, H:382-383, H:1024-1028
18 144-159 uint16 activeBlocks Blocks this epoch with ETH volume of at least MIN_BLOCK_VOLUME H:114, H:739-744
20 160-191 uint32 pathTicks Sum of clamped block-start moves over active blocks H:115, H:740-744
24 192-223 uint32 blockVolumeGwei Current block's ETH volume, gwei, saturating; reset each new block H:116, H:384, H:777

GeneLens decoder: L:69-75. To convert: wei = revenueGwei * 1e9; GENE-wei = netGeneMicro * 1e12. The current block is folded into activeBlocks / pathTicks only at the next block's first touch (H:738-745).

#Slots 7, 8 and 22: spend counters

Slot Byte offset Bits Type Field Meaning Source
7 0 0-31 uint32 Caps.epoch Epoch the counters below belong to H:120
7 4 32-127 uint96 Caps.bid ETH placed this epoch by METABOLISM bids (kind 1) H:121, H:487
7 16 128-223 uint96 Caps.immunity ETH placed this epoch by IMMUNITY (kind 2) H:122, H:503
8 0 0-127 uint128 Caps2.ask GENE placed this epoch as asks (kind 3) H:126, H:548
8 16 128-223 uint96 Caps2.regen ETH spent this epoch by REGENERATION H:127, H:588
22 0 0-31 uint32 Caps3.epoch Epoch of the churn counter H:207
22 4 32-127 uint96 Caps3.churn ETH placed this epoch by the churn tier (kind 4) H:208, H:520

Caps2 has no epoch field. It is reset together with Caps when Caps.epoch differs from the current epoch (H:794-800). A reader must therefore treat slots 7 and 8 as zero for the current epoch whenever Caps.epoch differs from it, and slot 22 as zero whenever Caps3.epoch differs (GeneLens applies the same rule, L:180-184). The counters are rolled only inside metabolize() and regenerate() (H:456, H:562).

GeneLens decoders: L:78-87 (slots 7-8), L:183-184 (slot 22).

#Slot 9: epoch records

Location: keccak256(abi.encode(uint256(e), uint256(9))) (L:262-264).

Byte offset Bits Type Field Meaning Source
0 0-95 uint96 revenue ETH-side LP fees on buys, wei (revenueGwei * 1 gwei at close) H:76, H:750
12 96-127 uint32 activeBlocks H:77, H:751
16 128-159 uint32 pathTicks H:78, H:752
20 160-247 uint88 ethCommitted Cumulative ethCommitted at close, wei H:79, H:753
31 248-255 bool closed true once written H:80, H:754

GeneLens decoder: L:266-273. A record is written only for the epoch that was open at the last touch before an epoch boundary (H:748-756). An epoch with no touch is never written and reads as all zero (H:880).

#Slots 10 to 17: gene status

Gene g (0 GENESIS, 1 METABOLISM, 2 REGENERATION, 3 IMMUNITY; G:57-60) occupies slots 10 + 2g and 11 + 2g (L:47).

Slot Byte offset Bits Type Field Meaning Source
10+2g 0 0-7 enum State (uint8) state 0 DORMANT, 1 OBSERVING, 2 EXPRESSIBLE, 3 EXPRESSED H:65-70, H:84
10+2g 1 8-15 uint8 history IMMUNITY only: last-7-epochs stress bitmap H:85, H:922-923
10+2g 2 16-47 uint32 count Consecutive pressure epochs (IMMUNITY: epochs observed, max 7) H:86, H:910, H:924
10+2g 6 48-79 uint32 firstEvidenceEpoch First epoch of the expressing evidence H:87, H:942
10+2g 10 80-111 uint32 expressedEpoch Epoch at which the gene expressed H:88, H:943
11+2g 0 0-255 bytes32 evidenceHash keccak256(abi.encode(g, first, last, EpochRecord[first..last])); for GENESIS keccak256(abi.encode(uint8(0), genomeHash, launchBlock)) H:89, H:270, H:975-982

GeneLens decoder: L:46-56. State 2 (EXPRESSIBLE) is never stored at rest: _express writes EXPRESSED directly and only emits the EXPRESSIBLE step as an event (H:937-947).

#Slot 18: position registry

  • Slot 18 holds the array length (L:89-91).
  • Element i is at uint256(keccak256(abi.encode(uint256(18)))) + i (L:95).
  • Each element packs int24 lower at bits 0-23 and int24 upper at bits 24-47 (H:92-95; L:96-97).
  • Index 0 is the genesis band [GENESIS_TICK_LOWER, GENESIS_TICK_UPPER] (H:420-431).
  • Entries are deduplicated by (lower, upper) through slot 19 (H:699-705), so adding to an existing band does not append.

Liquidity per position is not in GeneHook storage. Read it from the PoolManager with getPositionInfo(poolId, hook, lower, upper, bytes32(0)); every protocol position uses salt 0 (H:604; L:239).

#Slot 21: net-flow budgets

Location: keccak256(abi.encode(uint256(e), uint256(21))) (L:161).

Byte offset Bits Type Field Meaning Source
0 0-111 uint112 bid LP_FEE x newly funded net GENE bought in e, valued at pLow (wei); read by METABOLISM bids during epoch e+1 H:201, H:867
14 112-223 uint112 imm LP_FEE x newly funded net GENE sold in e, valued at pLow (wei); read by IMMUNITY during epoch e+1 H:202, H:870
28 224-247 int24 pLow The epoch's cheapest price (highest tick) over its block starts and its close H:203, H:761, H:865

At most one of bid / imm is non-zero (H:866-872). Spending in epoch e reads _budgets[e - 1] only (H:815, H:830, H:531, H:567).

GeneLens decoder: L:160-165.

#Slot 23: cumulative flow

Byte offset Bits Type Field Meaning Source
0 0-63 int64 nc Cumulative net curve GENE out since launch, 1e12-wei units H:216, H:845, H:855
8 64-127 int64 hwm All-time high of nc H:217, H:847-850
16 128-191 int64 lwm Lowest nc since that high H:218, H:850-853

Updated only when a closing epoch has non-zero net flow (H:838). GeneLens decoder: L:195-200.

#Worked decode

# Pseudocode: sload(i) is eth_getStorageAt(hook, i) or hook.extsload([i])
def field(word, off_bytes, width_bytes, signed=False):
    v = (word >> (8 * off_bytes)) & ((1 << (8 * width_bytes)) - 1)
    if signed and v >= 1 << (8 * width_bytes - 1):
        v -= 1 << (8 * width_bytes)
    return v

w0 = sload(0)
atl_tick = field(w0, 16, 3, signed=True)        # BlockState.atlTick
w2 = sload(2)
launched = field(w2, 5, 1) != 0
evolve_cursor = field(w2, 6, 4)

rec_slot = keccak256(abi_encode(uint256(e), uint256(9)))
r = sload(rec_slot)
revenue_wei = field(r, 0, 12)
closed = field(r, 31, 1) != 0

The offsets are those in the tables above; each is cross-checked against the GeneLens decoder cited in the same row.

#Changes from RC3

  • Slot 0 dropped prevMaxTick, prevMinTick, prev2MaxTick and added atlTick (H:98-105).
  • Slot 1 field volumeGwei became int56 netGeneMicro in the same bits (H:113).
  • Slot 2 no longer holds floorTick or immFloorTick (forge layout above ends slot 2 at lastRegenerateBlock, offset 16).
  • Slots 21-23 are new (H:198-221).

Material describing the RC3 layout is historical; this page describes RC4.

Sources (24)
  • src/GeneHook.sol:25-33
  • src/GeneHook.sol:65-128
  • src/GeneHook.sol:155-221
  • src/GeneHook.sol:255-270
  • src/GeneHook.sol:284-307
  • src/GeneHook.sol:364
  • src/GeneHook.sol:378-387
  • src/GeneHook.sol:400-433
  • src/GeneHook.sol:479-552
  • src/GeneHook.sol:588
  • src/GeneHook.sol:604-625
  • src/GeneHook.sol:665-718
  • src/GeneHook.sol:728-800
  • src/GeneHook.sol:812-883
  • src/GeneHook.sol:907-982
  • src/GeneHook.sol:1024-1028
  • src/GeneLens.sol:17-34
  • src/GeneLens.sol:46-200
  • src/GeneLens.sol:239
  • src/GeneLens.sol:262-282
  • src/Genome.sol:18-19
  • src/Genome.sol:22-24
  • src/Genome.sol:57-60
  • test/unit/LensLayout.t.sol:11-16

Paths are relative to the GENE repository at tag rc4-audit-candidate (aca5fcd).