/** * Audit what is actually onchain — the check to run before locking anything, * and the one to point a sceptical collector at. * * FROM_ID=1 TO_ID=100 npx hardhat run scripts/inspect.js --network mainnet * * For every token in the range it reports the storage key, how many bytes are * under it, whether those bytes match the local AVIF exactly, whether the * metadata matches, and — for tokens already redeemed — decodes the live * tokenURI the creator serves and checks the image inside it against the same * file. * * Env: * EXTENSION / STORAGE / CREATOR default: deployments/.json * FROM_ID / TO_ID token range, default 1..100 * DUMP_DIR write each decoded tokenURI image here */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { addressFor, avifPath, base64Chunks, connect, creatorAt, decodeTokenURI, keyName, main, metaPath, rawChunks, readBlobBytes, readDeployment, } from "./_lib.js"; main(async () => { const { ethers, net } = await connect(); const ext = await ethers.getContractAt( "ColorgasmRedeemExtension", addressFor("extension", net), ); const blobs = await ethers.getContractAt("ColorgasmStorage", addressFor("storage", net)); const creator = creatorAt(ethers, addressFor("creator", net)); const renderer = await ethers.getContractAt("ColorgasmRendererV3", await ext.renderer()); const preEncoded = await renderer.preEncoded(); const from = Number(process.env.FROM_ID ?? 1); const to = Number(process.env.TO_ID ?? 100); const dumpDir = process.env.DUMP_DIR; if (dumpDir) mkdirSync(dumpDir, { recursive: true }); console.log( `extension ${(await ext.storageLocked()) ? "LOCKED" : "unlocked"} ` + `renderer ${await ext.renderer()}${(await ext.rendererLocked()) ? " (LOCKED)" : ""} ` + `${preEncoded ? "pre-encoded" : "raw"}, ${(await renderer.base64Json()) ? "base64" : "utf8"} JSON ` + `redeemed ${await ext.redeemedCount()}\n`, ); console.log(" id key stored sealed image metadata redeemed tokenURI"); let pointed = 0; let bytes = 0n; const problems = []; for (let id = from; id <= to; id++) { const key = await ext.artworkKeyOf(id); if (key === ethers.ZeroHash) { console.log(` ${pad(id)} —`); continue; } pointed++; const [size, sealed, onchainMeta, redeemedId] = await Promise.all([ blobs.sizeOf(key), blobs.isSealed(key), ext.metadataOf(id), ext.redeemedTokenOf(id), ]); bytes += size; if (!sealed) problems.push(`#${id} "${keyName(key)}" is not sealed — its bytes can still change`); // compare the stored bytes against the local file const imgPath = avifPath(id); let imageState = "no file"; if (existsSync(imgPath)) { const image = readFileSync(imgPath); const want = Buffer.concat( (preEncoded ? base64Chunks(image) : rawChunks(image)).map((c) => Buffer.from(c.slice(2), "hex"), ), ); const stored = await readBlobBytes(blobs, ethers.provider, key); imageState = stored.equals(want) ? "exact" : `MISMATCH ${stored.length}/${want.length}`; if (!stored.equals(want)) problems.push(`#${id} stored bytes differ from ${imgPath}`); } const mPath = metaPath(id); let metaState = "no file"; if (existsSync(mPath)) { const localMeta = "0x" + readFileSync(mPath).toString("hex"); metaState = onchainMeta === localMeta ? "exact" : "MISMATCH"; if (onchainMeta !== localMeta) problems.push(`#${id} metadata differs from ${mPath}`); } // the live tokenURI, for tokens that have been redeemed let uriState = "—"; if (redeemedId !== 0n) { try { const { json, imageBytes } = decodeTokenURI(await creator.tokenURI(redeemedId)); const local = existsSync(imgPath) ? readFileSync(imgPath) : null; uriState = local === null ? `${json.name} (${imageBytes.length} B)` : imageBytes.equals(local) ? `${json.name} ✓` : `${json.name} IMAGE MISMATCH`; if (local !== null && !imageBytes.equals(local)) { problems.push(`#${id} rendered tokenURI image differs from ${imgPath}`); } if (dumpDir) writeFileSync(join(dumpDir, `${id}.avif`), imageBytes); } catch (e) { uriState = `ERROR ${e.message}`; problems.push(`#${id} tokenURI: ${e.message}`); } } console.log( ` ${pad(id)} ${keyName(key).padEnd(15)} ${String(size).padStart(7)} ` + `${(sealed ? "yes" : "NO").padEnd(6)} ${imageState.padEnd(9)} ${metaState.padEnd(9)} ` + `${String(redeemedId === 0n ? "no" : `#${redeemedId}`).padEnd(9)} ${uriState}`, ); } const d = readDeployment(net); console.log( `\n${pointed} token(s) pointed at artwork, ${bytes} bytes stored` + (d.storage ? `\nstorage ${d.storage}` : "") + (dumpDir ? `\ndecoded images written to ${dumpDir}/` : ""), ); if (problems.length > 0) { console.log(`\n${problems.length} problem(s):`); for (const p of problems) console.log(` ✗ ${p}`); process.exitCode = 1; } else if (pointed > 0) { console.log("everything onchain matches the local files"); } }); const pad = (n) => String(n).padStart(3);