Skip to content

Add collection freeze manifests and guards#84

Merged
punk6529 merged 3 commits into
mainfrom
codex/metadata-freeze-manifest
Jun 11, 2026
Merged

Add collection freeze manifests and guards#84
punk6529 merged 3 commits into
mainfrom
codex/metadata-freeze-manifest

Conversation

@punk6529

@punk6529 punk6529 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Refs #47.

This PR implements the first P1-META-002 collection freeze-boundary slice for StreamCore.

It adds:

  • typed freeze manifest hashing and stored manifest views
  • CollectionFrozen(collectionId, manifestHash, schemaVersion, admin)
  • freeze eligibility checks for ended minting, elapsed final-supply delay, and final live-token metadata
  • freeze-time supply finalization and reserved max token ID tightening
  • post-freeze guards for current StreamCore metadata-significant paths
  • focused StreamMetadataFreeze.t.sol coverage
  • docs/roadmap/run-state traceability

Issue #47 remains open because dependency content immutability is broader than this slice. Immutable dependency version records and registry provenance remain P1-META-003 / #48.

Validation

  • forge test --match-contract StreamMetadataFreezeTest -vvv
  • make check
  • powershell -ExecutionPolicy Bypass -File scripts\check.ps1
  • forge fmt --check smart-contracts\StreamCore.sol smart-contracts\IStreamCore.sol test\StreamMetadataFreeze.t.sol
  • git diff --check
  • Markdown heading scan
  • Traceability grep
  • Slither comparison: High=4, Medium=19 unchanged; total findings 718

Review Notes

  • Claude review intentionally skipped per current maintainer instruction; CodeRabbit review requested separately.
  • This PR does not attempt dependency content immutability. It blocks dependency registry replacement once any collection has been frozen and commits the active dependency content hash into the freeze manifest.

Summary by CodeRabbit

  • New Features

    • Collection freeze: emits a freeze manifest event, stores a manifest hash, finalizes supply, tightens max token index, and blocks metadata-significant changes and dependency-registry swaps after freeze
    • APIs to preview and read collection freeze manifest hashes
  • Documentation

    • Expanded freeze semantics, manifest contents, eligibility rules, roadmap, ops and status notes
  • Tests

    • Comprehensive freeze suite covering happy path, negative cases (including premint token hash rejection), immutability and post-freeze guards

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fdd46ebd-18a6-4024-8060-250ca87814fb

📥 Commits

Reviewing files that changed from the base of the PR and between 4842edf and b48fbe9.

📒 Files selected for processing (3)
  • ops/AUTONOMOUS_RUN.md
  • smart-contracts/StreamCore.sol
  • test/StreamMetadataFreeze.t.sol
🚧 Files skipped from review as they are similar to previous changes (3)
  • ops/AUTONOMOUS_RUN.md
  • test/StreamMetadataFreeze.t.sol
  • smart-contracts/StreamCore.sol

📝 Walkthrough

Walkthrough

StreamCore records a deterministic collection freeze manifest hash, emits CollectionFrozen, finalizes supply, blocks metadata-significant writes and dependency-registry swaps after freeze, and exposes stored and preview manifest-hash views; docs, ops, and tests were updated to reflect and validate these behaviors.

Changes

Collection Freeze Manifest & Immutability Enforcement

Layer / File(s) Summary
Specification, Roadmap, and Operational Docs
docs/known-blockers.md, docs/metadata.md, docs/status.md, ops/AUTONOMOUS_RUN.md, ops/ROADMAP.md, test/README.md
Clarifies freeze manifest semantics and roadmap/test status: freeze prerequisites, recorded manifest hash/event, supply finalization, post-freeze write restrictions, and remaining P1 items (dependency/version immutability, burn/escaping semantics).
Interface, Constants, Storage, and Events
smart-contracts/IStreamCore.sol, smart-contracts/StreamCore.sol
Adds two public view methods for stored/preview manifest hashes, a METADATA_FREEZE_MANIFEST_TYPEHASH, new custom errors (e.g., MetadataFrozen, FrozenCollectionDependencyRegistry), per-collection manifest storage, per-token live-metadata bookkeeping, global frozenCollectionCount, and CollectionFrozen event.
Freeze Eligibility, Live-Metadata Tracking, and Hashing helpers
smart-contracts/StreamCore.sol
Implements _requireFreezeEligible, live-token metadata add/remove/refresh helpers, _finalizeCollectionSupply, and the manifest hashing pipeline that aggregates collection state, supply/integration info, info/script, and per-token metadata record hashes.
freezeCollection Orchestration and Public APIs
smart-contracts/StreamCore.sol
Refactors freezeCollection to validate eligibility, finalize supply, compute & persist the manifest hash, increment frozen counter, and emit CollectionFrozen. Updates setFinalSupply to use _finalizeCollectionSupply and adds collectionFreezeManifestHash / previewCollectionFreezeManifestHash views.
Mutation Guards and Dependency Swap Blocking
smart-contracts/StreamCore.sol
Adds _requireCollectionNotFrozen checks to addRandomizer, mint, artistSignature, setTokenHash, changeTokenData, and updateImagesAndAttributes; updateContracts reverts when frozenCollectionCount != 0 to block dependency-registry swaps after any freeze.
Tests and Test Documentation
test/StreamMetadataFreeze.t.sol, test/README.md
New Foundry tests exercise happy-path freeze, negative gating cases (mint window, final-supply window, pending live metadata), burned-token exceptions, post-freeze immutability across mutation paths, dependency-registry swap blocking, plus test helpers and README coverage notes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 In bytes we bind the final day,

A hash that keeps the ledger's say,
Mint doors close, the manifest stays,
No swaps, no edits, frozen ways,
A carrot nods — the code's bouquet 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add collection freeze manifests and guards' directly and clearly summarizes the main changes: introduction of freeze manifest hashing and related guard logic to prevent post-freeze state mutations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/metadata-freeze-manifest

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Validation already run locally:

  • forge test --match-contract StreamMetadataFreezeTest -vvv
  • make check
  • powershell -ExecutionPolicy Bypass -File scripts\check.ps1
  • forge fmt --check smart-contracts\StreamCore.sol smart-contracts\IStreamCore.sol test\StreamMetadataFreeze.t.sol
  • git diff --check
  • Markdown heading scan and traceability grep
  • Slither comparison unchanged at High=4, Medium=19; total findings 718

Claude review intentionally skipped per maintainer instruction for this PR.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

@punk6529 Kicking off the review now. I'll go through the freeze manifest hashing, guard logic, eligibility checks, and test coverage in StreamMetadataFreeze.t.sol.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/StreamMetadataFreeze.t.sol (1)

133-168: ⚡ Quick win

Use explicit revert assertions for all freeze-guard checks in this test.

The generic vm.expectRevert() calls at Line 133, Line 136, Line 141, Line 157, and Line 167 can pass on unrelated failures. Asserting the specific revert strings/errors will make these regressions precise.

Example tightening
-        vm.expectRevert();
+        vm.expectRevert(bytes("err/freezed"));
         deployed.core.setCollectionData(COLLECTION_ID, address(0xA11CE), 5, 10, 1 days);

-        vm.expectRevert();
+        vm.expectRevert(bytes("Not allowed"));
         deployed.core.changeMetadataView(COLLECTION_ID, true);

-        vm.expectRevert();
+        vm.expectRevert(bytes("Not allowed"));
         deployed.core
             .updateCollectionInfo(
                 COLLECTION_ID,
                 "Genesis",
                 "6529",
                 "Description",
                 "https://6529.io",
                 "CC0",
                 "ipfs://new-base/",
                 "https://cdn.example/script.js",
                 bytes32(0),
                 999999,
                 scripts
             );

-        vm.expectRevert();
+        vm.expectRevert(bytes("Data frozen"));
         deployed.core.changeTokenData(TOKEN_ID, "4,5,6");

-        vm.expectRevert();
+        vm.expectRevert(bytes("Data frozen"));
         deployed.core.updateImagesAndAttributes(tokenIds, images, attributes);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/StreamMetadataFreeze.t.sol` around lines 133 - 168, Replace the five
generic vm.expectRevert() calls with explicit revert assertions that match the
freeze-guard revert reason emitted by the core contract: for setCollectionData,
changeMetadataView, updateCollectionInfo, changeTokenData, and
updateImagesAndAttributes call
vm.expectRevert(abi.encodePacked("<EXACT_REVERT_STRING>")) (or
vm.expectRevert(<CustomError>.selector) if the contract uses custom errors)
using the exact revert message or custom error used in the freeze guard, then
perform the same call; update each vm.expectRevert() at the locations around
deployed.core.setCollectionData(...), deployed.core.changeMetadataView(...),
deployed.core.updateCollectionInfo(...), deployed.core.changeTokenData(...), and
deployed.core.updateImagesAndAttributes(...) so the test asserts the specific
error rather than any revert.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ops/AUTONOMOUS_RUN.md`:
- Line 40: Update the run-state in ops/AUTONOMOUS_RUN.md to reflect the open PR
`#84` by changing the status from "Status: In progress." to "Status: Open", update
the PR URL/reference to point to PR `#84`, and set the top-level timestamp (the
`Last updated` field) to the current UTC time for this change; also make the
same corrections where the same pre-open text appears (the other occurrences
noted around the same section, previously referenced as lines 3556-3558) so the
durable state is consistent across the file.

In `@smart-contracts/StreamCore.sol`:
- Around line 904-914: The current full-range scans in
_requireLiveTokenMetadataFinal (iterating mintedCount from
collectionAdditionalData.collectionCirculationSupply and checking _exists +
tokenToHash) cause freezeCollection and the preview path to be uncallable for
large collections; instead add and maintain a per-collection pending-metadata
counter (e.g., collectionAdditionalData.pendingMetadataCount) or a
per-collection set/bitmap of pending token ids that gets updated when tokens are
minted and when tokenToHash is set, then replace the loop in
_requireLiveTokenMetadataFinal with a constant-time check against that counter
(revert CollectionHasPendingTokenMetadata if pendingMetadataCount > 0) and
update mint, setTokenHash (or equivalent metadata assignment functions) to
increment/decrement the counter atomically so freezeCollection and preview (the
code around lines ~1008-1022) no longer scan every token id.

---

Nitpick comments:
In `@test/StreamMetadataFreeze.t.sol`:
- Around line 133-168: Replace the five generic vm.expectRevert() calls with
explicit revert assertions that match the freeze-guard revert reason emitted by
the core contract: for setCollectionData, changeMetadataView,
updateCollectionInfo, changeTokenData, and updateImagesAndAttributes call
vm.expectRevert(abi.encodePacked("<EXACT_REVERT_STRING>")) (or
vm.expectRevert(<CustomError>.selector) if the contract uses custom errors)
using the exact revert message or custom error used in the freeze guard, then
perform the same call; update each vm.expectRevert() at the locations around
deployed.core.setCollectionData(...), deployed.core.changeMetadataView(...),
deployed.core.updateCollectionInfo(...), deployed.core.changeTokenData(...), and
deployed.core.updateImagesAndAttributes(...) so the test asserts the specific
error rather than any revert.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc144288-75ad-4ee3-a347-c6e7950debac

📥 Commits

Reviewing files that changed from the base of the PR and between be2bdbe and 903d4b2.

📒 Files selected for processing (9)
  • docs/known-blockers.md
  • docs/metadata.md
  • docs/status.md
  • ops/AUTONOMOUS_RUN.md
  • ops/ROADMAP.md
  • smart-contracts/IStreamCore.sol
  • smart-contracts/StreamCore.sol
  • test/README.md
  • test/StreamMetadataFreeze.t.sol

Comment thread ops/AUTONOMOUS_RUN.md Outdated
Comment thread smart-contracts/StreamCore.sol

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
smart-contracts/StreamCore.sol (2)

321-333: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Post-freeze burns still mutate the freeze manifest surface.

burn() updates both burnAmount and the live-token accumulator without a frozen check. Those values feed _freezeSupplyStateHash() and _liveTokenMetadataHash(), so a burn after freezeCollection() makes previewCollectionFreezeManifestHash() drift away from the stored collectionFreezeManifestHash(). If the freeze boundary is meant to remain stable, burns need the same guard or the preview path needs to return the stored hash once frozen.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smart-contracts/StreamCore.sol` around lines 321 - 333, The burn() function
is mutating freeze-related state (calling _removeLiveTokenMetadataRecord and
incrementing burnAmount) after a collection has been frozen, which
desynchronizes _freezeSupplyStateHash() and _liveTokenMetadataHash() used by
previewCollectionFreezeManifestHash() and the stored
collectionFreezeManifestHash(); add a frozen-check guard in burn() that prevents
any state changes when the target collection is frozen (check the collection's
frozen flag set by freezeCollection()), or alternatively short-circuit
previewCollectionFreezeManifestHash() to return the stored
collectionFreezeManifestHash() when frozen; ensure you reference and protect
data mutated in burn() (burnAmount, _removeLiveTokenMetadataRecord, _burn) so
frozen collections remain immutable.

471-488: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate pre-mint hashes against the collection’s token range.

The new liveToken branch only enforces _collectionID for already-minted tokens. For pre-mint callbacks, a randomizer assigned to collection A can still write tokenToHash for an ID in collection B’s reserved range; when B later mints it, _addLiveTokenMetadataRecord() treats that token as already final, and Line 476 prevents B’s own randomizer from correcting it. Please reject _mintIndex values outside _collectionID’s reserved range before accepting any pre-mint hash.

Suggested fix
 function setTokenHash(uint256 _collectionID, uint256 _mintIndex, bytes32 _hash) external {
         require(msg.sender == collectionAdditionalData[_collectionID].randomizerContract);
         _requireCollectionNotFrozen(_collectionID);
         require(_hash != bytes32(0), "Zero token hash");
+        uint256 minTokenId = collectionAdditionalData[_collectionID].reservedMinTokensIndex;
+        uint256 maxTokenId = collectionAdditionalData[_collectionID].reservedMaxTokensIndex;
+        require(_mintIndex >= minTokenId && _mintIndex <= maxTokenId, "Wrong collection");
         require(
             tokenToHash[_mintIndex]
                 == 0x0000000000000000000000000000000000000000000000000000000000000000
         );
         bool liveToken = _exists(_mintIndex);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smart-contracts/StreamCore.sol` around lines 471 - 488, The setTokenHash path
allows pre-mint writes to tokenToHash for IDs outside the target collection; add
a check that _mintIndex falls inside _collectionID’s reserved token ID range
before accepting any hash. Use the collection metadata in
collectionAdditionalData[_collectionID] (e.g., its start token id and size/last
id fields) to compute the valid range and add a require(...) that _mintIndex is
>= start and <= end; keep this validation alongside the existing
_requireCollectionNotFrozen and before writing tokenToHash so both pre-mint and
live-token flows are guarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@smart-contracts/StreamCore.sol`:
- Around line 321-333: The burn() function is mutating freeze-related state
(calling _removeLiveTokenMetadataRecord and incrementing burnAmount) after a
collection has been frozen, which desynchronizes _freezeSupplyStateHash() and
_liveTokenMetadataHash() used by previewCollectionFreezeManifestHash() and the
stored collectionFreezeManifestHash(); add a frozen-check guard in burn() that
prevents any state changes when the target collection is frozen (check the
collection's frozen flag set by freezeCollection()), or alternatively
short-circuit previewCollectionFreezeManifestHash() to return the stored
collectionFreezeManifestHash() when frozen; ensure you reference and protect
data mutated in burn() (burnAmount, _removeLiveTokenMetadataRecord, _burn) so
frozen collections remain immutable.
- Around line 471-488: The setTokenHash path allows pre-mint writes to
tokenToHash for IDs outside the target collection; add a check that _mintIndex
falls inside _collectionID’s reserved token ID range before accepting any hash.
Use the collection metadata in collectionAdditionalData[_collectionID] (e.g.,
its start token id and size/last id fields) to compute the valid range and add a
require(...) that _mintIndex is >= start and <= end; keep this validation
alongside the existing _requireCollectionNotFrozen and before writing
tokenToHash so both pre-mint and live-token flows are guarded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52aa3f07-00b0-40fa-881d-73cff602c48d

📥 Commits

Reviewing files that changed from the base of the PR and between 903d4b2 and 4842edf.

📒 Files selected for processing (4)
  • docs/metadata.md
  • ops/AUTONOMOUS_RUN.md
  • smart-contracts/StreamCore.sol
  • test/StreamMetadataFreeze.t.sol
✅ Files skipped from review due to trivial changes (1)
  • docs/metadata.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • ops/AUTONOMOUS_RUN.md
  • test/StreamMetadataFreeze.t.sol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant