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

Documentation · Learn

Epochs

How GENE divides time into epochs, what it records about each one, and when those records are written.

GENE keeps time in blocks. An epoch (a GENE term) is a fixed number of blocks counted from the launch block (parameters): epoch e is (block.number - launchBlock) / EPOCH_BLOCKS (GeneHook.sol:747; GeneLens.sol:100-105). Epochs are the unit in which the protocol records evidence, creates budgets and evaluates genes.

#The first touch of a block

The hook does its bookkeeping once per block, on the block's first touch: the first swap of the block, or the first call to metabolize(), regenerate() or evolve() (GeneHook.sol:364, :728-730). At that moment it reads the pool's current tick. Nothing moves a v4 price except a swap, and every swap of this pool runs the hook except the hook's own buyback, which regenerate() makes after it has already touched the block (Hooks.sol:253, :293; GeneHook.sol:301). So that tick is the price the previous touched block left: the block-start tick (GeneHook.sol:732-733). A flash loan inside a block cannot change it.

METABOLISM and churn bids are positioned from the block-start all-time low (GeneHook.sol:484, :516-518). Spot can move past that reference later in the same block, or stay parked in empty ticks across blocks. Every ETH placement must be single-sided: if it would need GENE, the whole metabolize() call reverts InvariantBroken (GeneHook.sol:655-660). Audit scope note: metabolize() called while spot sits at or beyond the bid band (see Security).

On each first touch the hook:

  1. finalizes the previous touched block: if its ETH volume reached the active-block threshold (parameters), it counts as an active block and adds its price move, clamped (parameters), to pathTicks (GeneHook.sol:738-745);
  2. if the epoch number has changed, closes the open epoch (below);
  3. updates the epoch's price extremes and the all-time anchors (GeneHook.sol:766-774). See anchors.

Every swap, first or not, also updates the open epoch's accumulators in afterSwap: revenue on buys, net GENE leaving the curve, and block volume (GeneHook.sol:107-117, :368-390).

#Closing an epoch

When a first touch lands in a new epoch, the hook writes a permanent EpochRecord for the epoch it was tracking. The record fits in one storage slot and holds exactly what gene conditions read: revenue, active blocks, pathTicks, cumulative ETH committed to bid Mass, and a closed flag (GeneHook.sol:72-81, :748-756). It emits EpochClosed with the record, the epoch's net GENE flow and its price extremes (GeneHook.sol:133, :757), and, when its net flow funds one, creates the next epoch's net-flow budgets (GeneHook.sol:761, :838, :857).

Two consequences follow from closing at the first touch:

  • Only the last touched epoch gets a record. If a whole epoch passes with no touch at all, it has no stored record and reads as all-zero activity (GeneHook.sol:748-757, :880). Audit scope note: how the net flow of an epoch closed that late is treated (see Security).
  • Views lag until the first touch. Epoch-dependent views describe the last touched epoch until the new epoch's first touch (GeneHook.sol:728-780). An interface should compute the epoch from launchBlock and the block number and label budgets as pending.

#Evolving

Closing an epoch records it; evolving folds it into the gene state machines. evolve(maxEpochs) processes closed epochs from a cursor, up to the given count (GeneHook.sol:311-329). Anyone may call it after launch, and it has no cooldown.

metabolize() also evolves up to a fixed number of pending epochs (parameters) before it spends anything, and it places ETH only when evolution has fully caught up (GeneHook.sol:288-289, :465-469). This keeps gene states and stress signals final for every closed epoch before ETH moves.

Operational consequences:

  • an epoch with no flow places no ETH in the next epoch;
  • with more pending epochs than metabolize() folds, ETH placement waits for evolve();
  • the first metabolize() needs a prior evolve(), because METABOLISM must be expressed first.

See genes for what the processed records decide.

Sources (16)
  • src/Genome.sol:22-24
  • src/Genome.sol:50-54
  • src/GeneHook.sol:72-81
  • src/GeneHook.sol:97-117
  • src/GeneHook.sol:133
  • src/GeneHook.sol:281-329
  • src/GeneHook.sol:355-390
  • src/GeneHook.sol:465-471
  • src/GeneHook.sol:484
  • src/GeneHook.sol:516-518
  • src/GeneHook.sol:655-660
  • src/GeneHook.sol:728-780
  • src/GeneHook.sol:879-894
  • src/GeneLens.sol:100-105
  • lib/v4-core/src/libraries/Hooks.sol:253
  • lib/v4-core/src/libraries/Hooks.sol:293

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