Documentation · Integrate
Verify evidence hashes
How to recompute the evidence hash of every expressed gene from stored epoch records and confirm it against GeneHook's storage, its events and GeneLens.
Every gene expression stores an evidence hash: a commitment to the exact stored epoch records of its evidence range (for IMMUNITY, the whole observed window, including epochs that were not stressed). Anyone can recompute it from storage or from logs and compare. The inputs below come from the deployed hook, listed on addresses.
#What is hashed
| Gene | Evidence hash | Source |
|---|---|---|
| GENESIS (0) | keccak256(abi.encode(uint8 0, bytes32 genomeHash, uint256 launchBlock)) |
GeneHook.sol:270 |
| METABOLISM (1), REGENERATION (2), IMMUNITY (3) | keccak256(abi.encode(uint8 g, uint32 first, uint32 last, EpochRecord[] records)), where records are the stored records of epochs first to last inclusive |
GeneHook.sol:975-982 |
EpochRecordis the tuple(uint96 revenue, uint32 activeBlocks, uint32 pathTicks, uint88 ethCommitted, bool closed)(GeneHook.sol:75-81).firstandlastare stored with the hash asfirstEvidenceEpochandexpressedEpoch(GeneHook.sol:941-944).- For METABOLISM and REGENERATION,
first = last + 1 - observe: the consecutive epochs that met the rule (GeneHook.sol:911). For IMMUNITY,first = last + 1 - count, wherecountis the number of epochs observed, at most the window length (GeneHook.sol:924, 933). - An epoch in the range that was never touched has no stored record. It encodes as
(0, 0, 0, 0, false)(GeneHook.sol:880, 977-980). This can occur inside an IMMUNITY window.
The hash covers stored records only, so an expression is reconstructable from storage (invariant EVO2).
#Three places to read the stored hash
GeneLens.geneStatus(hook, g).evidenceHash(GeneLens.sol:46-56), or the raw slot11 + 2g(GeneLens.sol:47, 55).- The
evidenceHashfield of bothGeneTransitionlogs emitted at expression (GeneHook.sol:945-946). GeneLens.evidenceHashFor(hook, g, first, last), which recomputes it from storage (GeneLens.sol:133-147).
#Recompute with viem
// verify-evidence.mjs (Node 18+, npm install viem)
// env: RPC_URL, HOOK_ADDRESS (after deployment), LENS_ARTIFACT (frozen GeneLens build; see Read state)
import { createPublicClient, http, parseAbi, encodeAbiParameters, keccak256 } from "viem";
import { readFileSync } from "node:fs";
const { RPC_URL, HOOK_ADDRESS } = process.env;
const LENS_CREATION_CODE = JSON.parse(readFileSync(process.env.LENS_ARTIFACT ?? "out/GeneLens.sol/GeneLens.json", "utf8")).bytecode.object;
const lensAbi = parseAbi([
"function launchBlock(address hook) view returns (uint40)",
"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 evidenceHashFor(address hook, uint8 g, uint32 first, uint32 last) view returns (bytes32)",
]);
const hookAbi = parseAbi(["function genomeHash() view returns (bytes32)"]);
const RECORDS = {
type: "tuple[]",
components: [
{ name: "revenue", type: "uint96" },
{ name: "activeBlocks", type: "uint32" },
{ name: "pathTicks", type: "uint32" },
{ name: "ethCommitted", type: "uint88" },
{ name: "closed", type: "bool" },
],
};
export const genesisEvidence = (genomeHash, launchBlock) =>
keccak256(encodeAbiParameters([{ type: "uint8" }, { type: "bytes32" }, { type: "uint256" }], [0, genomeHash, BigInt(launchBlock)]));
export const geneEvidence = (g, first, last, records) =>
keccak256(encodeAbiParameters([{ type: "uint8" }, { type: "uint32" }, { type: "uint32" }, RECORDS], [g, first, last, records]));
const client = createPublicClient({ transport: http(RPC_URL) });
const blockNumber = await client.getBlockNumber();
const lens = (functionName, args) =>
client.readContract({ code: LENS_CREATION_CODE, abi: lensAbi, functionName, args: [HOOK_ADDRESS, ...args], blockNumber });
const genomeHash = await client.readContract({ address: HOOK_ADDRESS, abi: hookAbi, functionName: "genomeHash", blockNumber });
const launchBlock = await lens("launchBlock", []);
const s0 = await lens("geneStatus", [0]);
console.log("GENESIS", genesisEvidence(genomeHash, launchBlock) === s0.evidenceHash ? "matches" : "DOES NOT MATCH");
for (const g of [1, 2, 3]) {
const s = await lens("geneStatus", [g]);
if (s.state !== 3) { console.log(g, "not expressed"); continue; }
const first = s.firstEvidenceEpoch, last = s.expressedEpoch;
const records = [];
for (let e = first; e <= last; e++) records.push(await lens("epochRecord", [e]));
const mine = geneEvidence(g, first, last, records);
const lensHash = await lens("evidenceHashFor", [g, first, last]);
console.log(g, mine === s.evidenceHash && lensHash === s.evidenceHash ? "matches" : "DOES NOT MATCH", { first, last, mine });
}To check the hash without trusting storage reads, take the records from the finalized EpochClosed logs instead (see rebuild the journal), encode untouched epochs as zero records, and compare the result with the evidenceHash in the GeneTransition log.
#Recompute GENESIS evidence with cast
# after launch; LAUNCH_BLOCK is the block of the Launched log
GH=$(cast call $HOOK_ADDRESS "genomeHash()(bytes32)" --rpc-url $RPC_URL)
cast keccak $(cast abi-encode "f(uint8,bytes32,uint256)" 0 $GH $LAUNCH_BLOCK)
# must equal the evidenceHash of the GENESIS GeneTransition log, and the word in hook storage slot 11
cast storage $HOOK_ADDRESS 11 --rpc-url $RPC_URLThe genome hash itself is recomputed from the constants in verify the deployment. Gene rules: Genome.
Sources (7)
- src/GeneHook.sol:72-90
- src/GeneHook.sol:268-271
- src/GeneHook.sol:879-947
- src/GeneHook.sol:975-982
- src/GeneLens.sol:46-60
- src/GeneLens.sol:133-147
- src/GeneLens.sol:262-273
Paths are relative to the GENE repository at tag rc4-audit-candidate (aca5fcd).