Skip to content

Formalize auction custody and settlement#59

Merged
punk6529 merged 3 commits into
mainfrom
codex/auction-custody-state-machine
Jun 10, 2026
Merged

Formalize auction custody and settlement#59
punk6529 merged 3 commits into
mainfrom
codex/auction-custody-state-machine

Conversation

@punk6529

@punk6529 punk6529 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #22.

This implements the accepted ADR 0002 auction-custody target behavior for P0-AUCT-001, building on PR #58's outbid pull-credit work.

  • require StreamDrops to use a configured auction contract before auction drops mint
  • mint auction drops into auction-contract escrow and register explicit auction records
  • add StreamAuctions ERC-721 receiver support, canonical status views, terminal states, end-time views, and pending no-bid claim views
  • reject bids outside Active, keep auction-local end-time extension in the auction record, and preserve bidder-credit accounting
  • support no-bid EOA settlement, contract-poster pending NFT claims, with-bid settlement, failed-transfer rollback, and pre-bid cancellation
  • convert final auction proceeds into auction-local poster/protocol/curator pull credits without claiming full ADR 0003 completion
  • update roadmap, ADRs, known blockers, test docs, and a dedicated auction custody operational note

Review-Fix Additions

  • added no-bid pending-claim rollback coverage for a rejecting recipient
  • documented and tested non-divisible proceeds rounding, with the remainder accruing to curator credit
  • documented the current atomic Created -> Active custody path and legacy minter end-time divergence after auction extension
  • added auction-contract interface validation before StreamDrops accepts a configured auction contract
  • added AuctionRecord.tokenId to match ADR storage shape
  • added forced-ETH surplus coverage with an intentional test-only ForceEth helper
  • added zero-address constructor/setter guards for auction proceeds recipients
  • fixed tokenId casing in indexer guidance

Non-goals

This PR intentionally does not finish the broader payment/accounting roadmap:

  • fixed-price pull payments remain open
  • protocol-wide ADR 0003 ledger categories remain open
  • curator reward accounting remains open
  • randomizer reserve accounting remains open
  • pause/admin controls and deployment readiness remain open
  • full invariant/fork/deployment rehearsal gates remain open

Validation

  • forge test --match-contract 'StreamAuction(Custody|Payments)Test|StreamDropsCharacterizationTest' -vvv passed with 35 tests
  • make check passed with 85 tests and known compiler/NatSpec/lint warnings
  • powershell -ExecutionPolicy Bypass -File scripts\check.ps1 passed with 85 tests and known compiler/NatSpec/lint warnings
  • forge fmt --check smart-contracts\AuctionContract.sol smart-contracts\StreamDrops.sol smart-contracts\IStreamAuctions.sol test\StreamAuctionCustody.t.sol test\StreamAuctionPayments.t.sol test\StreamDropsCharacterization.t.sol test\mocks\MockStreamAuctions.sol passed after adding Foundry to PATH
  • git diff --check passed
  • Markdown heading and traceability scans passed for touched docs/state files
  • repo-local Slither via .venv-tools\Scripts\slither.exe with Foundry on PATH returned the expected non-zero baseline-finding exit: 10 High, 25 Medium, 53 Low, 511 Informational, 6 Optimization; no reentrancy-no-eth findings and no production auction emergency arbitrary-send-eth finding on this delta
  • Slither's extra High finding versus the previous PR body is the intentional test-only ForceEth.force selfdestruct helper used to prove forced ETH does not corrupt owed/surplus accounting

Review notes

Please review especially the custody/state transitions in smart-contracts/AuctionContract.sol, the StreamDrops auction-contract registration path, and the target-state coverage in test/StreamAuctionCustody.t.sol and test/StreamAuctionPayments.t.sol.

Summary by CodeRabbit

  • New Features

    • Formalized auction custody & settlement state machine with escrowed minting, explicit lifecycle statuses, permissionless no-bid and with-bid settlement, proceeds categorization, withdrawals, and pre-bid-only cancellation.
  • Documentation

    • Added comprehensive auction custody/settlement docs, updated ADRs and roadmap with clarified scope, rollout, test plan, and accepted risks.
  • Tests

    • Added extensive auction custody/payments tests, mocks, and characterization updates covering settlement, cancellation, extensions, failure rollback, and proceeds behavior.

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df1f4759-d9c1-4935-97ed-bc93bb2228e4

📥 Commits

Reviewing files that changed from the base of the PR and between 7df1476 and 1e52d6d.

📒 Files selected for processing (13)
  • docs/adr/0002-auction-custody.md
  • docs/adr/0003-payment-accounting.md
  • docs/auction-custody.md
  • ops/AUTONOMOUS_RUN.md
  • ops/ROADMAP.md
  • smart-contracts/AuctionContract.sol
  • smart-contracts/IStreamAuctions.sol
  • smart-contracts/StreamDrops.sol
  • test/README.md
  • test/StreamAuctionCustody.t.sol
  • test/StreamAuctionPayments.t.sol
  • test/StreamDropsCharacterization.t.sol
  • test/mocks/MockStreamAuctions.sol
✅ Files skipped from review due to trivial changes (3)
  • docs/auction-custody.md
  • docs/adr/0002-auction-custody.md
  • test/README.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • test/mocks/MockStreamAuctions.sol
  • smart-contracts/IStreamAuctions.sol
  • smart-contracts/StreamDrops.sol
  • ops/AUTONOMOUS_RUN.md
  • ops/ROADMAP.md
  • test/StreamAuctionCustody.t.sol
  • smart-contracts/AuctionContract.sol

📝 Walkthrough

Walkthrough

This PR implements the P0-AUCT-001 auction custody and settlement state machine (ADR 0002), introducing an escrow-based custody model where auction NFTs are held by the auction contract from creation through settlement, a canonical AuctionStatus state machine with explicit transitions, idempotent settlement for both no-bid and with-bid flows, pull-based proceeds credits for poster/protocol/curator, and no-bid pending-claim support for contract posters.

Changes

Auction Custody and Settlement Implementation

Layer / File(s) Summary
ADR and Design Documentation
docs/adr/0002-auction-custody.md, docs/adr/0003-payment-accounting.md, docs/auction-custody.md, docs/known-blockers.md, ops/AUTONOMOUS_RUN.md, ops/ROADMAP.md
ADR 0002 maps P0-AUCT-001/P0-AUCT-002 to auction-custody state-machine behavior and defers protocol-wide payment-accounting work to ADR 0003. New auction-custody.md documents the target-state custody model, status machine, settlement/cancellation rules, and accounting scope; roadmap/ops/known-blockers reflect updated statuses.
Auction Contract State Model
smart-contracts/AuctionContract.sol, smart-contracts/IStreamAuctions.sol
Adds AuctionStatus/ProceedsCreditType and AuctionRecord storage, registerAuction interface, per-token state and view helpers (retrieveAuctionStatus, retrieveAuctionEndTime, pendingNoBidNftClaimant), and categorized proceeds-credit ledgers and totals.
Settlement and User Flows
smart-contracts/AuctionContract.sol (settlement/withdrawal/cancellation)
Implements participate gating and extension, _settleNoBidAuction/_settleWithBidAuction, _creditAuctionProceeds with explicit poster/protocol/curator split, claimNoBidAuctionToken, withdrawAuctionProceedsCredit*, cancelAuction, onERC721Received validations, and includes proceeds in totalOwed() via totalProceedsOwed().
StreamDrops Auction Integration
smart-contracts/StreamDrops.sol
Adds auctionContract storage, updateAuctionContract admin validation (non-zero + isStreamAuctionsContract()), updates _executeAuctionDrop to mint to the auctionContract escrow and call IStreamAuctions.registerAuction.
Test Infrastructure and Mocks
test/mocks/MockStreamAuctions.sol, test/README.md, test/StreamDropsCharacterization.t.sol
Adds MockStreamAuctions to capture registerAuction inputs; deployDropsWithAuction helper; test README documents target-state auction custody/settlement coverage; characterization tests added for auctionContract requirement and registration capture.
Auction Custody Test Suite
test/StreamAuctionCustody.t.sol
New suite validating registered-auction Active status and escrow custody, bid acceptance/rejection, permissionless no-bid settlement and terminal SettledNoBid, pending-claim flows for contract posters and rollback on claim failure, settlement failure bookkeeping, cancellation rules, and auction-extension behavior.
Characterization and Integration Tests
test/StreamDropsCharacterization.t.sol, test/StreamDropsIntegrationCharacterization.t.sol, test/StreamDropsERC1271.t.sol
Characterization tests now assert auction contract configuration requirement and custody routing to configured auction contract; integration tests deploy real StreamAuctions, verify custody and status recording, and assert no-bid settlement state; ERC1271 helpers wire auctions mock in deployment helpers.
Payment Settlement Tests
test/StreamAuctionPayments.t.sol
Updates settlement tests to assert atomic crediting and NFT transfer semantics, exercises withdrawAuctionProceedsCredit() for poster/payout/curator, verifies totalProceedsOwed and totalOwed behavior, covers forced-ETH surplus and remainder-to-curator rounding, preserves credits on withdrawal failure, and refactors auction creation helpers; adds RejectingProceedsRecipient and ForceEth test helpers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #26: Adds auction-local credit mappings and totalProceedsOwed; this PR implements per-category credits and includes proceeds in totalOwed(), directly addressing that issue's objectives.

Possibly related PRs

Poem

🐰 In escrow's hop and ledger's rhyme,
Tokens wait their settled time.
Bids and claims and careful flows,
Rabbit cheers where custody grows.
—A small hop for stateful prose

🚥 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 'Formalize auction custody and settlement' directly and clearly summarizes the main change: implementing ADR 0002 auction-custody behavior to establish known custody and explicit settlement state management.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #22: canonical AuctionStatus states, enforced state transitions, idempotent settlement for both no-bid/with-bid cases, no-bid recipient semantics, cancellation policy, lifecycle events, and comprehensive tests covering custody assertion, settlement idempotency, failed-transfer rollback, bid rejection post-settlement, and receiver reversion.
Out of Scope Changes check ✅ Passed All changes align with issue #22 scope: custody model, state machine, settlement flows, and cancellation are in scope; broader payment accounting (ADR 0003), pause controls, randomizer hardening, and deployment discipline remain explicitly deferred as non-goals.
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/auction-custody-state-machine

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

Copy link
Copy Markdown
Contributor Author

@claude please review this PR, with special attention to auction custody/state transitions, with-bid settlement atomicity, no-bid contract-poster claim semantics, and whether the docs/test matrix accurately describe the remaining non-goals.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review latest head 7df147605729f90f9dd80a86d47a969bd09051e6 please. CI is green; looking for completion on the current auction custody/state-machine diff.

@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 PR diff and key changed files
gh pr view 59 --repo 6529-Collections/6529Stream --json title,body,files,headRefName,baseRefName | jq '{title, headRefName, baseRefName, files: [.files[].path]}'

Length of output: 1016


🏁 Script executed:

#!/bin/bash
# Get the full diff for the PR at the specified commit
git diff origin/main...7df147605729f90f9dd80a86d47a969bd09051e6 --stat

Length of output: 1255


🏁 Script executed:

#!/bin/bash
# Read the main auction contract
cat smart-contracts/AuctionContract.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 18558


🏁 Script executed:

#!/bin/bash
# Read IStreamAuctions interface
cat smart-contracts/IStreamAuctions.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 471


🏁 Script executed:

#!/bin/bash
# Read StreamDrops changes related to auction
cat smart-contracts/StreamDrops.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 17737


🏁 Script executed:

#!/bin/bash
# Read the custody test file
cat test/StreamAuctionCustody.t.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 10714


🏁 Script executed:

#!/bin/bash
# Read the payments test file
cat test/StreamAuctionPayments.t.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 18168


🏁 Script executed:

#!/bin/bash
# Read docs - ADR 0002
fd -e md . docs/ | xargs grep -l "0002\|auction" 2>/dev/null | head -10

Length of output: 437


🏁 Script executed:

#!/bin/bash
# Read ADR files
fd "ADR\|adr" docs/ -e md | head -10
cat docs/adr/0002-auction-custody.md 2>/dev/null || fd "0002" docs/ | head -5 | xargs cat 2>/dev/null || echo "ADR 0002 not found at expected path"

Length of output: 17720


🏁 Script executed:

#!/bin/bash
cat docs/auction-custody.md 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 4661


🏁 Script executed:

#!/bin/bash
cat docs/known-blockers.md 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 1477


🏁 Script executed:

#!/bin/bash
cat ops/ROADMAP.md 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 89392


🏁 Script executed:

#!/bin/bash
cat test/README.md 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 1958


🏁 Script executed:

#!/bin/bash
cat test/mocks/MockStreamAuctions.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 946


🏁 Script executed:

#!/bin/bash
# Check the full diff to see what changed in StreamDropsCharacterization and StreamDropsIntegrationCharacterization
git diff origin/main...7df147605729f90f9dd80a86d47a969bd09051e6 -- test/StreamDropsCharacterization.t.sol test/StreamDropsIntegrationCharacterization.t.sol test/StreamDropsERC1271.t.sol

Length of output: 9809


🏁 Script executed:

#!/bin/bash
# Check the actual diff for AuctionContract.sol and StreamDrops.sol
git diff origin/main...7df147605729f90f9dd80a86d47a969bd09051e6 -- smart-contracts/AuctionContract.sol smart-contracts/StreamDrops.sol smart-contracts/IStreamAuctions.sol

Length of output: 20805


🏁 Script executed:

#!/bin/bash
# Check ADR 0003 and ops/ROADMAP changes
git diff origin/main...7df147605729f90f9dd80a86d47a969bd09051e6 -- docs/adr/0003-payment-accounting.md ops/AUTONOMOUS_RUN.md

Length of output: 12323


🏁 Script executed:

#!/bin/bash
# Check for the ReentrancyGuard implementation
cat smart-contracts/ReentrancyGuard.sol 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 3419


🏁 Script executed:

#!/bin/bash
# Check IERC721 and IERC721Receiver implementations
cat smart-contracts/IERC721.sol 2>/dev/null
cat smart-contracts/IERC721Receiver.sol 2>/dev/null

Length of output: 6043


🏁 Script executed:

#!/bin/bash
# Look for any proceedsCredit rounding or dust tracking
rg "dust\|rounding\|totalPosterOwed\|totalProtocolOwed\|totalCuratorOwed\|posterCredit\|protocolCredit\|curatorCredit" smart-contracts/AuctionContract.sol

Length of output: 465


🏁 Script executed:

#!/bin/bash
# Check test helpers
cat test/helpers/StreamFixture.sol 2>/dev/null || echo "FILE NOT FOUND"
cat test/helpers/DropAuthTestHelper.sol 2>/dev/null | head -100

Length of output: 6059



@punk6529 — reviewed at 7df147605729f90f9dd80a86d47a969bd09051e6. The custody/state-machine implementation faithfully tracks ADR 0002, settlement atomicity and no-bid claim semantics are correctly structured, and the docs/test matrix scope boundaries are accurate. Findings below, roughly in order of importance.


Auction custody & state transitions

The registerAuction path is clean: StreamDrops mints to auctionContract, then immediately calls registerAuction, which does require(ownerOf(tokenId) == address(this)) before writing the record. The custody invariant is observable on-chain the moment the drop tx lands. ✓

retrieveAuctionStatus derives EndedNoBid/EndedWithBid from time and bid state, and the terminalStatus field gates all repeated-settlement attempts correctly. ✓

Created state is unreachable. registerAuction always sets custodyConfirmed: true. The retrieveAuctionStatus branch if (record.custodyConfirmed == false) return Created is dead code in practice. ADR 0002 explicitly permits skipping Created when mint and custody confirmation are atomic, so this is by design — but the custodyConfirmed field in the struct and the branch in the view function are now dead weight. Consider a comment or a note in docs/auction-custody.md to save the next reader from auditing the unreachable path.


With-bid settlement atomicity

The CEI order in _settleWithBidAuction is correct:

terminalStatus = SettledWithBid     ← state
auctionClaim = true                 ← state
totalAuctionBidEscrow -= bid        ← accounting
_creditAuctionProceeds(...)         ← internal only, no external calls
safeTransferFrom(...)               ← external call last

If safeTransferFrom reverts (e.g. NonReceiverBidder), all state and accounting reverts with it. The test testWithBidSettlementFailureLeavesEscrowAndCreditsUnchanged confirms this path. The nonReentrant guard on claimAuction blocks any callback attempt from onERC721Received. ✓


No-bid contract-poster claim semantics

The two-phase flow is correct:

  1. claimAuction_settleNoBidAuction: detects poster.code.length != 0, sets pendingNoBidNftClaimant, returns without setting terminalStatus → status remains EndedNoBid
  2. claimNoBidAuctionToken: requires msg.sender == pendingNoBidNftClaimant, clears the field, sets SettledNoBid, transfers to caller-chosen _recipient
  3. Re-entry via a second claimAuction while pending is blocked by the require(pendingNoBidNftClaimant == address(0)) guard ✓
  4. After claimNoBidAuctionToken sets the terminal state, a third claimAuction hits require(EndedNoBid || EndedWithBid) and reverts ✓

Missing test — failed NFT transfer in claimNoBidAuctionToken. ADR 0002 test plan explicitly requires "Failed NFT transfer leaves auction unsettled and does not create final payment credits" and the claimNoBidAuctionToken path is a separate settlement code path. If _recipient is a contract that rejects ERC-721, the state changes revert atomically (correct by Solidity semantics), but there is no explicit test asserting this. The existing tests cover testWithBidSettlementFailureLeavesEscrowAndCreditsUnchanged for the with-bid path but the analogous no-bid claim rollback test is absent:

function testNoBidContractPosterClaimFailureRollsBack() public {
    NonReceiverPoster poster = new NonReceiverPoster();
    AuctionSetup memory setup = _createAuction(address(poster), block.timestamp + 1 days, N);
    vm.warp(setup.auctionEndTime + 1);
    setup.auctions.claimAuction(setup.tokenId); // → sets pendingNoBidNftClaimant

    NonReceiverBidder rejectingRecipient = new NonReceiverBidder(); // rejects ERC-721
    (bool success,) = address(poster).call(
        abi.encodeWithSelector(
            poster.claim.selector, setup.auctions, setup.tokenId, address(rejectingRecipient)
        )
    );

    success.assertFalse("claim to rejecting recipient succeeded");
    setup.deployed.core.ownerOf(setup.tokenId)
        .assertEq(address(setup.auctions), "token left escrow");
    setup.auctions.pendingNoBidNftClaimant(setup.tokenId)
        .assertEq(address(poster), "pending claim cleared");
    uint256(setup.auctions.retrieveAuctionStatus(setup.tokenId))
        .assertEq(uint256(StreamAuctions.AuctionStatus.EndedNoBid), "status advanced");
}

Proceeds split — undocumented rounding

uint256 posterCredit = _highestBid / 2;
uint256 protocolCredit = _highestBid / 4;
uint256 curatorCredit = _highestBid - posterCredit - protocolCredit;

The curator absorbs any integer remainder (1–2 wei for non-4-divisible bids). totalPosterOwed + totalProtocolOwed + totalCuratorOwed == _highestBid holds exactly by construction, so the totalOwed invariant is fine. But ADR 0003 says "Make payout split rounding explicit." The current test fixture uses RESERVE_PRICE = 5 ether which is divisible by 4, so the dust path is never exercised. A one-line comment in _creditAuctionProceeds noting that the curator absorbs the remainder would satisfy ADR 0003's rounding-documentation requirement, and a small complementary test assertion (e.g. bid = 7 wei) would close the gap.


Minor findings

onERC721Received marked view. Solidity allows a non-view override with view (covariant). Works today, but if this hook ever needs to write state (e.g. registry side-effects) the modifier would need to be dropped and that would be a breaking interface change. Worth noting in a comment.

updateAuctionContract lacks interface validation. Every other contract setter in StreamDrops and StreamAuctions validates the target by calling a known method (e.g. isMinterContract(), isAdminContract()). updateAuctionContract only checks != address(0). A misconfigured address would silently succeed on updateAuctionContract and only fail at drop-mint time with a low-level revert from registerAuction. Low risk (admin-gated), but asymmetric with the rest of the pattern.

AuctionRecord is missing tokenId. ADR 0002 intended storage shape includes uint256 tokenId in the struct. The implementation omits it (token ID is only the mapping key). This means a record cannot self-identify without its context. Not a functional bug, but an auditor or indexer reconstructing state from the auctionRecords public mapping gets a record without a first-class token ID field.

Forced-ETH test not present. ADR 0002 test plan lists "forced ETH does not corrupt owed/surplus views" as required. StreamAuctionPayments covers emergencyWithdrawable with a vm.deal surplus scenario, but there is no explicit selfdestruct-style forced-ETH test confirming totalOwed is unaffected when ETH arrives outside normal flows.

AuctionExtended diverges from legacy minter end time. testBidNearEndExtendsAuctionRecord deliberately asserts the split: retrieveAuctionEndTime returns the extended time while minter.getAuctionEndTime returns the original. This is correct (status derivation uses the auction record's endTime), but any off-chain consumer still reading minter.getAuctionEndTime for the end time will get the wrong value post-extension. docs/auction-custody.md should note this divergence.


Docs / test matrix accuracy

ADR 0002 status block, ADR 0003 diff, docs/auction-custody.md, known-blockers.md, test/README.md, and ROADMAP Appendix B are all self-consistent and accurately scoped. The non-goals (fixed-price pull payments, curator reward accounting, randomizer reserves, ADR 0003 protocol-wide ledger, pause controls, deployment rehearsal) are clearly marked as open. Test matrix rows for Auction custody failure and No-bid settlement ambiguity are correctly advanced to Passing; all broader payment/randomness/metadata rows remain Missing or In Progress as appropriate. No accuracy issues with the docs.


Summary

Finding Severity for this PR
Missing claimNoBidAuctionToken failed-transfer rollback test Medium — required by ADR 0002 test plan
Proceeds rounding undocumented and untested for non-4-divisible amounts Low
Created state dead code — document or remove custodyConfirmed field Low
claimNoBidAuctionToken-path forced-ETH / invariant test missing Low
onERC721Received as view — forward-compat note Info
updateAuctionContract lacks interface validation Info
AuctionRecord missing tokenId field per ADR storage shape Info
AuctionExtended minter-divergence not documented Info

The core implementation is sound. The one item I'd prioritize before merge is the missing rollback test for claimNoBidAuctionToken with a rejecting recipient — it's the only explicitly required ADR 0002 test-plan item that has no coverage at all in this diff.

@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: 1

🧹 Nitpick comments (1)
smart-contracts/AuctionContract.sol (1)

381-405: 💤 Low value

Consider validating payOutAddress and curatorsPoolAddress are non-zero before crediting.

If either address is address(0) (e.g., due to admin misconfiguration), proceeds credited to them become unclaimable. The admin update functions (updatePayOutAddress, updateCuratorsPoolAddress) don't validate against zero addresses.

This is a low-probability misconfiguration scenario given admin controls, but for defense-in-depth you could add a check here or in the update functions.

🛡️ Optional: Add zero-address validation
 function _creditAuctionProceeds(uint256 _tokenid, address _poster, uint256 _highestBid)
     private
 {
+    require(payOutAddress != address(0), "No payout address");
+    require(curatorsPoolAddress != address(0), "No curator address");
     uint256 posterCredit = _highestBid / 2;

Or alternatively in the update functions:

 function updatePayOutAddress(address _payOutAddress)
     public
     FunctionAdminRequired(this.updatePayOutAddress.selector)
 {
+    require(_payOutAddress != address(0), "Zero address");
     payOutAddress = _payOutAddress;
 }
🤖 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/AuctionContract.sol` around lines 381 - 405, The
_creditAuctionProceeds function currently credits payOutAddress and
curatorsPoolAddress without verifying they are not address(0); add validation to
prevent credits to the zero address by requiring payOutAddress != address(0) and
curatorsPoolAddress != address(0) before performing credits (use require with
clear error messages), or alternatively add the same non-zero checks inside the
admin setters updatePayOutAddress and updateCuratorsPoolAddress to block setting
zero addresses; reference _creditAuctionProceeds, payOutAddress,
curatorsPoolAddress, updatePayOutAddress and updateCuratorsPoolAddress when
implementing the fixes.
🤖 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 `@docs/auction-custody.md`:
- Around line 122-123: The doc uses inconsistent casing `tokenid`; update all
occurrences to `tokenId` (e.g., in the sentence about indexers and examples) so
it matches the API and other docs; ensure the guidance references `tokenId` plus
`dropId` from the registration event and calls `retrieveAuctionStatus(tokenId)`
consistently everywhere in this document.

---

Nitpick comments:
In `@smart-contracts/AuctionContract.sol`:
- Around line 381-405: The _creditAuctionProceeds function currently credits
payOutAddress and curatorsPoolAddress without verifying they are not address(0);
add validation to prevent credits to the zero address by requiring payOutAddress
!= address(0) and curatorsPoolAddress != address(0) before performing credits
(use require with clear error messages), or alternatively add the same non-zero
checks inside the admin setters updatePayOutAddress and
updateCuratorsPoolAddress to block setting zero addresses; reference
_creditAuctionProceeds, payOutAddress, curatorsPoolAddress, updatePayOutAddress
and updateCuratorsPoolAddress when implementing the fixes.
🪄 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: 1ef1ddc0-29cf-4ab5-973d-27403f630744

📥 Commits

Reviewing files that changed from the base of the PR and between 256cb20 and 7df1476.

📒 Files selected for processing (16)
  • docs/adr/0002-auction-custody.md
  • docs/adr/0003-payment-accounting.md
  • docs/auction-custody.md
  • docs/known-blockers.md
  • ops/AUTONOMOUS_RUN.md
  • ops/ROADMAP.md
  • smart-contracts/AuctionContract.sol
  • smart-contracts/IStreamAuctions.sol
  • smart-contracts/StreamDrops.sol
  • test/README.md
  • test/StreamAuctionCustody.t.sol
  • test/StreamAuctionPayments.t.sol
  • test/StreamDropsCharacterization.t.sol
  • test/StreamDropsERC1271.t.sol
  • test/StreamDropsIntegrationCharacterization.t.sol
  • test/mocks/MockStreamAuctions.sol

Comment thread docs/auction-custody.md Outdated

Copy link
Copy Markdown
Contributor Author

Review-fix commit pushed: 1e52d6dfaf8a61bc7066ee3fade144bf4a397488.

Addressed the CodeRabbit review items:

  • added testNoBidContractPosterClaimFailureRollsBack for pending no-bid NFT claim rollback to a rejecting recipient
  • documented and tested non-divisible proceeds rounding; curator credit receives the remainder so all wei remain owed
  • documented the current atomic Created -> Active path, receiver-hook read-only behavior, and legacy minter end-time divergence after extension
  • added IStreamAuctions.isStreamAuctionsContract() and StreamDrops.updateAuctionContract validation
  • added AuctionRecord.tokenId
  • added forced-ETH surplus coverage; the resulting Slither suicidal High is intentional and test-only in ForceEth.force
  • added zero-address constructor/setter guards for auction proceeds recipients
  • fixed tokenId casing in indexer guidance

Latest local validation:

  • forge test --match-contract 'StreamAuction(Custody|Payments)Test|StreamDropsCharacterizationTest' -vvv passed with 35 tests
  • make check passed with 85 tests
  • powershell -ExecutionPolicy Bypass -File scripts\check.ps1 passed with 85 tests
  • forge fmt --check ... passed
  • git diff --check passed
  • Slither baseline rerun: expected non-zero exit, 10 High / 25 Medium / 53 Low / 511 Informational / 6 Optimization; no reentrancy-no-eth, no production auction emergency arbitrary-send-eth

@coderabbitai review

@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 for the fix commit
git show 1e52d6dfaf8a61bc7066ee3fade144bf4a397488 --stat

Length of output: 1096


🏁 Script executed:

#!/bin/bash
# Check the full diff of the fix commit
git show 1e52d6dfaf8a61bc7066ee3fade144bf4a397488 --no-color

Length of output: 26209



@punk6529 — verified at 1e52d6dfaf8a61bc7066ee3fade144bf4a397488. All eight findings from the previous review are closed cleanly.

Finding Status
Missing claimNoBidAuctionToken failed-transfer rollback test testNoBidContractPosterClaimFailureRollsBack added with NonReceiverRecipient; asserts token stays in escrow, pendingNoBidNftClaimant unchanged, status stays EndedNoBid
Proceeds rounding undocumented/untested ✅ Comment in _creditAuctionProceeds, ADR 0003 section updated, docs/auction-custody.md prose added, testProceedsRemainderAccruesToCuratorCredit uses 7 wei (poster=3, protocol=1, curator=3, sum=7)
Created state dead code ✅ Comment added to the unreachable branch; docs/auction-custody.md explains atomic drop path enters Active directly and Created is reserved for future non-atomic flows
Forced-ETH invariant missing testForcedEthOnlyIncreasesAuctionLocalSurplus added; ForceEth.selfdestruct Slither High is test-only and documented
onERC721Received as view — forward-compat note docs/auction-custody.md indexer section notes the hook is currently read-only and any future state-writing behavior is a deliberate interface change
updateAuctionContract lacks interface validation isStreamAuctionsContract() added to IStreamAuctions, StreamAuctions, and MockStreamAuctions; setter calls it; testUpdateAuctionContractRejectsNonAuctionContract added
AuctionRecord missing tokenId field ✅ Field added to struct, populated in registerAuction
AuctionExtended minter-divergence undocumented docs/auction-custody.md notes retrieveAuctionEndTime(tokenId) is authoritative post-extension and the legacy minter view may still show the original signed end time

The two bonus items (zero-address constructor/setter guards with testProceedsRecipientConfigurationRejectsZeroAddresses, and tokenId casing fix in the indexer guidance) are well-placed hardening. Collection ID bumps to avoid cross-test collisions are correct.

Implementation is sound. No new issues observed in the fix commit. ✓

[approve]

Copy link
Copy Markdown
Contributor Author

Autonomous merge decision for PR #59:

  • GitHub CI is green on 1e52d6dfaf8a61bc7066ee3fade144bf4a397488 (CI run 27267940459)
  • CodeRabbit command review 4668806431 says all eight previous findings are closed cleanly and no new issues were observed
  • visible review thread PRRT_kwDOM7REis6Ibep6 is resolved/outdated
  • Claude was explicitly requested but is externally unavailable due to organization overage limits
  • CodeRabbit commit status remains pending, matching the stale-status pattern documented on prior PRs; the latest review evidence is clean

Proceeding to merge under the autonomous maintainer rules.

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-AUCT-001] Formalize auction custody, settlement, and lifecycle state machine

1 participant