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:
- Byte offsets count from the least significant byte. A field at offset
obytes with widthwbytes is(word >> (8*o)) & ((1 << (8*w)) - 1). - Signed fields must be sign-extended. For an
int24field: take the 24-bit valuev; ifv >= 0x800000, the value isv - 0x1000000. The same rule applies at 56 bits (int56) and 64 bits (int64). GeneLens does this withint24(uint24(w >> off))(L:276-281) andint64(uint64(...))(L:197-199). - Read every slot of one view at one block tag. All values in one panel must come from the same block.
- Values describe the last touched block. Slot 0 changes only when a swap,
metabolize(),regenerate()orevolve()touches a new block (H:364, H:728-730). Slot 1 also changes on every swap:afterSwapupdatesrevenueGwei,netGeneMicroandblockVolumeGwei(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_IDhas no getter and equalsuint256(uint160(token()))(H:235). - The action guard
ACTION_SLOT(H:155) is a transient-storage slot (tload/tstore, H:400-417).extsloadusessloadand cannot see it; it is zero outside the hook's ownunlock.
#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
iis atuint256(keccak256(abi.encode(uint256(18)))) + i(L:95). - Each element packs
int24 lowerat bits 0-23 andint24 upperat 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) != 0The 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,prev2MaxTickand addedatlTick(H:98-105). - Slot 1 field
volumeGweibecameint56 netGeneMicroin the same bits (H:113). - Slot 2 no longer holds
floorTickorimmFloorTick(forge layout above ends slot 2 atlastRegenerateBlock, 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).