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

Documentation · Integrate

Rebuild the journal

How to rebuild GENE's history (epoch records, Mass, gene transitions, buybacks) from finalized GeneHook logs alone, and check it against the views.

The journal is the public history of a GENE deployment, rebuilt from the hook's logs. It needs no archive node: every value below comes from logs, and closed epoch records are permanent in storage. The inputs are HOOK_ADDRESS, listed on addresses, and GENESIS_BLOCK, the block of its Genesis log.

#Rules

  • Only finalized logs emitted by the hook address make a record (see events and finality). Views, eth_call results, simulations and pending transactions make no record.
  • One record per event, in (blockNumber, logIndex) order. Anyone re-indexing the chain then gets the same sequence. Sum several events only under a labelled total, for example "IMMUNITY deployed Y ETH this epoch".
  • IMMUNITY deployment comes only from MassAdded with kind 2. immunityActive() is pressure, a present-tense view; it describes no deployment, past or future.
  • PlacementSkipped is not Mass. Nothing was committed and no reserve, cap or budget was used (GeneHook.sol:142-144, 619-621).

#What each log contributes

Log Journal content Derived totals
Genesis The genome hash the hook was built with
Launched Pool opened; genesis band ticks, liquidity, GENE seeded, dust burned launchBlock = this log's block (GeneHook.sol:265, 272)
MassAdded kind 0 The genesis position (registry index 0) massLiquidity += liquidity (GeneHook.sol:432)
MassAdded kinds 1, 2, 4 ETH bid Mass: METABOLISM, IMMUNITY, churn tier massLiquidity += liquidity; ethCommitted += amount (GeneHook.sol:623-626, 665)
MassAdded kind 3 GENE ask Mass massLiquidity += liquidity; geneCommitted += amount (GeneHook.sol:623-626, 665)
EpochClosed The closed epoch's stored record, net curve GENE, block-start extremes Record for that epoch (GeneHook.sol:749-757)
FlowBudgets Net-flow budget created for the next epoch (GeneHook.sol:866-872)
GeneTransition Observation start or reset; expression with its evidence hash Gene state; EXPRESSIBLE is transient (GeneHook.sol:937-947)
FeesHarvested Fees collected by metabolize()'s harvest pokes. Fees collected when a placement tops up an existing band have no log, so this is not the call's total (GeneHook.sol:554-559, 688)
Regenerated Buyback: ETH spent, GENE burned, limit tick

#Epoch records

EpochClosed.record is the same struct the hook stores for that epoch (GeneHook.sol:749-757). An epoch that passed with no touch has no EpochClosed log and no stored record; it reads as (0, 0, 0, 0, false) (GeneHook.sol:880). Encode it that way when you recompute an evidence hash over a range that contains it.

#Position registry

The hook registers a range the first time it adds liquidity there, before emitting MassAdded for it (GeneHook.sol:431-433, 623-626, 664, 699-705). The registry order is therefore the order in which each (tickLower, tickUpper) pair first appears in MassAdded logs, with the genesis band at index 0. Each registry entry's liquidity is the sum of liquidity over its MassAdded logs, because protocol positions only grow (GeneHook.sol:594-608).

#Rebuild with viem

// journal.mjs   (Node 18+, npm install viem); uses poll() and the event ABI from index.mjs
import { formatEther } from "viem";
import { poll } from "./index.mjs";

const KIND = ["genesis", "METABOLISM bid", "IMMUNITY bid", "ask", "churn-tier bid"];
const GENE = ["GENESIS", "METABOLISM", "REGENERATION", "IMMUNITY"];

export function rebuild(logs) {
  const records = new Map(); // epoch -> EpochRecord
  const ranges = new Map(); // "lower:upper" -> liquidity, in first-seen order
  const journal = [];
  let massLiquidity = 0n, ethCommitted = 0n, geneCommitted = 0n, launchBlock = null;

  for (const l of logs) {
    const a = l.args;
    const at = { block: l.blockNumber, logIndex: l.logIndex, tx: l.transactionHash };
    switch (l.eventName) {
      case "Launched":
        launchBlock = l.blockNumber;
        journal.push({ ...at, text: `Launched: genesis band ${a.tickLower}..${a.tickUpper}, ${formatEther(a.geneSeeded)} GENE seeded` });
        break;
      case "MassAdded": {
        const k = Number(a.kind), key = `${a.tickLower}:${a.tickUpper}`;
        ranges.set(key, (ranges.get(key) ?? 0n) + a.liquidity);
        massLiquidity += a.liquidity;
        if (k === 1 || k === 2 || k === 4) ethCommitted += a.amount;
        if (k === 3) geneCommitted += a.amount;
        if (k === 2) journal.push({ ...at, text: `IMMUNITY DEPLOYED ${formatEther(a.amount)} ETH OF PERMANENT MASS` });
        else if (k !== 0) journal.push({ ...at, text: `${KIND[k]}: ticks ${a.tickLower}..${a.tickUpper}, amount ${k === 3 ? formatEther(a.amount) + " GENE" : formatEther(a.amount) + " ETH"}` });
        break;
      }
      case "EpochClosed":
        records.set(Number(a.epoch), a.record);
        break;
      case "GeneTransition":
        if (Number(a.to) === 3) journal.push({ ...at, text: `${GENE[Number(a.gene)]} expressed at epoch ${a.epoch}, evidence ${a.evidenceHash}` });
        break;
      case "Regenerated":
        journal.push({ ...at, text: `REGENERATION: ${formatEther(a.ethSpent)} ETH spent, ${formatEther(a.geneBurned)} GENE burned` });
        break;
      default:
        break;
    }
  }
  return { journal, records, ranges, massLiquidity, ethCommitted, geneCommitted, launchBlock };
}

const { finalLogs } = await poll();
const r = rebuild(finalLogs);
console.log(r.journal.map((e, i) => `${i + 1}. block ${e.block}: ${e.text}`).join("\n"));

#Check the rebuild against the views

Read the views at the last finalized block you indexed and compare (see read state):

Rebuilt from logs Must equal Source
massLiquidity GeneHook.massLiquidity() GeneHook.sol:186, 432, 665
ethCommitted GeneHook.ethCommitted() GeneHook.sol:184, 624
geneCommitted GeneHook.geneCommitted() GeneHook.sol:185, 625
range list, in order GeneLens.positionCount and positionAt(i) GeneLens.sol:89-98
liquidity per range StateView getPositionInfo(poolId, HOOK_ADDRESS, lower, upper, bytes32(0)) GeneHook.sol:604
record of each closed epoch GeneLens.epochRecord(hook, e) GeneLens.sol:58-60

A mismatch means a missing or extra log in the index. Report it; do not hide or adjust it.

#Values logs cannot recover

Reachable depth (GeneLens.report), immunityActive() and gene progress counts are views; no GeneHook log records them. Reachable depth is pure math over the positions, their liquidity and the price (GeneLens.sol:229-259), so it can be recomputed for a past block if you also index the pool's PoolManager Swap logs. immunityActive() and gene progress counts depend on when evolution ran; reading them for a past block needs an archive node. A history rebuilt from logs alone marks them as not recorded rather than estimating them. Any page that shows depth, IMMUNITY or bids links the known limitations.

Next: verify evidence hashes.

Sources (15)
  • src/GeneHook.sol:75-81
  • src/GeneHook.sol:138-145
  • src/GeneHook.sol:184-186
  • src/GeneHook.sol:420-435
  • src/GeneHook.sol:479-552
  • src/GeneHook.sol:589
  • src/GeneHook.sol:610-627
  • src/GeneHook.sol:654-666
  • src/GeneHook.sol:699-705
  • src/GeneHook.sol:747-757
  • src/GeneHook.sol:880
  • src/GeneHook.sol:937-947
  • src/GeneLens.sol:89-98
  • src/GeneHook.sol:554-559
  • src/GeneLens.sol:229-259

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