Skip to content

Store raw randomness output hashes#70

Merged
punk6529 merged 4 commits into
mainfrom
codex/randomizer-raw-output-hash
Jun 10, 2026
Merged

Store raw randomness output hashes#70
punk6529 merged 4 commits into
mainfrom
codex/randomizer-raw-output-hash

Conversation

@punk6529

@punk6529 punk6529 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Store rawOutputHash = keccak256(abi.encode(randomWords)) in each randomness request alongside the derived seed, without storing full provider word arrays.
  • Domain-separate derived seed generation with RANDOMNESS_SEED_TYPEHASH and request-bound fields: provider, request ID, collection, token, randomizer epoch, and rawOutputHash.
  • Extend RandomnessFulfilled, post-processing failure, retry success, and retry failure events to expose the raw-output hash; fulfillment events now also include provider and epoch context for indexers.
  • Add IRandomizerLifecycle request/state view signatures so monitoring and audit tooling can read request records and raw-output hashes through the lifecycle interface.
  • Emit provider-specific raw-word RequestFulfilled events from both VRF and arRNG adapters while keeping contract state hash-only.
  • Add VRF/arRNG lifecycle and retry coverage for stored raw-output hashes, event assertions, retry preservation, stale zero-hash state, lifecycle interface views, monotonic log helper matching, and post-request token data mutation not biasing the seed.
  • Update ADR 0005, roadmap/status/blocker docs, test guide, and autonomous run state.

Validation

  • forge test --match-contract StreamRandomizerLifecycleTest -vvv passed: 19 tests, 0 failed.
  • forge test --match-contract StreamRandomizerRetryTest -vvv passed: 10 tests, 0 failed.
  • forge fmt --check smart-contracts\IRandomizerLifecycle.sol smart-contracts\RandomizerRNG.sol smart-contracts\StreamRandomizerLifecycle.sol test\StreamRandomizerLifecycle.t.sol test\StreamRandomizerRetry.t.sol passed.
  • git diff --check passed.
  • Traceability grep for P0-RAND-007, rawOutputHash, raw-output hash, RANDOMNESS_SEED_TYPEHASH, RequestFulfilled(uint256,uint256[]), IRandomizerLifecycle, retry confirmation, and stale raw hash passed.
  • Markdown heading scan passed for roadmap/status/ADR/test docs.
  • make check passed: 171 tests, 0 failed.
  • powershell -ExecutionPolicy Bypass -File scripts\check.ps1 passed: 171 tests, 0 failed.
  • Slither baseline unchanged: slither_exit=-1, total=687, high=9, medium=29, weak-prng=2, arbitrary-send-eth=0, reentrancy-eth=0, reentrancy-no-eth=0, reentrancy-events=22.

Closes #43.

Summary by CodeRabbit

  • New Features

    • Randomness requests now store and expose a canonical raw-output hash alongside derived seeds; lifecycle events and a new fulfillment event include this hash and raw-word result context.
    • Added a public seed typehash and new retrieval interfaces to inspect request state/records.
  • Documentation

    • Architecture, roadmap, status, blockers, and ops notes updated to reflect storage/observability and review routing.
  • Tests

    • Test suites updated to derive/validate raw-output hashes across fulfillment, retries, and failures.
  • Chores

    • Ops/worklog and PR queue updated.

@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.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the raw-output hash storage implementation, especially the event ABI changes, RANDOMNESS_SEED_TYPEHASH domain-separated seed derivation from rawOutputHash, retry/failure preservation of the stored hash, and the no-full-raw-words storage policy from ADR 0005.

@coderabbitai

coderabbitai Bot commented Jun 10, 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: e97e91ce-ce4b-435e-b706-74e5d28a41ab

📥 Commits

Reviewing files that changed from the base of the PR and between f8d0470 and f52cd8f.

📒 Files selected for processing (1)
  • ops/AUTONOMOUS_RUN.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • ops/AUTONOMOUS_RUN.md

📝 Walkthrough

Walkthrough

PR #70 implements canonical rawOutputHash storage and typed seed derivation: contracts compute rawOutputHash = keccak256(abi.encode(randomWords)), derive a seed using RANDOMNESS_SEED_TYPEHASH + rawOutputHash, persist rawOutputHash on fulfillment, and expose both values in fulfillment and post-processing events; tests, ADRs, ROADMAP, status, blockers, and ops docs were updated accordingly.

Changes

Randomness Raw Output Hash Storage

Layer / File(s) Summary
Interface, request model, and initial request state
smart-contracts/IRandomizerLifecycle.sol, smart-contracts/StreamRandomizerLifecycle.sol, smart-contracts/RandomizerRNG.sol
Adds request-state enum and RandomnessRequest view APIs; declares RANDOMNESS_SEED_TYPEHASH, extends RandomnessRequest with rawOutputHash, adds RequestFulfilled event, and initializes rawOutputHash to bytes32(0) on request recording.
Event signature updates and emissions
smart-contracts/StreamRandomizerLifecycle.sol, smart-contracts/RandomizerRNG.sol
Extends RandomnessFulfilled, RandomnessPostProcessingFailed, RandomnessPostProcessingRetried, and RandomnessPostProcessingRetryFailed to include rawOutputHash; updates emissions to pass stored request.rawOutputHash; emits RequestFulfilled(requestId, randomWords) during RNG fulfillment.
Raw hash and seed derivation + fulfillment flow
smart-contracts/StreamRandomizerLifecycle.sol
Adds _hashRawRandomWords and _deriveRandomnessSeed helpers; computes rawOutputHash = keccak256(abi.encode(randomWords)), derives typed seed via RANDOMNESS_SEED_TYPEHASH plus rawOutputHash, stores rawOutputHash on fulfillment, and threads both values through post-processing flows.
Lifecycle and retry tests (VRF & ARRNG)
test/StreamRandomizerLifecycle.t.sol, test/StreamRandomizerRetry.t.sol
Adds _rawOutputHash and _expectedSeed helpers; updates tests to compute/verify emitted and stored rawOutputHash and derived seed across fulfillment, post-processing failures, retries, retry-limit scenarios, immutability under token-data mutation, and migration cases.
Test helpers and README updates
test/README.md, test/...
Refactors assertion helpers to accept expectedRawOutputHash, documents canonical storage and retry semantics in test README and per-test comments.
ADR, ROADMAP, status, blockers, and ops
docs/adr/0005-randomness.md, ops/ROADMAP.md, docs/known-blockers.md, docs/status.md, ops/AUTONOMOUS_RUN.md
Formalizes seed-and-storage policy to require storing derived seed/hash + rawOutputHash (no raw word arrays in contract storage), updates ROADMAP test-matrix and P0-RAND-* bullets, reflows status/blockers wording, and records ops queue/worklog updates for PR #70.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • 6529-Collections/6529Stream#69: Overlaps retry/fulfillment event/state handling and advances bounded post-processing retry work that this PR extends with rawOutputHash persistence.
  • 6529-Collections/6529Stream#68: Introduced deterministic post-processing failure paths and failureDataHash plumbing; this PR augments those events/state with rawOutputHash.
  • 6529-Collections/6529Stream#66: Added token-level lifecycle view helpers that return RandomnessRequest; this PR changes RandomnessRequest shape (adds rawOutputHash) used by those views.

Poem

🐰 I hash the raw words, tuck them tight,
A seed is born from typed delight,
Events sing both the hash and seed,
Tests nod true — the logs agree indeed,
Hop, hop — randomness stored just right.

🚥 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 'Store raw randomness output hashes' directly and specifically describes the main change in the changeset—implementing storage of rawOutputHash alongside derived seeds.
Linked Issues check ✅ Passed All coding requirements from issue #43 [P0-RAND-007] are implemented: storage of rawOutputHash and derived seed without full words, deterministic domain-separated seed derivation, seed binding to request fields, event enrichment with rawOutputHash and context, lifecycle interface views, tests validating stored data, determinism, token-data non-bias, and event coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing ADR 0005 randomness storage policy: smart-contract modifications for hash derivation/storage/events, interface extensions for request views, test additions for validation, and documentation updates. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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/randomizer-raw-output-hash

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Head moved with a state-only ops/AUTONOMOUS_RUN.md update after opening the PR. Please review the current head.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all relevant files
fd -e sol -e md | sort

echo "=== Directory structure ==="
tree -L 4 2>/dev/null || find . -maxdepth 4 -type f | sort

Length of output: 6506


🏁 Script executed:

#!/bin/bash
# Read the main randomizer contract
fd -e sol | xargs grep -l "RANDOMNESS_SEED_TYPEHASH\|rawOutputHash\|RandomnessFulfilled" 2>/dev/null

echo "=== Find sol files ==="
fd -e sol

Length of output: 2879


🏁 Script executed:

#!/bin/bash
cat smart-contracts/StreamRandomizerLifecycle.sol

Length of output: 17357


🏁 Script executed:

#!/bin/bash
cat smart-contracts/IRandomizerLifecycle.sol
cat smart-contracts/IRandomizer.sol

Length of output: 866


🏁 Script executed:

#!/bin/bash
cat docs/adr/0005-randomness.md

Length of output: 24502


🏁 Script executed:

#!/bin/bash
cat test/StreamRandomizerLifecycle.t.sol

Length of output: 35141


🏁 Script executed:

#!/bin/bash
cat test/StreamRandomizerRetry.t.sol

Length of output: 27111


🏁 Script executed:

#!/bin/bash
cat smart-contracts/RandomizerVRF.sol
echo "==="
cat smart-contracts/RandomizerRNG.sol

Length of output: 13488



@punk6529 — Full review of the raw-output hash storage implementation below.


Overall Assessment

The core design is sound and faithfully implements the ADR 0005 seed-and-storage policy. rawOutputHash is stored correctly, preserved through every failure and retry path, the RANDOMNESS_SEED_TYPEHASH shape matches the ADR's recommended shape exactly, and the no-full-words-in-storage rule is upheld. I have a few findings ranging from informational to minor.


1. ZeroDerivedSeed guard is unreachable (informational)

File: smart-contracts/StreamRandomizerLifecycle.sol, _fulfillRandomnessRequest

bytes32 rawOutputHash = _hashRawRandomWords(_randomWords);
derivedSeed = _deriveRandomnessSeed(..., rawOutputHash);
if (derivedSeed == bytes32(0)) {
    revert ZeroDerivedSeed(_requestId);
}

_deriveRandomnessSeed always hashes RANDOMNESS_SEED_TYPEHASH as the first abi.encode element. Since RANDOMNESS_SEED_TYPEHASH is a compile-time non-zero constant, the output of the outer keccak256 is statistically impossible to be zero. The check is dead code in practice, which can confuse auditors wondering when it fires. Either add an inline comment acknowledging this is defense-in-depth against future refactors, or remove it; a comment is preferred.


2. VRF emits full random words in event; RNG does not (minor inconsistency)

File: smart-contracts/RandomizerVRF.sol, fulfillRandomWords

try gencoreContract.setTokenHash(...) {
    _confirmRandomnessFulfillment(_requestId);
    emit RequestFulfilled(_requestId, _randomWords);   // full words exposed
} catch ...

ADR 0005 explicitly permits emitting raw words in a provider-specific event. However, RandomizerRNG.fulfillRandomWords has no equivalent emission—indexers and auditors get full VRF output auditability but only the hash for arRNG callbacks. If arRNG word auditability matters for the monitoring baseline, add a matching RequestFulfilled (or equivalent) event in RandomizerRNG. If the asymmetry is intentional (e.g., arRNG words are already logged by the arRNG controller), document it in the contract or ADR.


3. Retry success emits two consecutive events; indexers must handle both (design note)

File: smart-contracts/StreamRandomizerLifecycle.sol, _confirmRandomnessPostProcessingRetry

emit RandomnessPostProcessingRetried(..., request.rawOutputHash);
_confirmRandomnessFulfillment(_requestId);   // also emits RandomnessFulfilled

On a successful retry, the log stream contains RandomnessPostProcessingRetried immediately followed by RandomnessFulfilled for the same requestId. The tests assert both, so this is intentional, but it means indexers must not treat a second RandomnessFulfilled for the same request as a replay attack or double-fulfillment. This contract-level invariant ("a RandomnessFulfilled after RandomnessPostProcessingRetried for the same requestId is the success confirmation") should be documented in the contract NatDoc or the ADR.


4. calculateTokenHash writes tokenIdToCollection then reads it back through requestRandomWords (minor)

Files: smart-contracts/RandomizerVRF.sol, smart-contracts/RandomizerRNG.sol

// calculateTokenHash
tokenIdToCollection[_mintIndex] = _collectionID;
requestRandomWords(_mintIndex, ethRequired);

// requestRandomWords
uint256 collectionId = tokenIdToCollection[tokenid];  // reads it back

_collectionID is already in scope at the calculateTokenHash call site. Passing it directly to requestRandomWords (i.e., adding a uint256 _collectionId parameter) would eliminate the storage round-trip and make the data flow explicit. The current code is correct but introduces an implicit dependency: if requestRandomWords is ever called from a different path before calculateTokenHash sets tokenIdToCollection, collectionId will be 0 and the lifecycle record will be malformed. The require(msg.sender == gencore) guard on both functions currently prevents this, but the coupling is worth tightening.


5. IRandomizerLifecycle interface does not expose ADR 0005 required views (gap)

File: smart-contracts/IRandomizerLifecycle.sol

ADR 0005 §"Events And Views" lists as required:

request record by provider request ID, request record by token ID, request state by token ID, derived seed/hash by token ID, raw provider output hash by token ID if fulfilled

The interface currently only exposes:

function supportsRandomizerLifecycle() external view returns (bool);
function pendingRandomnessRequests(uint256 collectionId) external view returns (uint256);
function totalPendingRandomnessRequests() external view returns (uint256);

retrieveRandomnessRequest, retrieveRandomnessRequestForToken, randomnessRequestState, randomnessRequestStateForToken, and requestToToken/tokenToRequest are all public on the concrete contract but absent from the interface. External integrators (monitoring scripts, auditors, other contracts) using IRandomizerLifecycle cannot observe rawOutputHash or any request-record field. Consider adding at minimum retrieveRandomnessRequest and randomnessRequestStateForToken to the interface.


6. Log-scanning helpers can silently reset found from true to false (test robustness)

Files: test/StreamRandomizerLifecycle.t.sol and test/StreamRandomizerRetry.t.sol

for (uint256 i = 0; i < logs.length; i++) {
    if (/* topic match */) {
        found = actualProvider == ... && actualSeed == ...;  // ← overwrites prior true
    }
}
found.assertTrue("fulfilled event");

If a first log entry matches topics and data (setting found = true), and a later log entry matches topics but not data, found is reset to false and the assertion fails spuriously—or, in a crafted test with misordered logs, a wrong-data log could shadow a correct one. Replace the assignment with found = found || (actual... == expected...) or break on first successful match to make the helpers robust.


7. No test asserts rawOutputHash == bytes32(0) for a stale request (coverage gap)

_markRandomnessRequestStale never sets rawOutputHash; it stays at the zero value from _recordRandomnessRequest. This is correct behavior, but there is no explicit assertion in the test suite confirming it. A one-line check in testMarkedStaleRequestIsObservableAndCannotFulfill would close the gap:

vrf.markStaleRequest(1);
vrf.retrieveRandomnessRequest(1).rawOutputHash.assertEq(bytes32(0), "stale raw hash");

Summary table

# Severity Area Finding
1 Informational Contract ZeroDerivedSeed is unreachable; document as defense-in-depth
2 Minor VRF vs RNG Raw-words event emitted by VRF but not RNG; asymmetric auditability
3 Design doc Events Dual-event on retry success needs indexer documentation
4 Minor Adapters tokenIdToCollection storage round-trip in calculateTokenHashrequestRandomWords
5 Minor / ADR gap Interface IRandomizerLifecycle missing rawOutputHash and request-record views required by ADR 0005
6 Test robustness Tests Log-scan helpers can overwrite found=true with found=false
7 Coverage Tests No assertion for rawOutputHash == 0 on stale request

The rawOutputHash storage, TYPEHASH domain separation, and retry/failure preservation are all correctly implemented. The most actionable items are findings #5 (interface gap vs ADR) and #6 (test helper fragility).

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR info and list changed files
gh pr view 70 --repo 6529-Collections/6529Stream --json title,body,headRefName,headRefOid,baseRefName,files | jq '{title,headRefName,headRefOid,baseRefName,files:.files[].path}'

Length of output: 2341


🏁 Script executed:

#!/bin/bash
# Get the full diff of the PR
gh pr diff 70 --repo 6529-Collections/6529Stream 2>/dev/null | head -3000

Length of output: 66795


🏁 Script executed:

#!/bin/bash
# List all changed files
gh pr view 70 --repo 6529-Collections/6529Stream --json files | jq -r '.files[].path'

Length of output: 401


🏁 Script executed:

#!/bin/bash
cat smart-contracts/StreamRandomizerLifecycle.sol

Length of output: 17357


🏁 Script executed:

#!/bin/bash
# Check for rawOutputHash in views
rg -n "rawOutputHash\|retrieveRandomnessRequest\|retrieveRandomnessRequestForToken" smart-contracts/StreamRandomizerLifecycle.sol

Length of output: 419


🏁 Script executed:

#!/bin/bash
# Check TYPEHASH duplication and any import of it from tests
rg -n "RANDOMNESS_SEED_TYPEHASH" --type=sol

Length of output: 387


🏁 Script executed:

#!/bin/bash
# Search for TYPEHASH across all .sol files
rg -n "RANDOMNESS_SEED_TYPEHASH"

Length of output: 2277


🏁 Script executed:

#!/bin/bash
# Verify _assertRandomnessFulfilled duplication between test files
grep -n "_assertRandomnessFulfilled\|_rawOutputHash\|_expectedSeed" test/StreamRandomizerLifecycle.t.sol test/StreamRandomizerRetry.t.sol

Length of output: 3658



@punk6529: Review of the current head (dfbdab4) follows.


Review: Store raw randomness output hashes (PR #70)

ops/AUTONOMOUS_RUN.md

State-only update — active branch, last-merged PR reference, timestamp, Queue Item 28 completion, and Queue Item 29 worklog. The CodeRabbit-only review-path instruction is cleanly captured. No concerns.


smart-contracts/StreamRandomizerLifecycle.sol

Event ABI changes — all four affected events are internally consistent with the topic-hash strings used in both test files:

Event Indexed params Non-indexed params
RandomnessFulfilled requestId, collectionId, tokenId provider, randomizerEpoch, derivedSeed, rawOutputHash
RandomnessPostProcessingFailed requestId, collectionId, tokenId provider, randomizerEpoch, derivedSeed, rawOutputHash, failureDataHash
RandomnessPostProcessingRetried requestId, collectionId, tokenId provider, randomizerEpoch, retryCount, derivedSeed, rawOutputHash
RandomnessPostProcessingRetryFailed requestId, collectionId, tokenId provider, randomizerEpoch, retryCount, derivedSeed, rawOutputHash, failureDataHash

Cross-checked against keccak256 topic strings in both test files — all match. ✅

RANDOMNESS_SEED_TYPEHASH domain separation — the constant is public on the contract:

bytes32 public constant RANDOMNESS_SEED_TYPEHASH = keccak256(
    "6529StreamRandomnessSeed(address provider,uint256 requestId,uint256 collectionId,uint256 tokenId,uint256 randomizerEpoch,bytes32 rawOutputHash)"
);

_deriveRandomnessSeed includes it as the first element of abi.encode, which is standard EIP-712-style domain separation. The six request-bound fields (provider, requestId, collectionId, tokenId, randomizerEpoch, rawOutputHash) match the ADR 0005 spec exactly. ✅

request.provider vs address(this)provider is stored as address(this) at request-record time. _fulfillRandomnessRequest gates on request.provider != address(this) before computing the seed, so the value passed into _deriveRandomnessSeed is always the address that was recorded — correct and consistent. ✅

No-full-raw-words storage_hashRawRandomWords is private pure; the uint256[] array never touches storage. ✅

rawOutputHash zero-init and preservation — initialized to bytes32(0) in _recordRandomnessRequest, set during _fulfillRandomnessRequest, and never cleared by _setRandomnessPostProcessingFailedState (which only updates state and failureDataHash). Retried and retry-failed paths emit the stored request.rawOutputHash. ✅

RandomnessStale not updated — intentionally omitted because stale requests never received a fulfillment, so there is no rawOutputHash to emit. Correct. ✅


test/StreamRandomizerLifecycle.t.sol / test/StreamRandomizerRetry.t.sol

Coverage — new tests cover: VRF and arRNG rawOutputHash storage on fulfillment, preservation through failed post-processing, preservation through retry success and retry failure, and the post-request changeTokenData mutation test (testVrfSeedIgnoresMutableTokenDataChangedAfterRequest). The mutation test correctly changes token data after the request is recorded and before fulfillment, proving seed derivation is independent of mutable metadata. ✅

Two minor maintainability observations:

  1. RANDOMNESS_SEED_TYPEHASH duplicated in test files. The contract already declares this as public constant. Both test files redeclare it as private constant with the full string literal. If the string changes in the contract, tests won't catch the mismatch automatically.

    // Instead of redeclaring, tests could reference the contract constant:
    bytes32 private constant RANDOMNESS_SEED_TYPEHASH =
        StreamRandomizerLifecycle.RANDOMNESS_SEED_TYPEHASH;
    // (requires casting from abstract, or accessing via a deployed adapter instance)

    The practical risk is low since the constant is test-visible and the full test suite would break on a hash mismatch, but direct reference would make the dependency explicit.

  2. _assertRandomnessFulfilled, _rawOutputHash, and _expectedSeed duplicated between the two test files. These helpers are independently defined. Moving them to CharacterizationTestBase or a shared StreamRandomizerTestBase would eliminate the duplication. Not a correctness issue.


Docs (docs/adr/0005-randomness.md, docs/known-blockers.md, docs/status.md, ops/ROADMAP.md, test/README.md)

All updated sections accurately reflect the implementation: rawOutputHash storage policy, RANDOMNESS_SEED_TYPEHASH seed derivation, event shape changes, and P0-RAND-007 closure. The P0-RAND-007 open follow-up entry is correctly removed from the ADR. ✅


Summary

The core implementation is correct: rawOutputHash is stored, seeds are properly domain-separated, no raw word arrays hit storage, and all events expose both values consistently. The two test-side observations (TYPEHASH duplication, helper duplication) are maintainability nits and don't affect correctness. The PR is clean.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please re-review current head f8d0470. I addressed the prior review by adding lifecycle interface request/state views, arRNG raw-word provider events, retry-success event documentation, stale zero-hash coverage, monotonic log helper matching, and a defense-in-depth comment for the zero-seed guard. Local focused/full/Windows/Slither gates are green and unchanged where expected.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the diff from the PR at current head
gh pr diff 70 --repo 6529-Collections/6529Stream 2>/dev/null | head -5000

Length of output: 77542


🏁 Script executed:

#!/bin/bash
# Read updated contract files
cat smart-contracts/StreamRandomizerLifecycle.sol
echo "=== IRandomizerLifecycle ==="
cat smart-contracts/IRandomizerLifecycle.sol
echo "=== RandomizerRNG ==="
cat smart-contracts/RandomizerRNG.sol

Length of output: 26449


🏁 Script executed:

#!/bin/bash
# Read test files for updated helpers and stale coverage
grep -n "rawOutputHash\|found\|stale\|ZeroDerivedSeed\|defense\|monoton\|_assertRandomness" test/StreamRandomizerLifecycle.t.sol | head -80
echo "==="
grep -n "rawOutputHash\|found\|stale\|ZeroDerivedSeed\|defense\|monoton\|_assertRandomness" test/StreamRandomizerRetry.t.sol | head -80

Length of output: 6026



@punk6529 — Review of head f8d0470 follows.


Re-review: Store raw randomness output hashes (PR #70 @ f8d0470)

All six flagged items are addressed. Verification below.


Finding #1ZeroDerivedSeed defense-in-depth comment ✅

// This should be unreachable for keccak256 output, but the explicit
// guard preserves the no-zero-seed invariant if derivation changes.
if (derivedSeed == bytes32(0)) {

Clear and accurate. Auditors will understand intent without confusion.


Finding #2 — arRNG raw-word provider event ✅

RandomizerRNG now mirrors RandomizerVRF exactly:

event RequestFulfilled(uint256 requestId, uint256[] randomWords);
// ...
emit RequestFulfilled(id, numbers);

The lifecycle test calls _assertProviderRequestFulfilled(vm.getRecordedLogs(), address(rng), 1, words) for the arRNG path, confirming parity. ✅


Finding #3 — Dual-event retry-success documentation ✅

ADR 0005 and ops/ROADMAP.md both now contain:

Successful deterministic post-processing retries emit RandomnessPostProcessingRetried followed by RandomnessFulfilled for the same request ID. Indexers should treat that fulfillment event as retry success confirmation, not as a second provider callback.


Finding #5IRandomizerLifecycle interface views ✅

Interface now exposes the full RandomnessRequest struct (including rawOutputHash), retrieveRandomnessRequest, retrieveRandomnessRequestForToken, randomnessRequestState, randomnessRequestStateForToken, requestToToken, tokenToRequest, and tokenIdToCollection. The lifecycle test casts IRandomizerLifecycle(address(vrf)) and verifies rawOutputHash, state, and mapping views through the interface — confirming runtime conformance. ✅

One maintenance note: IRandomizerLifecycle.RandomnessRequest is a manual mirror of the concrete struct. If the concrete adds a field in a future PR, both must be updated in sync.


Finding #6 — Monotonic log-helper matching ✅

Every helper that decodes and compares event data now uses found = found || matches. The one remaining helper that uses found = true; (_assertCollectionRandomizerUpdated) is an existence-only check — it gates on topic match and sets found unconditionally, with no data comparison that could shadow a prior match. That pattern is correct as-is.


Finding #7 — Stale zero-hash coverage ✅

Two assertions added in testMarkedStaleRequestIsObservableAndCannotFulfill:

request.rawOutputHash.assertEq(bytes32(0), "stale raw hash");
// ...
interfaceRequest.rawOutputHash.assertEq(bytes32(0), "iface stale raw hash");

The interface view assertion is a nice bonus that also validates the #5 fix.


Finding #4tokenIdToCollection storage round-trip (not addressed)

The calculateTokenHash → requestRandomWords coupling is unchanged. This was the only minor item not mentioned in the response. It remains correct under the current msg.sender == gencore guards and doesn't affect this PR's objectives, so no action is required here.


Residual nit (from prior review, informational)

RANDOMNESS_SEED_TYPEHASH is still redeclared as private constant in both test files rather than referencing StreamRandomizerLifecycle.RANDOMNESS_SEED_TYPEHASH directly. No correctness impact — a mismatch would cause all seed assertions to fail — but a direct reference would make the dependency explicit. Not blocking.


Summary

Finding Status
#1 ZeroDerivedSeed comment ✅ Resolved
#2 arRNG raw-word event ✅ Resolved
#3 Retry dual-event doc ✅ Resolved
#4 Storage round-trip Not addressed (minor, not blocking)
#5 Interface views ✅ Resolved
#6 Monotonic log helpers ✅ Resolved
#7 Stale zero-hash coverage ✅ Resolved

Implementation is correct and complete for the PR's stated objectives. PR is clean.

[approve]

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please re-check latest head f52cd8f. This is a state-only sync in ops/AUTONOMOUS_RUN.md recording the already-green CI run, clean CodeRabbit review comment 4672884249, and the documented stale aggregate status pattern. No Solidity, tests, or product docs changed from the reviewed implementation.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what files changed at latest head vs the previously reviewed head
gh pr view 70 --repo 6529-Collections/6529Stream --json files | jq -r '.files[].path'

echo "=== Recent commits ==="
gh pr view 70 --repo 6529-Collections/6529Stream --json commits | jq -r '.commits[-5:] | .[] | "\(.oid[0:8]) \(.messageHeadline)"'

echo "=== Diff stat ==="
gh pr diff 70 --repo 6529-Collections/6529Stream 2>/dev/null | grep "^diff --git" | sort

Length of output: 1803


🏁 Script executed:

#!/bin/bash
# Check what files changed in f52cd8f specifically (the incremental commit)
git diff --name-only f8d0470b f52cd8f3

echo "=== Full diff of f52cd8f ==="
git diff f8d0470b f52cd8f3

Length of output: 5004



@punk6529 — Confirmed. The f52cd8f commit changes exactly one file: ops/AUTONOMOUS_RUN.md. The diff is a pure state sync:

  • Timestamp bump (17:56 → 17:58 UTC)
  • Queue item 29 status updated to reflect CI green and CodeRabbit clean
  • PR worklog updated to "merge-ready by autonomous maintainer decision"
  • CI run 27295049942 and CodeRabbit comment 4672884249 recorded as evidence
  • Decision log entry appended

No Solidity, tests, or product docs touched. The implementation reviewed and approved at f8d0470b is unchanged. The prior approval stands — PR #70 is clean.

[approve]

@punk6529 punk6529 merged commit 350667f into main Jun 10, 2026
2 checks passed
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.

[P0-RAND-007] Decide and implement raw random words versus derived hash storage policy

1 participant