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

Documentation · Integrate

Read state

How to read GENE's on-chain state at one block, from GeneHook getters, deployless GeneLens calls, Uniswap v4 StateView and raw storage.

Every snippet on this page takes HOOK_ADDRESS, the hook address listed on addresses. The snippets also run against a local fork or devnet where you deploy the frozen source yourself.

#Where each value lives

Source What it serves Notes
GeneHook getters launched, genomeHash, token, poolId, poolKey, launcher, poolManager, reserves, ethCommitted, geneCommitted, massLiquidity, ethBudget, immunityActive, REQUIRED_FLAGS (GeneHook.sol:161-186, 812, 965, 990)
GeneHook extsload(bytes32[]) Any storage word, one sload per slot, no calls, no writes (GeneHook.sol:950-963)
GeneLens Typed decoders of GeneHook storage: geneStatus, epochRecord, blockState, caps, anchors, flowBudgets, ethBudgets, cumulativeFlow, report, evidenceHashFor, genome Stateless, unbound, never called by GeneHook (GeneLens.sol:12-19)
Uniswap v4 StateView Pool slot0, active liquidity, position liquidity Uniswap periphery (StateView.sol:19-93)

GeneLens is optional. It holds no state, funds or permissions, and anyone can deploy a copy or decode the same slots with eth_getStorageAt (GeneLens.sol:12-16). A GeneLens address is not part of the protocol. The pattern below needs no GeneLens deployment at all: it runs the frozen GeneLens creation code in a deployless eth_call.

Full function lists: GeneHook, GeneLens. Slot map: storage layout.

#Rule: read everything at one block

Pass the same blockNumber to every call that feeds one view of the state. Values read at different blocks can disagree (for example, massLiquidity and the position list).

#Get the GeneLens creation code

Build the frozen source and check the creation code against the frozen hashes on verify the deployment:

git checkout rc4-audit-candidate        # aca5fcd3bbc9ecb1026de58bea431d406d8ecfff
forge build
python3 -c "import json,hashlib;b=bytes.fromhex(json.load(open('out/GeneLens.sol/GeneLens.json'))['bytecode']['object'][2:]);print(len(b),hashlib.sha256(b).hexdigest())"
# expected: 10610 d14ed67685d2ab52f47acf59f75d02abd9e8b72a76ff06654cf151a3948f8958

#Read state with viem

// read-state.mjs   (Node 18+, npm install viem)
// env: RPC_URL, HOOK_ADDRESS (after deployment), STATE_VIEW_ADDRESS (Uniswap v4 StateView), LENS_ARTIFACT
import { createPublicClient, http, parseAbi } from "viem";
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";

const { RPC_URL, HOOK_ADDRESS, STATE_VIEW_ADDRESS } = process.env;
const LENS_ARTIFACT = process.env.LENS_ARTIFACT ?? "out/GeneLens.sol/GeneLens.json";

const hookAbi = parseAbi([
  "function launched() view returns (bool)",
  "function genomeHash() view returns (bytes32)",
  "function poolId() view returns (bytes32)",
  "function token() view returns (address)",
  "function ethReserve() view returns (uint128)",
  "function regenReserve() view returns (uint128)",
  "function geneReserve() view returns (uint128)",
  "function ethCommitted() view returns (uint128)",
  "function geneCommitted() view returns (uint128)",
  "function massLiquidity() view returns (uint256)",
  "function ethBudget() view returns (uint256)",
  "function immunityActive() view returns (bool)",
]);

const lensAbi = parseAbi([
  "function currentEpoch(address hook) view returns (uint32)",
  "function blockState(address hook) view returns ((int24 lastStartTick, uint32 epoch, int24 epochMaxTick, int24 epochMinTick, int24 athTick, int24 atlTick) bs, (uint40 lastBlock, uint48 revenueGwei, int56 netGeneMicro, uint16 activeBlocks, uint32 pathTicks, uint32 blockVolumeGwei) acc)",
  "function geneStatus(address hook, uint8 g) view returns ((uint8 state, uint8 history, uint32 count, uint32 firstEvidenceEpoch, uint32 expressedEpoch, bytes32 evidenceHash))",
  "function epochRecord(address hook, uint32 e) view returns ((uint96 revenue, uint32 activeBlocks, uint32 pathTicks, uint88 ethCommitted, bool closed))",
  "function anchors(address hook) view returns (int24 atlTick, int24 athTick)",
  "function ethBudgets(address hook) view returns (uint256 bidLeft, uint256 immunityLeft, uint256 churnLeft)",
  "function report(address hook) view returns ((uint256 permanentMassLiquidity, uint256[3] reachableEthDown, uint256[3] reachableGeneUp, uint256 cumulativeEthCommitted, uint256 cumulativeGeneCommitted, uint256 positions))",
]);

const stateViewAbi = parseAbi([
  "function getSlot0(bytes32 poolId) view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)",
  "function getLiquidity(bytes32 poolId) view returns (uint128 liquidity)",
]);

// The frozen GeneLens creation code, checked against the freeze record before use.
const LENS_CREATION_CODE = JSON.parse(readFileSync(LENS_ARTIFACT, "utf8")).bytecode.object;
const sha = createHash("sha256").update(Buffer.from(LENS_CREATION_CODE.slice(2), "hex")).digest("hex");
if (sha !== "d14ed67685d2ab52f47acf59f75d02abd9e8b72a76ff06654cf151a3948f8958") throw new Error("not the frozen GeneLens");

const client = createPublicClient({ transport: http(RPC_URL) });
const blockNumber = await client.getBlockNumber();

const hook = (functionName) => client.readContract({ address: HOOK_ADDRESS, abi: hookAbi, functionName, blockNumber });
// Deployless read: viem executes the creation code in an eth_call; nothing is deployed.
const lens = (functionName, args = []) =>
  client.readContract({ code: LENS_CREATION_CODE, abi: lensAbi, functionName, args: [HOOK_ADDRESS, ...args], blockNumber });

if (!(await hook("launched"))) {
  console.log("not launched");
} else {
  const poolId = await hook("poolId");
  const [ethReserve, regenReserve, geneReserve, massLiquidity, ethCommitted, immunityActive] = await Promise.all(
    ["ethReserve", "regenReserve", "geneReserve", "massLiquidity", "ethCommitted", "immunityActive"].map(hook),
  );
  const [currentEpoch, [bs], [atlTick, athTick], budgets, report, metabolism] = await Promise.all([
    lens("currentEpoch"), lens("blockState"), lens("anchors"), lens("ethBudgets"), lens("report"), lens("geneStatus", [1]),
  ]);
  const [, spotTick, protocolFee, lpFee] = await client.readContract({
    address: STATE_VIEW_ADDRESS, abi: stateViewAbi, functionName: "getSlot0", args: [poolId], blockNumber,
  });

  // Epoch-dependent views describe the last touched epoch until the new epoch's first touch.
  const opened = Number(bs.epoch) === Number(currentEpoch);
  // Ticks are log1.0001(GENE per ETH): the ETH price of one GENE is 1.0001^(-tick).
  const ethPerGene = (tick) => Math.pow(1.0001, -Number(tick));

  console.log({
    block: blockNumber, currentEpoch, lastTouchedEpoch: bs.epoch, budgetsCurrent: opened,
    ethReserve, regenReserve, geneReserve, massLiquidity, ethCommitted,
    spotTick, lpFee, protocolFee, atlTick, athTick, atlEthPerGene: ethPerGene(atlTick),
    bidLeft: opened ? budgets[0] : "pending", immunityLeft: opened ? budgets[1] : "pending", churnLeft: opened ? budgets[2] : "pending",
    reachableEthDown: report.reachableEthDown, metabolismState: metabolism.state, immunityPressure: opened && immunityActive,
  });
}

#Read state with cast

# after deployment
B=$(cast block-number --rpc-url $RPC_URL)
cast call $HOOK_ADDRESS "launched()(bool)" --block $B --rpc-url $RPC_URL
cast call $HOOK_ADDRESS "massLiquidity()(uint256)" --block $B --rpc-url $RPC_URL
cast call $HOOK_ADDRESS "ethReserve()(uint128)" --block $B --rpc-url $RPC_URL
POOL_ID=$(cast call $HOOK_ADDRESS "poolId()(bytes32)" --rpc-url $RPC_URL)
cast call $STATE_VIEW_ADDRESS "getSlot0(bytes32)(uint160,int24,uint24,uint24)" $POOL_ID --block $B --rpc-url $RPC_URL
cast storage $HOOK_ADDRESS 0 --block $B --rpc-url $RPC_URL     # BlockState word

#Decode a raw slot

Slot 0 holds BlockState (GeneHook.sol:98-105). GeneLens decodes it at these bit offsets (GeneLens.sol:275-282): lastStartTick 0, epoch 24, epochMaxTick 56, epochMinTick 80, athTick 104, atlTick 128.

// continues read-state.mjs
const w = BigInt(await client.getStorageAt({ address: HOOK_ADDRESS, slot: "0x0", blockNumber }));
const int24 = (x) => Number(BigInt.asIntN(24, x & 0xffffffn));
console.log({ athTick: int24(w >> 104n), atlTick: int24(w >> 128n), epoch: Number((w >> 24n) & 0xffffffffn) });

test/unit/LensLayout.t.sol checks every GeneLens decoder against a reference hook with typed views (GeneLens.sol:17-19). The full slot map is on the storage layout page.

#What the values mean

Value Meaning Display rule
massLiquidity() Sum of liquidity units (L, the Uniswap v4 measure) the hook ever added; never decreases A liquidity amount; show it in liquidity units
ethCommitted() Historical sum of ETH principal placed into bid Mass (kinds 1, 2 and 4) A historical sum; ETH currently held is in the reserves and positions
report().reachableEthDown ETH a seller receives from protocol positions for a 5%, 10% or 25% price move, at the current price Reads 0 when no protocol position lies within the move, as near spot in a falling market; show the 0
ethReserve, regenReserve, geneReserve Harvested fees held as ERC-6909 claims at the PoolManager (GeneHook.sol:181-183)
ethBudget() METABOLISM bid budget for the current epoch, net of the REGENERATION share once that gene is expressed One of three separate budgets; label each one on its own
anchors() atlTick (all-time low, the anchor of every protocol buy) and athTick (all-time high, the ask anchor) Present atlTick as the protocol buy anchor
immunityActive() Read-only signal Shows pressure; a deployment is a mined MassAdded with kind 2

#Views that read ahead of the epoch

ethBudget(), immunityActive() and every epoch-dependent GeneLens view describe the last touched epoch until the new epoch's first touch: a swap, evolve, metabolize or regenerate (GeneHook.sol:728-765). Compare GeneLens.currentEpoch(hook) with blockState(hook).bs.epoch. When they differ, show "budgets pending (epoch not yet opened)" instead of the stored values.

Related: events and finality, known limitations.

Sources (14)
  • src/GeneHook.sol:97-117
  • src/GeneHook.sol:161-186
  • src/GeneHook.sol:236-246
  • src/GeneHook.sol:728-780
  • src/GeneHook.sol:802-817
  • src/GeneHook.sol:950-973
  • src/GeneHook.sol:987-992
  • src/GeneLens.sol:12-34
  • src/GeneLens.sol:36-43
  • src/GeneLens.sol:46-206
  • src/GeneLens.sol:209-227
  • src/GeneLens.sol:262-282
  • lib/v4-periphery/src/lens/StateView.sol:19-93
  • test/unit/LensLayout.t.sol

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