Job
Deploy part 3 of 3 of the IMD6900 Frens on-chain art, exactly as it is in the repository, in this order: FrenArtChunk5, FrenArtChunk6, FrenArtChunk7 (src/FrenArtChunks.sol, no constructor arguments), then FrenRenderer (src/FrenRenderer.sol) with seven address arguments in this order: 0xa92dAcfF6d6fcC218ADe20eD24857376BD8eBE81 (FrenArtChunk1, IMD launch #819), 0x9666A481e20F1dB59EEbD6c43D11Ae3505468c92 (FrenArtChunk2, #819), 0x71BdEB749b3ee428730eBB3E9b34B03A99D82356 (FrenArtChunk3, #838), …
Published · Contracts
- app
- FrenArtChunk5 0x8734632850a8dff2b18f49807ad321501a56906e · Ethereum mainnet
- app
- FrenArtChunk6 0x11d5564e9d41ccbb4fb92df2d719282c90c959fd · Ethereum mainnet
- app
- FrenArtChunk7 0xcfc3c7d69aea5a4f8dea3105b66cf51cb63a12c0 · Ethereum mainnet
- app
- FrenRenderer 0xc92495adc59d711a89a91cc8d90d8f7d92d075ba · Ethereum mainnet
- github
- identity-md-launches/launch-868-frenartchunk5-frenartchunk6-frenartchunk
Work
- posted15 minto the first attempt
- reviewed
#1905Audit imported codeClaude7 findings · 1 low
The review is complete. The findings are in
.imd-findings.jsonat the repository root, no source files were changed, and my scratch tests were removed.Verdict: no defects in the contracts themselves. FrenRenderer and the chunks behave as the brief describes. I found one low-severity test problem and several informational notes.
What I verified
- The offline suite passes, 8 of 8 tests.
- Chunks 1 to 4 on Ethereum mainnet at the brief's addresses have exactly the code sizes and keccak256 hashes that FrenArtIndex stores. The mainnet fork test passes against a public RPC.
- A scratch test compared
canvasandpendingURIagainst the vendored reference renderer across every one of the 69 layers, every trait value and 40 seeds. All output was byte for byte identical. - FrenRenderer's runtime passes the protected forbidden-opcode scan. It has no owner, no storage writes, no state-changing functions, no proxy, initializer, DELEGATECALL, CALLCODE or SELFDESTRUCT.
- The constructor only stores addresses and deploys on a chain without the earlier chunks. Every read goes through the code-hash check, and the framed-read arithmetic for chunks 4 and 7 matches the generator.
Findings written
- Low: test_LaunchesFitTransactions is vacuous under forge 1.8.3. It logs about 1.3M gas for launch 3, while a local deployment costs about 14.2M and the two mainnet launches used 11.3M and 10.6M. Launch 3 still fits the 16.8M cap at roughly 85 to 87 percent, but the test cannot catch an overflow of it.
- Info: pendingURI costs about 25.5M gas per eth_call and tokenURI 9 to 10.3M, which some RPC endpoints cap below.
- Info: the renderer's NatSpec and the CHUNK_SIZES comment describe checks the code no longer performs, and the derive script regenerates the stale text.
- Info: the repo's admission-scan test skips FrenRenderer, which the protected harness does scan.
- Info: the README names the IMD6900Frens contract at an address with no code on mainnet, Base or Arbitrum, so the consumer interface could not be verified on chain.
- Info: a coverage statement listing what was read, what was verified, and what was unreachable: the frens contract, the launch policy gas ceiling, and compiler builds other than solc 0.8.26.
ran onclaude · claude-fable-5-1 · 36 turns · 14m 57s · 386 in · 30.4K out · 966.6K cachedsubmissionefe7bcf9cf450bffb437b93c0b2feb44293750815bc13423c1fe830c0b3a58dbdevice866bf60e68b081d923404b6a8eda4a10747ad98392d84d058002e0efbf408d9astarted from18a3cc95c10d3139f185186013ce334aca02e274bundlenonechanged · 0 filesnothingtest_LaunchesFitTransactions measures ~1/10 of the real launch gas, so its EIP-7825 guard is vacuoustest/FrenRenderer.t.sol:147
Run
forge test --match-test test_LaunchesFitTransactions -vv(also with --isolate): logs show 'launch 3 gas (with calldata): 1310335' and the test passes.Compare with
cast receipt 0x7b6503966cc85ffbf267a50625e47392554d6a30d47fa5bff884e0bc7110012d gasUsedon Ethereum = 11290222 for launch 1, which the same test reports as 1000622.Expected: the measured total approximates the on-chain cost (~11.3M, ~10.6M, ~14.3M).
Actual: ~1.0M, ~0.96M, ~1.3M, so a launch far above the cap would still pass.
pendingURI costs ~25.5M gas per eth_call; tokenURI ~9-10.3Msrc/FrenRenderer.sol:76
Measured in a scratch test on the chunk renderer: pendingURI(1) = 25,486,903 gas (reference renderer: 25,307,608), tokenURI(1, 0xbd59c, 42) = 9,058,559 gas (reference: 9,021,984); the repo's own test logs tokenURI = 10,318,372 for combo 0x6a2a4d. Both functions are what the frens contract will return from its tokenURI, so marketplaces and wallets fetch them through eth_call.
Several public RPC endpoints cap eth_call gas at 25-30M, so pendingURI (and tokenURI on providers with lower caps) can fail to render there even though nothing is wrong on chain. This matches the launch renderer byte for byte (test_SameAsTheLaunchRenderer) and is not a regression introduced by the chunk packing; it is reported so the team can confirm their metadata path's RPC gas limits before pointing the frens here.
Deploy FrenArtChunk1..7 and FrenRenderer(c1..c7) locally; measure
g = gasleft(); r.pendingURI(1); g - gasleft()-> 25,486,903.Expected by users of a 25M-gas eth_call endpoint: a data URI.
Actual: out-of-gas revert on that endpoint; the same call succeeds with a 30M+ gas allowance.
Renderer NatSpec says chunks are checked 'when this is deployed'; the constructor checks nothingsrc/FrenRenderer.sol:10
After commit 18a3cc9 the constructor only stores the seven addresses and every read checks the chunk's code hash in _entry (line 332). The contract-level NatSpec still describes the old behaviour, and script/art/derive_renderer.py (line 25) regenerates the same sentence, so re-running the script keeps the stale text.
A reader (or the adapter) relying on the header would expect a wrong-address deployment to fail at construction; it does not: FrenRenderer(address(1), ..., address(7)) deploys fine and only reverts BadArt on the first read.
new FrenRenderer(address(1), address(2), address(3), address(4), address(5), address(6), address(7)) succeeds (see test_DeploysWithoutTheEarlierChunks, which deploys against mainnet addresses that do not exist on the test chain).
Expected per the NatSpec: constructor reverts.
Actual: deploys;
tokenURI(1, 0, 0)then reverts BadArt.FrenArtIndex.CHUNK_SIZES is unused; its comment claims the renderer checks itsrc/FrenArtIndex.sol:22
FrenRenderer reads INDEX, CHUNK_HASHES, FRAMED, TABLES, FACE_TABLE, FACE_LAYERS and SHADOW; nothing reads CHUNK_SIZES (grep over src/ finds only its definition). The code-hash check in _entry subsumes a size check, so this is not a security issue, but the generated comment is misleading documentation of what the renderer enforces.
Values for reference: 0x5dc1=24001, 0x5e4b=24139, 0x5bb7=23479, 0x52c3=21187, 0x4bcb=19403, 0x539d=21405, 0x22f0=8944, which do match the mainnet code sizes of chunks 1-4 and the local chunks 5-7.
grep -n CHUNK_SIZES src/*.solreturns only src/FrenArtIndex.sol:23.Expected per the comment: a read path in FrenRenderer comparing p.code.length to the entry.
Actual: none; a chunk of the wrong size is rejected only through the codehash comparison at src/FrenRenderer.sol:332.
test_ChunksPassTheAdmissionScan does not scan FrenRenderer, which the admission scan also checkstest/FrenRenderer.t.sol:181
The protected harness (Contracts.protected.t.sol, test_applicationRuntimeIsPresentBoundedAndHasNoEscapeOpcodes) runs the forbidden-opcode scan over every application in the launch, including FrenRenderer's runtime, while the repo test only loops over the seven chunks.
FrenRenderer's runtime compiled with solc 0.8.26 / via-ir / 200 runs is 14,096 bytes and currently scans clean (its 8 raw 0xf4, 12 raw 0xf2 and 63 raw 0xff bytes all sit inside PUSH data), so the launch is not blocked today.
Because the renderer's bytes change with any compiler or settings change, and the scan's result depends on PUSH alignment, the repository has no test that would catch a future renderer build that the admission scan refuses, as happened with chunks 4 and 7 (launch #820).
Read out/FrenRenderer.sol/FrenRenderer.json deployedBytecode and run the protected scan: 0 hits at 14,096 bytes with solc 0.8.26. Running
forge test --match-test test_ChunksPassTheAdmissionScanscans only chunks[0..6]; replacing FrenRenderer with a build containing a stray 0xff at an instruction boundary would still pass this test and be parked by admission.README names IMD6900Frens at 0x69004fEd...fa79d 'on Ethereum', but no code exists thereREADME.md:25
On 2026-10-07 eth_getCode for 0x69004fEd3d8a34FFA952d15A128f74D8340fa79d returns 0x on Ethereum mainnet (nonce 0, balance 0), and also on Base and Arbitrum One. The consumer of this renderer, and therefore the exact interface it will be called through (tokenURI(uint256,uint24,uint256) and pendingURI(uint256), selectors 0x12b57c38 and 0x973c89a7), could not be verified against a deployed contract; only the vendored test/ref/FrenRendererRef.sol copy was available.
If the frens are deployed later at a different address or with a different renderer interface, this renderer cannot be adjusted (no owner, nothing configurable), so the team should confirm the frens contract's renderer interface before launch 3.
cast code --rpc-url https://ethereum-rpc.publicnode.com 0x69004fEd3d8a34FFA952d15A128f74D8340fa79d-> 0x;cast nonce-> 0.Expected per README: the IMD6900Frens contract code.
Actual: empty account.
Review coverage: what was read, verified and not reachablesrc/FrenRenderer.sol:25
n/a: coverage statement. Commands used:
forge test;MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com forge test --match-path test/OnChainChunks.fork.t.sol;cast code/cast keccakfor the four chunk addresses; a scratch Foundry test iterating every layer against FrenRendererRef (removed after the run).
- built
#501Adapt contract projectCodex7 files changed
afterAudit imported codeImplemented launch-3 preparation, preserving the art, index, argument order, and renderer behavior.
forge build: passed.forge test: 9 passed; 2 optional fork suites skipped.- Protected harness: passed.
- Local CREATE2 rehearsal: 14,179,144 gas, below the limit with a 5% reserve.
ADAPTATION.md documents every change, audit disposition, and remaining integration check.
ran oncodex · gpt-6-astra · 10 turns · 16m 21s · 92.7K in · 27.4K out · 3.1M cachedsubmission83f58a003fe3d7213ac173fa3581c847503cec19196bfd4364303ce6e5d3a249deviceccf14607d159904bedce7f48a7cf427b6842048c6d5c0ecf39f170e4a137b04estarted from18a3cc95c10d3139f185186013ce334aca02e274bundle2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836f · 11 KBverifiedrebuilt and matched · verifier 0.1.0 ·changed · 7 filesADAPTATION.mdREADME.mdscript/art/derive_renderer.pyscript/check_launch_gas.pysrc/FrenRenderer.soltest/FrenRenderer.t.soltest/LaunchGasFactory.sol - reviewed
#528Audit permissionsClaudeno findings
afterAdapt contract projectThe review is complete and the findings file holds an empty list. Nothing in the assigned area produced a substantiated defect.
What I checked and verified
- Permission surface. The three chunk contracts and the renderer have no owner, no role, no initializer, no
receiveorfallback, nopayablefunction, nomsg.senderread, and no DELEGATECALL or SELFDESTRUCT. Every renderer function isvieworpure. The constructor only stores seven immutables and reads nothing, so the factory beingmsg.sendertraps nothing. There is no writer of any storage slot after deployment, so the inconsistent-guard, escalation and unprotected-init checks from the Access Control guide have no target. - Trust boundary on the constructor inputs. The only trust the renderer places is in the seven addresses, and
_entryatsrc/FrenRenderer.sol:354rejects any chunk whose full code hash is not the pinned one before copying a byte. A wrong, empty, swapped or copied-and-altered chunk revertsBadArton every read. The local suite confirms this for swapped and stranger addresses and for a fresh chain where chunks 1–4 are absent. - The four static mainnet addresses. I fetched the code at all four from a public Ethereum RPC and hashed it. All four hashes match
FrenArtIndex.CHUNK_HASHESbyte for byte, with code sizes 24001, 24139, 23479 and 21187. I also ran the fork test against mainnet and it passed, so the renderer deployed with these arguments draws the reference art. - Asymmetry. The paired surfaces are revealed vs pending rendering, framed vs unframed chunk reads, and
canvasvs_frenslot computation. Validation sits incanvasand_frenbefore any index read, and the unrevealed path generates only in-range combos. Both read branches bound their copy by the pinned hash and a length check. The byte-for-byte reference comparison tests pass for both URI paths. - Static analysis leads. The uninitialized locals are intentional zero starts, and the divide-before-multiply is the 32-to-33 frame alignment, which I checked satisfies
off % 32 + len <= frames * 32for every offset and length. None is a defect.
Coverage rows
Entry point Result Reason FrenArtChunk5/6/7 constructor holds no arguments, returns fixed bytes, no sender dependency FrenArtChunk5/6/7 runtime holds STOP at pc 0, PUSH32 frames in chunk 7 never execute, admission scan passes FrenRenderer constructor holds stores seven immutables only, no reads, no owner tokenURI, pendingURI, unrevealed holds view only, every art read hash-checked bmp, canvas, attributes holds view or pure, inputs bounded before index reads chunk1..7, faceLayers, shadow getters holds immutables and constants receive / fallback holds none exist, value calls revert Notes that are not findings. The length checks reverting
Missinginside_entryare unreachable once the hash matches, which is harmless redundancy. The roughly 25.5M gas cost ofpendingURIis already documented by the earlier audit and the README and is outside my area. The protected harness was not re-run here. Launch 3 has no manifest in the tree yet, so the$contractreferences could only be checked against the README recipe, which matches the brief.ran onclaude · claude-fable-5-1 · 27 turns · 5m 18s · 226 in · 14.2K out · 571.1K cachedsubmissionf0982afa903db0265f9a9986128f6d43b645863c239854a8c1a1a3edc9a3a7b0device45aa937328087de32ace0ccca4ca5ffecee6a239f16a12cf4e3fdd3ee3548623started from1b0fb658bfd4c739767314eb7f43f38f629d222ebundlenoneapplied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836fchanged · 0 filesnothing - Permission surface. The three chunk contracts and the renderer have no owner, no role, no initializer, no
- integrated
#1273ManifestCodex1 file changed
afterAdapt contract projectwrites tolaunch.jsonCreated launch.json with the exact deployment order and constructor arguments.
Validation and
forge buildpassed.forge test: 9 passed, 0 failed, 2 fork checks skipped.Only
launch.jsonchanged outside scratch artifacts.ran oncodex · gpt-6-astra · 4 turns · 3m 23s · 49.1K in · 3.5K out · 464.5K cachedsubmission050b5d8f2c18bbd7e8fd6cb1ce52fea5d02538b23ab4c88a15cf39f843411a06devicee0f2f45a1b346346273ed3d0f0cdd44484475e2cfe54387a89507ffb78254d1fstarted from1b0fb658bfd4c739767314eb7f43f38f629d222ebundle89853bc1fc213696170861f28a0e9ddbd7eeb73318d5bcd804d1d71056cd9f6b · 11 KBverifiedrebuilt and matched · verifier 0.1.0 ·applied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836fchanged · 1 filelaunch.json - reviewed
#1464Audit mathClaude1 finding
afterAdapt contract projectThe review is complete. The findings file is written and valid, the scratch tests are removed, and the tree has no stray files (the findings file is ignored by a global rule, which is why plain status shows nothing).
Outcome: the math in the renderer holds. One info-level note was recorded, no defects of medium severity or above.
What I checked in my area (Math Precision, Boundary, Numerical Gap)
- Index decoding and framed reads. Decoded all 70 index entries. Every entry sits inside its chunk, and for the two framed chunks the computed frame bounds land exactly on the chunk code sizes. The divide-before-multiply slither flagged is correct block arithmetic.
- Drawing math. Clamping of runs to the canvas, signed offsets, and row bounds are safe. Every layer file is well-formed: rows sum exactly to their width, no zero-count runs, data consumed exactly. All ten backgrounds are 120x120 at the origin, so the 37-value window fully covers the 84-pixel canvas.
- Bitmap header and grey conversion. Header fields fit their byte widths. Luminance never exceeds 255 and the dimming cannot overflow.
- Byte-equality sweep. Every face of every character, every coat, hat, item and background, plus extreme seeds and token ids, matched the vendored reference renderer byte for byte.
- Out-of-art combos. Every out-of-range field value reverts with Missing as intended.
- Gas boundary. All 2222 token ids render pendingURI under 30M in isolated call frames. The worst is about 26.95M. An earlier 37M reading was my own harness's memory growth, not the renderer.
- External boundary. The four deployed mainnet chunks carry exactly the code hashes the index demands, and the repository's fork test passes against them.
Finding recorded (info): combo bit 23 is outside the trait layout and is silently ignored, so combos 0x3AAD91 and 0xBAAD91 render identically. The reference renderer behaves the same, so it is a shared property rather than a regression.
Coverage rows: constructor, tokenURI, pendingURI, unrevealed, attributes, bmp, canvas all
holds; the chunk constructorsholds(return the literal art, hashes match). The slither uninitialized-local and missing-zero-check leads are intentional and not defects. Nothing in my area was left unreached. The launch gas script needs python3, which this worker lacks, so I did not rerun it.ran onclaude · claude-fable-5-1 · 33 turns · 11m 9s · 610 in · 27.8K out · 1.8M cachedsubmission448ee4b4154ea3269273b43c97171aa1ea30c1b6cd1ce3b3351ca75eb3bab954device11e4593175677f7e935b9352132cab0da4061164eac65d7df7445bf5ee44abe9started from1b0fb658bfd4c739767314eb7f43f38f629d222ebundlenoneapplied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836fchanged · 0 filesnothingcombo bit 23 is outside the trait layout and silently ignored: two different combos render the same tokensrc/FrenRenderer.sol:239
Deploy the seven chunks and FrenRenderer(c1..c7).
Call canvas(0x3AAD91, 42) and canvas(0xBAAD91, 42) (second = first | 1<<23).
Expected if bit 23 were treated like any other out-of-layout value: the second call reverts Missing().
Actual: both calls succeed and return identical canvases; tokenURI and attributes for the two combos are byte-identical as well.
- tested
#1003Write foundry testsCodex3 files changed
afterAdapt contract projectwrites totesttest/**Added deployment, art-integrity, corruption, invalid-input, reference-rendering, and ETH invariant tests. All changes are under
test/.forge build: passed; existing lint warnings remain.forge test: 22 passed, 0 failed, 2 optional fork suites skipped.- Invariants: 6,144 calls, zero handler reverts.
No new defects reproduced.
ran oncodex · gpt-6-astra · 8 turns · 7m 56s · 103.2K in · 15.2K out · 2.2M cachedsubmission86cec4a167ab2d8fc2f138a762e2fb006d3fc7b9d18daa29af1d7dca2fbb02c5devicedbf9fa8b755f8ed6b037eae8fe91db54a601cf6d65d11d95cae5e2267d34015fstarted from1b0fb658bfd4c739767314eb7f43f38f629d222ebundlea20e0a566854c7f84fcaf4a4cf6217d979e9436b796edb718c71b4b1bc8b566a · 18 KBverifiedrebuilt and matched · verifier 0.1.0 ·applied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836fchanged · 3 filestest/FrenArtCalls.invariant.t.soltest/FrenArtIntegrity.t.soltest/FrenRenderer.t.sol - reviewed
#595Audit flowClaude2 findings · 1 low
afterAdapt contract projectThe findings file is in place and excluded by the harness's own exclude list, which is why the tree reads clean. Scratch files are gone. Nothing else is needed.
Review result
I found no correctness or safety defect in the launch-3 control flow. The findings file holds one low and one info item, both with reproductions.
What I verified
- Chunk hashes on mainnet. All four static constructor addresses hold code whose keccak matches the index's hash table exactly (sizes 24001, 24139, 23479, 21187 bytes), fetched through a public RPC. The renderer's four static arguments are therefore correct for Ethereum.
- Read path. The index decoding in
_entry, the framed-chunk unframing arithmetic, the exact end-of-code boundary for chunk 7's palette, and every index into the tables and face table are bounded by the combo checks incanvasand_fren. Outputs match the reference renderer byte for byte in the existing tests. - Fresh-chain deploy. The constructor only stores addresses. With the earlier chunks absent, every read reverts BadArt, as the existing test shows.
- Compiler independence. The hash check, admission scan and reference equality pass under solc 0.8.26, 0.8.30 and 0.8.37. The renderer embeds its constants as PUSH32 data, so the forbidden-opcode walk cannot land on hash bytes regardless of layout.
- Slither leads. Divide-before-multiply is the intended frame arithmetic. The uninitialized locals are deliberate zeros. The missing zero checks are required by the fresh-chain deploy. None is a finding.
Findings
- Low. pendingURI costs 23.9M to 26.95M gas across all 2222 ids, above the 2^24 per-transaction cap for every id and above 25M for 1990 ids. It can only be served by eth_call with a large allowance. The README documents the sample; this adds the full range and the transaction-cap consequence.
- Info. The public
attributesfunction returns empty trait names for combos outside the art whilecanvasreverts. The reference behaves the same.
Coverage
Entry point Status Reason constructor holds stores seven addresses, reads nothing tokenURI holds worst case 10.93M gas, guarded by canvas pendingURI finding 1 gas above the transaction cap unrevealed holds combos generated in range attributes finding 2 empty names, matches reference bmp, canvas holds all field ranges checked before reads chunk getters, faceLayers, shadow holds constants and immutables FrenArtChunk5..7 holds STOP then data, hashes match the index Not reached: the deployment service's real factory overhead against the gas ceiling, which only its simulation can confirm.
ran onclaude · claude-fable-5-1 · 39 turns · 20m 0s · 642 in · 44.5K out · 2.1M cachedsubmission8b1d6ba909a53dd374699f9c63f50ce76cd3e89f5e1c91ecb16c41baf58e49d8devicee57a8e639cccfbab7731b0b8e7cc4a933e04614f25ecd053e25dc56bcb7d2d29started from1b0fb658bfd4c739767314eb7f43f38f629d222ebundlenoneapplied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836fchanged · 0 filesnothingpendingURI cannot execute inside any Ethereum transaction: every token id needs more than the EIP-7825 2^24 gas cap, and 1990 of 2222 ids need more than 25Msrc/FrenRenderer.sol:76
attributes() returns empty trait names for combos outside the art instead of reverting like canvas()src/FrenRenderer.sol:127
attributes is a public pure entry point that maps each combo field to a name list with _name, which returns "" when the index is past the list end (line 178). canvas and _fren revert Missing() for the same out-of-range fields (lines 242, 249, 281), and tokenURI calls bmp before attributes, so through tokenURI the revert always wins.
A direct caller of attributes (an off-chain indexer or a future consumer composing its own JSON) gets syntactically valid metadata with empty values for Face 13-15, Character 3, Coat 3, Shirt 6-7, Hat 3 and Background 10-15, rather than an error. The vendored reference renderer (test/ref/FrenRendererRef.sol line 172) behaves identically, and the brief asks for the renderer exactly as it is, so this is recorded for the author's awareness, not as a required change.
Call r.attributes(uint24(13 << 2)) on a deployed FrenRenderer: it returns
[{"trait_type":"Character","value":"Cyborg Pepe"},{"trait_type":"Face","value":""},...]with an empty Face value.Call r.canvas(uint24(13 << 2), 0) with the same combo: it reverts Missing().
Expected (if the two entry points are meant to agree): attributes reverts or the consumer never passes such a combo; actual: silent empty string.
- reviewed
#29Audit economicsClaude2 findings
afterAdapt contract projectThe review is complete.
.imd-findings.jsonholds two informational findings and no defects of low severity or higher. No tracked file was changed, and the scratch tests were removed.Outcome. Within the Economic Security, Invariant and Flow Gap guides, launch 3 as specified is sound. The renderer holds no funds, has no owner, makes no external calls other than code reads, and fails closed on any chunk whose code hash differs. The main economic risk for this kind of contract is a permanently broken dependency, and I verified that it does not exist here.
What I verified
- Live mainnet chunks 1 to 4. Over a public RPC, the four static addresses in the brief have exactly the code hashes and sizes in
FrenArtIndex.CHUNK_HASHES. The mainnet fork test also passed against them. - Art integrity. I regenerated the index, framing flags, size and hash tables, and all seven chunk byte strings from the art data independently. All match the committed sources byte for byte. Every one of the 69 layers has well-formed run-length data, and every index entry lies inside its chunk.
- Exhaustive rendering. Every character, face, eye, coat, shirt, hat and item value draws. All 10 backgrounds draw at all 1,369 window offsets. All 2,222 token ids render pendingURI. No valid input reverts.
- Launch 3 itself. The protected admission test passed with the exact four initcodes and predicted CREATE2 addresses. The local Anvil receipt check used 14.18M gas against the 16.78M cap. A one-byte-different chunk is refused on every palette-bearing read.
- Slither leads. All eleven were checked and rejected. The divide-before-multiply is intentional frame arithmetic verified by the reference tests. The uninitialized locals are intentional zeros. Missing zero-checks only make a misdeployed renderer fail closed with BadArt.
The two informational findings
- pendingURI gas ceiling. The collection-wide maximum is 26.93M gas at token 1985, so it fails under a 26M eth_call cap while the README's sample id 1 passes. 1,965 of 2,222 ids exceed 25M. None exceed 30M. This refines the README's documented integration limit rather than changing code.
- Combo bit 23 is ignored. Combos 1779030 and 10167638 render identically with identical attributes, while other out-of-range trait values revert. The reference renderer behaves the same and byte equivalence is required, so this is a note for the consumer to mask bit 23 before storing combos.
Coverage. All entry points traced and holding: constructor, tokenURI, pendingURI, unrevealed, attributes, bmp, canvas, and the chunk constructors. Nothing in my area was left unreached. Not verifiable here: the deployer's real factory overhead and policy gas ceiling, and the not-yet-deployed consumer contract.
ran onclaude · claude-fable-5-1 · 41 turns · 21m 43s · 418 in · 36.3K out · 1.3M cachedsubmission92b18502b6cb762ec9c6fe5e7dfbd9e50060a7114badc0dd33ee286dee039983device56e50117311155be93c3c3b79293d6ba6217df4024bcf993400ea696be39d5a7started from1b0fb658bfd4c739767314eb7f43f38f629d222ebundlenoneapplied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836fchanged · 0 filesnothingpendingURI worst case across the collection is 26.93M gas (token 1985), above the documented 25.5M sample; 1,965 of 2,222 ids exceed 25Msrc/FrenRenderer.sol:76
Combo bit 23 is ignored: two distinct uint24 combos render the same fren and attributes, while every other out-of-range trait value is rejectedsrc/FrenRenderer.sol:283
With the renderer deployed as in test/FrenRenderer.t.sol setUp: uint24 c0 = 1779030 (character 2, face 5, eye 1, coat 1, shirt 1, hat 1, bg 6, item 3); uint24 c1 = c0 | (1 << 23) = 10167638. r.tokenURI(7, c0, 7) == r.tokenURI(7, c1, 7) (identical strings), r.canvas(c0, 7) == r.canvas(c1, 7), r.attributes(c0) == r.attributes(c1).
Expected if combos are meant to be canonical: c1 reverts Missing like bg = 10 does (r.canvas(10 << 15, 0) reverts).
Actual: both succeed with the same output.
- Live mainnet chunks 1 to 4. Over a public RPC, the four static addresses in the brief have exactly the code hashes and sizes in
- reviewed
#205Audit judgeClaude3 findings · 1 low
afterAdapt contract project, Write foundry tests, Manifest, Audit math, Audit permissions, Audit economics, Audit flowThe findings file is in place and excluded from git by the harness, the scratch test is removed, and the working tree has no other changes. The review is complete.
Outcome. Three findings, none above low, written to
.imd-findings.json. No critical or high finding exists, so no proof test was attached. The launch is sound as specified.Specialist findings, settled.
- pendingURI gas (low, merged from audit_flow and audit_economics). Reproduced with a fresh-frame meter over all 2222 ids on forge 1.8.3 / solc 0.8.26. Worst case is token 1985 at 26,929,208 gas, minimum 23,900,005, 1965 ids above 25M, none above 27M, and every id above the 2^24 transaction cap. The README's 25.5M sample is therefore not the ceiling. The only actionable item is the README's RPC guidance, since the output must stay byte-identical to the reference.
- Combo bit 23 ignored (info, merged from audit_math and audit_economics). Reproduced: tokenURI, canvas and attributes are identical for a combo with and without bit 23, while background 10 reverts. Shared with the reference renderer, so the consumer must mask it, not this launch.
- attributes() returns empty names (info, audit_flow). Reproduced: face index 13 yields an empty Face value while canvas reverts Missing. Same in the reference.
Static-analysis leads. The divide-before-multiply in the framed read is exact frame arithmetic. The uninitialized locals are intentional zero defaults. The missing zero checks are by design and covered by the all-zero-addresses test.
My own pass, with evidence.
- The four static addresses in the manifest resolve on Ethereum to code hashes matching FrenArtIndex exactly. I checked them with cast against a public RPC, and the mainnet fork test drew every reference bitmap through them.
- The local Anvil launch rehearsal reproduced 14,179,144 gas for the whole launch, under the 2^24 cap with a 2.6M margin. The deployer's own factory overhead and policy ceiling remain its check.
- The manifest matches the brief in contract order, argument order and checksummed addresses. All 22 offline tests pass.
- Entry points: constructor, tokenURI, pendingURI, unrevealed, bmp, canvas, attributes and the immutable getters all hold. The renderer has no storage, owner, payable path, external call or forbidden opcode, and every art read authenticates the full chunk code hash before copying. The framed read of chunk 7 is bounds-exact by construction and verified by the palette test at an unaligned offset.
Not verified. The deployment service's exact gas ceiling and the intended consumer contract, which the README already marks as unverified.
ran onclaude · claude-fable-5-1 · 32 turns · 10m 3s · 290 in · 24.4K out · 844.5K cachedsubmissionaa6a4c5f17bbafda860c9976bf7e4e2a3a36814a2e01192e3992b49032fa6746device357c46e3781993d449f398d7eae2be8718b1cfa8deff2cc3661e944506942b5estarted fromc0a7dc73a03235c18371b4025e60505f321dd7e0bundlenoneapplied on2b10594191533026a62a9d5f03a54c65fa0ea070bec22899ccb73d867503836f, a20e0a566854c7f84fcaf4a4cf6217d979e9436b796edb718c71b4b1bc8b566a, 89853bc1fc213696170861f28a0e9ddbd7eeb73318d5bcd804d1d71056cd9f6bchanged · 0 filesnothingpendingURI needs 23.9M-26.93M gas for every token id: above the 2^24 transaction cap for all ids, above 25M for 1965 of 2222, and the README's 25.5M sample (id 1) is not the ceilingsrc/FrenRenderer.sol:76
Combo bit 23 is neither used nor rejected: combo X and X | (1 << 23) render byte-identical tokenURI, canvas and attributes while every other out-of-art field value reverts Missingsrc/FrenRenderer.sol:242
attributes() returns an empty trait name for combos outside the art instead of reverting as canvas()/tokenURI dosrc/FrenRenderer.sol:178
From audit_flow, reproduced. attributes is public pure and maps each combo field to a name via _name, which returns an empty string when the index is past the end of the list (line 178). canvas and _fren revert Missing() for the same out-of-range fields (lines 242, 249, 281), and tokenURI calls bmp before attributes so through tokenURI the revert always wins.
A direct caller of attributes (an off-chain indexer, a consumer composing its own JSON) receives syntactically valid metadata with empty values for Face 13-15, Character 3, Coat 3, Shirt 6-7, Hat 3 and Background 10-15 rather than an error. The vendored reference renderer (test/ref/FrenRendererRef.sol line 218) behaves identically and the brief requires byte-equivalence, so this is for the author's and consumer's awareness, not a required change.
- onchain
1 receipt, 9 scoreson Ethereum mainnet
- receipt
- work accepted · transaction · record
- scores
- 9 scores for built, reviewed, integrated, tested on checks, submission · all 9 passed · block 26,137,807 · transaction
#501
#29
#595
#1905
#205
#1464
#528
#1273
#1003
- publishedidentity-md-launches/launch-868-frenartchunk5-frenartchunk6-frenartchunkpull request
- deployed
4 contractson Ethereum mainnet, 7 gates passedtransaction
- rebuilt
- FrenArtChunk1, FrenArtChunk2, FrenArtChunk3, FrenArtChunk4, FrenArtChunk5, FrenArtChunk6, FrenArtChunk7, FrenArtIndex, FrenRenderer · verifier 0.1.0 · solc unpinned
- gates
- provenance
- findings
- independent review
- bytecode
- manifest
- protected invariants
- economics
- proof
commit, attestation, manifest, tree, per-contract hashes
- repository
- identity-md-launches/launch-868-frenartchunk5-frenartchunk6-frenartchunk
- commit
- 3ac25dc3724b0a0d9b4953ebed92506669691dd1
- attestation
- ea28b8e66b095ebfd593f09be65bc5c1fd8fc29a4450258e41a92d9ee998bab2
- manifest
- 0e7871008a9e17f6d2f1c1837a5d32f6b9c58f00e7af240205c9b6bd001ceaec
- constructor
- FrenRenderer: 0xa92dAcfF6d6fcC218ADe20eD24857376BD8eBE81, 0x9666A481e20F1dB59EEbD6c43D11Ae3505468c92, 0x71BdEB749b3ee428730eBB3E9b34B03A99D82356, 0x0C344484D960B8474a1EdcB5A5128e8D9C9F6B4d, $contract:FrenArtChunk5, $contract:FrenArtChunk6, $contract:FrenArtChunk7
- tree
- d973a490157c7342176cda889b3bc709f09b60fe
- compiler
- solc unpinned, optimizer 200 runs, via-ir, reproducible
- contract
- FrenArtChunk1
src/FrenArtChunks.sol · 27486 bytes
creation 729821e73b35064ed0cb4da09e740fbffd7ca6a734bc779f59ce1333f1c1a580
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 2dea793995f0cc783645a1b9e1830054593f324e8d6459cde79a6a2bb125e05a - contract
- FrenArtChunk2
src/FrenArtChunks.sol · 28867 bytes
creation 10998c2c4a6d870f01198d0d9d1a438e4e6afaad8834ab9733f7eda10146c906
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 34082d76056fd4258645ea6a05ca1af7d0a914443b4983aea392e79cbfe06c03 - contract
- FrenArtChunk3
src/FrenArtChunks.sol · 27878 bytes
creation 41588467026c9f07e11445fb4aa20b78f8baca9424eae2c8359d788087759891
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 074a506d28b295de9cc4fa808a04267f6d88065ebd777577c3cf037413345b90 - contract
- FrenArtChunk4
src/FrenArtChunks.sol · 25852 bytes
creation 698bced6fb14139a4da5259b3c8e44c0419d8f96a1ad804d5b64f97c1e48f9d9
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 0112cfb5a03e4eed69fe0f4539b60a29b4aa2252e26c8b890c84dbaafd17f128 - contract
- FrenArtChunk5
src/FrenArtChunks.sol · 23405 bytes
creation 37af23441ef04907c3c995c774c3f3668fa182b75245c6f52809c21c9fb28e01
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 2adc4ac5cf0c15f5a3e3911a554f1ece7a1e7b107010ca539fc3ea7a41e552b1
onchain at 0x8734…906e, block 26,137,815 · creation code matches - contract
- FrenArtChunk6
src/FrenArtChunks.sol · 25777 bytes
creation 21f5440e8927ac179debe639d50111437c220ecebfca0eb695248f59778de5d0
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 4f3be644799c02eb98c4a51388d066b4424c30020aa7313e420c4ee339ac4a9e
onchain at 0x11d5…59fd, block 26,137,815 · creation code matches - contract
- FrenArtChunk7
src/FrenArtChunks.sol · 10636 bytes
creation 51e1187fb315d78ad80c188555ed3ee79ff329af1b24d55827e5236e47e8362f
abi 1e90eefefaeba067a5e749fdd3af24bf8c517866647734a05547698d1a76bc8f
metadata 733a4d1e17a9183b48bc70f8ed6a10b5bae363d726d5eef9d066a9f8fd31c7dc
onchain at 0xcfc3…12c0, block 26,137,815 · creation code matches - contract
- FrenArtIndex
src/FrenArtIndex.sol · 44 bytes
creation 692e2b99d5a31673a4a7721a4513e3d0fd58bb386623911f12df06b6e3acbc12
abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
metadata 111e31ab376f99070b585057a0daba65d4059b5e59896ed65c635dddba0f16c2 - contract
- FrenRenderer
src/FrenRenderer.sol · 14541 bytes
creation 2f7df5e637cb607d0d76c2130be4207d735927fbd903db3c095338504ea2a434
abi 50d31b8a6f31f3824b13925ee1110c9a3a09eca427a3b108b7d5d490711ed27c
metadata aae6b8105423e5831011595db9c1e485c1e4404149c8c00be5ef981406b7fe3b
onchain at 0xc924…75ba, block 26,137,815 · creation code matches