import assert from "node:assert/strict"; /** * Test helpers. The byte-level pieces (chunking, keys, decoding tokenURIs) come * from scripts/_lib.js on purpose: the tests then exercise the exact code the * upload/deploy/redeem scripts run against Sepolia and mainnet. */ export { CHUNK, CLGSM_MAINNET as CLGSM, CREATOR_ABI, artworkKey, base64Chunks, creatorAt, decodeTokenURI, deployCreator, rawChunks, toChunks, } from "../scripts/_lib.js"; import { artworkKey, base64Chunks, rawChunks } from "../scripts/_lib.js"; /** * Assert two byte sequences are identical, whatever container they arrive in * (Buffer from eth_getCode, Uint8Array from a fixture, hex from a view call). */ export function sameBytes(actual, expected, message) { const a = Buffer.from(typeof actual === "string" ? actual.replace(/^0x/, "") : actual, "hex"); const b = Buffer.from(typeof expected === "string" ? expected.replace(/^0x/, "") : expected, "hex"); assert.equal( a.equals(b), true, message ?? `bytes differ: ${a.length} B actual vs ${b.length} B expected`, ); } /** Deterministic pseudo-random bytes — stands in for AVIF data. */ export function fakeImage(len, seed = 1) { const out = Buffer.alloc(len); let x = seed >>> 0; for (let i = 0; i < len; i++) { x = (x * 1664525 + 1013904223) >>> 0; out[i] = x & 0xff; } return out; } /** * A metadata fragment in the shape scripts/fetch-metadata.js produces: the * token's original JSON minus the image fields, without surrounding braces. */ export function fragment(id, extra = {}) { return JSON.stringify({ name: `COLORGASM #${String(id).padStart(3, "0")}`, created_by: "FelixFelixFelix", description: "Databent colour field, stored onchain.", attributes: [{ trait_type: "Series", value: "COLORGASM" }], ...extra, }).slice(1, -1); } /** * Deploy storage + extension + renderer and wire them — the same three steps * scripts/deploy.js performs onchain. */ export async function deployStack( ethers, creator, { firstId = 1, editionSize = 100, preEncoded = true } = {}, ) { const blobs = await ethers.deployContract("ColorgasmStorage"); await blobs.waitForDeployment(); const ext = await ethers.deployContract("ColorgasmRedeemExtension", [ creator, firstId, editionSize, ]); await ext.waitForDeployment(); const renderer = await ethers.deployContract("ColorgasmRendererV3", [ await ext.getAddress(), await blobs.getAddress(), preEncoded, ]); await renderer.waitForDeployment(); await (await ext.setBlobStorage(await blobs.getAddress())).wait(); await (await ext.setRenderer(await renderer.getAddress())).wait(); return { blobs, ext, renderer }; } /** * Upload an artwork the way the offchain script does: one chunk per * transaction, then seal. Returns the storage key and the gas it cost. */ export async function upload(blobs, id, image, { preEncoded = true, seal = true } = {}) { const key = artworkKey(id); const chunks = preEncoded ? base64Chunks(image) : rawChunks(image); let gas = 0n; for (const chunk of chunks) { gas += (await (await blobs.append(key, chunk)).wait()).gasUsed; } if (seal) gas += (await (await blobs.seal(key)).wait()).gasUsed; return { key, chunks: chunks.length, gas }; }