diff --git a/docs/adr/0003-payment-accounting.md b/docs/adr/0003-payment-accounting.md new file mode 100644 index 00000000..0149c0c5 --- /dev/null +++ b/docs/adr/0003-payment-accounting.md @@ -0,0 +1,535 @@ +# ADR 0003: Payment Accounting + +## Status + +Accepted. + +## Metadata + +| Field | Value | +| --- | --- | +| Date | 2026-06-10 | +| Issue | [P0-PAY-ADR](https://github.com/6529-Collections/6529Stream/issues/24) | +| Blocks | [P0-PAY-001](https://github.com/6529-Collections/6529Stream/issues/25), [P0-PAY-002](https://github.com/6529-Collections/6529Stream/issues/26), [P0-PAY-003](https://github.com/6529-Collections/6529Stream/issues/27), [P0-PAY-004](https://github.com/6529-Collections/6529Stream/issues/28), [P0-PAY-005](https://github.com/6529-Collections/6529Stream/issues/29), [P0-PAY-006](https://github.com/6529-Collections/6529Stream/issues/30), [P0-PAY-007](https://github.com/6529-Collections/6529Stream/issues/31), [P0-PAY-008](https://github.com/6529-Collections/6529Stream/issues/8) | +| Related issues | [P0-PAY-008](https://github.com/6529-Collections/6529Stream/issues/8), [P0-AUCT-002](https://github.com/6529-Collections/6529Stream/issues/12), [P0-AUCT-001](https://github.com/6529-Collections/6529Stream/issues/22) | +| Related ADRs | [ADR 0001](0001-drop-authorization.md), [ADR 0002](0002-auction-custody.md) | +| Affected contracts | `smart-contracts/StreamDrops.sol`, `smart-contracts/AuctionContract.sol`, `smart-contracts/StreamCuratorsPool.sol`, `smart-contracts/StreamMinter.sol`, `smart-contracts/RandomizerRNG.sol` | +| Work type | `DESIGN` | + +## Problem + +6529Stream needs a protocol-wide accounting model before any P0 payment-moving +rewrite. The current implementation mixes protocol state changes with +synchronous ETH transfers, which creates denial-of-service, reentrancy, and +emergency-withdrawal risks. + +Before public beta, the protocol needs to decide: + +- which balances are owed to posters, bidders, curators, and protocol + recipients +- how owed balances are separated from protocol surplus +- how outbid refunds and final auction proceeds become durable credits +- how curator reward claims become durable credits +- how withdrawals behave when recipients revert or attempt reentrancy +- how direct and forced ETH affect surplus and invariants +- how emergency withdrawals are bounded so owed funds cannot be swept + +## Current Behavior + +Current source references: + +- `smart-contracts/StreamDrops.sol#L72-L110`: `mintDrop` mints fixed-price or + auction drops and stores drop metadata. +- `smart-contracts/StreamDrops.sol#L90-L92`: fixed-price minting pushes ETH to + the poster, payout address, and curators pool with low-level `call`. +- `smart-contracts/AuctionContract.sol#L64-L88`: bidding refunds the previous + highest bidder with an external `call` before writing the new highest bid and + highest bidder. +- `smart-contracts/AuctionContract.sol#L91-L108`: auction settlement marks the + token claimed, pushes final proceeds to poster, payout, and curators, then + transfers the NFT. +- `smart-contracts/StreamCuratorsPool.sol#L55-L73`: curator reward claims mark a + Merkle leaf claimed and push ETH to the reward address. +- `smart-contracts/AuctionContract.sol#L147-L153`, + `smart-contracts/StreamMinter.sol#L124-L130`, + `smart-contracts/StreamCuratorsPool.sol#L84-L90`, and + `smart-contracts/RandomizerRNG.sol#L78-L84`: emergency withdrawals send the + full contract balance to the admin without an owed-balance or reserved-balance + boundary. +- `ops/SLITHER_BASELINE.md`: high-impact `arbitrary-send-eth` and + `reentrancy-eth` findings track these payment and emergency-withdrawal + surfaces. + +Current characterization tests intentionally pin some unsafe behavior as +migration tripwires, but they are not target-state payment tests: + +- `test/StreamDropsCharacterization.t.sol` +- `test/StreamDropsIntegrationCharacterization.t.sol` + +## Decision + +6529Stream will use pull-payment accounting for public-beta payment flows. + +The public-beta target design is: + +1. Minting, bidding, settlement, curator claiming, and emergency controls must + not push user-owed ETH as part of their main state transition. +2. ETH owed to posters, bidders, curators, curator reserves, and protocol + recipients must be represented as withdrawable credits. +3. Owed balances must be tracked separately from protocol surplus. +4. Every value-holding contract must expose enough views to prove its owed, + reserved, surplus, and emergency-withdrawable balances. +5. Withdrawals are the only user-facing path that transfers owed ETH out of the + ledger. +6. Failed withdrawals must not erase credits. +7. Emergency withdrawals may withdraw only surplus. They must not withdraw + poster credits, bidder credits, curator credits, curator reserves, protocol + credits, active bid escrow, randomness fee reserves, or any other + contract-defined reserved balance. +8. Direct ETH and forced ETH must not create user credits automatically and must + not corrupt owed-balance accounting. +9. State-changing payment events must include stable IDs and indexed fields so + indexers can reconstruct credits, withdrawals, and surplus movement. +10. Payment-accounting implementation PRs must include targeted regression + tests and invariants before Gate C can close. + +## Ledger Model + +The implementation may choose exact Solidity names, but it should implement a +shared ledger module, base contract, or tightly consistent local ledgers with +the same external semantics. + +The preferred P0 implementation is a reusable internal payment ledger used by +the payment-moving contracts. A separate dedicated ledger contract is allowed +only if the implementation proves that custody, ownership, emergency controls, +and upgrade/redeployment risks are not increased. + +Required credit and reserve categories: + +| Category | Meaning | Example source | +| --- | --- | --- | +| `Poster` | Sale or auction proceeds owed to a poster. | Fixed-price mint split, with-bid auction settlement | +| `Bidder` | Refunds owed to bidders. | Previous highest bidder after an outbid, cancelled pre-bid auction refund if applicable | +| `Curator` | Rewards owed to individual curator reward addresses. | Valid Merkle reward claim | +| `CuratorReserve` | Funds reserved for future curator reward claims before an individual reward address is credited. This may be accountless aggregate storage or keyed to the curators pool identity, but it is not withdrawable except through the curator claim process. | Curators-pool share of mint or auction proceeds | +| `Protocol` | Amounts owed to the configured payout, treasury, or protocol recipient. | Payout share of mint or auction proceeds | +| `AuctionBidEscrow` | Active highest-bid funds held before outbid, cancellation, or settlement. This is reserved balance, not a withdrawable bidder credit. | Highest bid while an auction is active or ended but unsettled | +| `RandomnessReserve` | Funds reserved for randomness provider requests or callbacks. This is contract-specific reserved balance, not protocol surplus. | `RandomizerRNG` balance needed for arRNG-style requests | + +The implementation may encode categories as an enum, separate mappings, or +separate storage structs, but it must preserve these views or equivalents: + +```solidity +function creditOf(uint8 category, address account) external view returns (uint256); +function totalPosterOwed() external view returns (uint256); +function totalBidderOwed() external view returns (uint256); +function totalCuratorOwed() external view returns (uint256); +function totalCuratorReserved() external view returns (uint256); +function totalProtocolOwed() external view returns (uint256); +function totalAuctionBidEscrow() external view returns (uint256); +function totalRandomnessReserved() external view returns (uint256); +function totalOwed() external view returns (uint256); +function totalReserved() external view returns (uint256); +function surplus() external view returns (uint256); +function emergencyWithdrawable() external view returns (uint256); +``` + +`totalOwed` and `totalReserved` must include every withdrawable or reserved +amount that must not be swept by emergency withdrawal. If a contract needs +non-payment reserves, those reserves must be included in `totalReserved` and +excluded from `emergencyWithdrawable`. + +## Accounting Rules + +### Fixed-Price Minting + +Fixed-price minting must record credits instead of pushing ETH. + +The received payment must equal the validated fixed-price amount before credits +are recorded. + +For the current 50 / 25 / 25 economics: + +- poster credit is `msg.value / 2` +- curator reserve credit is `msg.value / 4` +- protocol credit is `msg.value - posterCredit - curatorReserveCredit` + +The protocol share receives the remainder so the split accounts for every wei. +Implementation PRs may change the economics only if a later ADR or roadmap +issue accepts the change and tests the new rounding policy. + +Fixed-price execution must remain consistent with ADR 0001: + +- payable fixed-price execution requires the signed `payer` to match the + execution policy from ADR 0001 +- free fixed-price execution must not create positive payment credits +- zero-address credit recipients are rejected unless a later ADR defines a burn + or donation policy + +### Auction Bidding + +Auction bidding must be compatible with ADR 0002's escrow custody model. + +Required behavior: + +- A valid bid records the bidder and bid amount without external refund calls. +- The current highest bid is active auction escrow, not a withdrawable credit + for the current highest bidder. +- When a new highest bid replaces a previous highest bid, the previous bidder + becomes a `Bidder` creditor for the previous bid amount and active auction + escrow moves to the new highest bid. +- A reverting previous bidder cannot block the new bid. +- Bid state and credit state must be updated before any external interaction + that can observe the transaction. +- Bidder credits must remain withdrawable even if the auction is later settled, + cancelled, paused, or emergency-handled. + +### Auction Settlement + +With-bid settlement must create credits instead of pushing final proceeds. + +For the current 50 / 25 / 25 economics: + +- poster credit is `highestBid / 2` +- curator reserve credit is `highestBid / 4` +- protocol credit is `highestBid - posterCredit - curatorReserveCredit` + +No-bid settlement must not create final payment credits unless the +implementation records an explicit fee or refund rule in a later ADR. + +Settlement must be idempotent. Repeated settlement attempts must not duplicate +credits, duplicate NFT transfers, or emit misleading duplicate terminal events. + +A failed NFT transfer must not mark the auction settled and must not create +final payment credits. If ADR 0002's no-bid NFT claim fallback is used, payment +credits remain separate from NFT claim state. + +### Curator Reward Claims + +Curator reward claims must separate Merkle authorization from ETH transfer. + +Required behavior: + +- Validate the Merkle proof and claim policy. +- Mark the claim consumed before any withdrawal transfer is possible. +- Credit the reward address in the `Curator` category. +- Decrease `CuratorReserve` by the credited amount if the reserve is held in the + same contract. +- Reject duplicate claims. +- Reject ambiguous or malformed leaves according to the Merkle encoding policy + in the roadmap. +- Let the reward address withdraw through the standard withdrawal path. + +### Direct And Forced ETH + +Direct ETH and forced ETH are surplus unless a protocol action records a credit +or reserve in the same transaction. + +Required behavior: + +- `receive` or `fallback` ETH must not credit `msg.sender` automatically. +- Forced ETH must not change credits, owed totals, or reserves. +- Direct or forced ETH may increase `surplus`. +- Direct or forced ETH must not mutate credits, reserves, or owed totals; if + the contract balance rises above `totalOwed`, the excess is surplus. +- Tests must include a force-send helper contract so the invariant suite covers + ETH received outside normal payable entrypoints. + +### Randomness Reserves + +`RandomizerRNG` and any future randomness adapter must distinguish provider +funding from surplus. + +Required behavior: + +- ETH reserved for randomness requests is included in `totalReserved`. +- Emergency withdrawal cannot withdraw randomness fee reserves. +- Direct ETH is not automatically randomness reserve unless a funded request or + explicit reserve action records it as such. +- Spending reserve on a randomness provider must reduce + `totalRandomnessReserved` or the equivalent reserved-balance view. +- If an adapter holds no provider reserve, it must document that its full + balance is surplus and prove that with tests. + +## Withdrawal Semantics + +The implementation may expose category-specific withdrawals or a batched +withdrawal API, but it must preserve this behavior: + +1. Only the credit owner can withdraw the owner's credits unless a later ADR + defines delegated withdrawal. +2. Withdrawing to `msg.sender` is required. +3. Withdrawing to a non-zero recipient address is allowed only if the + implementation records both the credit owner and recipient in events. +4. Withdrawals must use checks-effects-interactions and reentrancy protection. +5. Credits and aggregate owed totals are decremented before the external call + and restored or reverted if the call fails. +6. A failed withdrawal must preserve the caller's credit. +7. A failed withdrawal may either revert atomically with a custom error or + restore state and return a failure result. The chosen behavior must be + documented and tested. +8. No withdrawal may reduce another account's credit or another category's + aggregate total. +9. Withdrawal pause policy from this ADR is limited to payment safety: + withdrawal pause, if implemented, may only pause transfers temporarily; it + must not alter credits, totals, reserves, or emergency-withdrawable surplus. + ADR 0004 owns who can pause and unpause. + +Recommended custom errors: + +```solidity +error PaymentZeroAddress(); +error PaymentZeroAmount(); +error PaymentNoCredit(); +error PaymentTransferFailed(address account, address recipient, uint256 amount); +error PaymentInsufficientSurplus(uint256 requested, uint256 available); +``` + +## Emergency Withdrawal Policy + +Emergency withdrawal is a surplus-withdrawal function, not an owed-funds sweep. + +Required formula: + +```solidity +totalOwed = + totalPosterOwed + + totalBidderOwed + + totalCuratorOwed + + totalCuratorReserved + + totalProtocolOwed + + totalAuctionBidEscrow + + totalRandomnessReserved + + otherContractSpecificReserved; + +surplus = + address(this).balance > totalOwed + ? address(this).balance - totalOwed + : 0; + +emergencyWithdrawable = surplus; +``` + +Required behavior: + +- `emergencyWithdraw(amount, recipient)` or equivalent must require + `amount <= emergencyWithdrawable`. +- The recipient must be non-zero. +- The function must emit an event with the amount and remaining surplus. +- If `address(this).balance < totalOwed`, emergency withdrawal must revert or + expose zero withdrawable surplus until the deficit is resolved. +- Blanket full-balance emergency withdrawal is rejected for all contracts that + can hold owed or reserved funds. +- Contracts that claim to hold no owed or reserved funds must document why their + full balance is surplus and must still expose tests proving that assumption. + +## Invariants + +Implementation PRs must make these invariants executable: + +- `totalOwed == totalPosterOwed + totalBidderOwed + totalCuratorOwed + + totalCuratorReserved + totalProtocolOwed + totalAuctionBidEscrow + + totalRandomnessReserved + otherContractSpecificReserved`. +- Category totals equal the sum of tracked account credits. If Solidity cannot + iterate accounts directly, invariant tests must maintain ghost accounting. +- `address(this).balance >= totalOwed` after every normal value-moving + transaction. +- `emergencyWithdrawable == address(this).balance - totalOwed` when + `address(this).balance >= totalOwed`. +- `emergencyWithdrawable == 0` when `address(this).balance < totalOwed`. +- Failed withdrawal does not reduce credit or category totals. +- Reentrant withdrawal cannot drain more than the caller's available credit. +- Outbid refunds are credits, not push payments. +- Auction settlement credits are created at most once. +- Direct and forced ETH do not change credits or owed totals. +- Emergency withdrawal cannot withdraw owed or reserved funds. + +## Events + +The P0 implementation must emit events for every external payment state +transition. Event names may change during implementation, but the event catalog +must include: + +- credit recorded +- credit consumed by withdrawal +- withdrawal succeeded +- withdrawal failed, if the implementation uses a non-reverting failure path +- direct surplus received, if the contract accepts direct ETH +- emergency surplus withdrawal +- reserve increased +- reserve decreased + +Events should include stable IDs and indexed query fields where useful: + +- indexed `account` +- indexed `recipient` +- indexed `category` +- indexed `dropId`, `auctionId`, `tokenId`, or `collectionId` when applicable +- `amount` +- `newAccountCredit` +- `newCategoryTotal` +- `totalOwed` +- `surplus` +- source operation or reason + +Forced ETH cannot reliably emit an event at receipt time. The implementation may +expose a reconciliation event if an explicit function observes and records the +new surplus, but credits and owed totals must remain correct without that event. + +## Alternatives Considered + +### Keep Push Payments + +Rejected. Push payments let reverting recipients block minting, bidding, +settlement, or claims, and they expand reentrancy risk in already complex state +transitions. + +### Push Refunds But Pull Final Proceeds + +Rejected. The auction bid path is one of the highest-risk value flows. Outbid +refunds must become bidder credits so malicious previous bidders cannot block +valid bids. + +### Ad Hoc Per-Feature Ledgers + +Rejected as the default. Per-feature ledgers are easy to make inconsistent and +hard to audit. P0 should use a shared ledger module or identical category and +view semantics wherever separate storage is unavoidable. + +### Admin Full-Balance Emergency Sweep + +Rejected. A full-balance sweep can confiscate owed bidder, poster, curator, or +protocol credits and makes the accounting model unverifiable. + +### Auto-Credit Direct ETH Senders + +Rejected. Direct ETH and forced ETH cannot safely prove business intent. Only +protocol actions that validate the relevant drop, auction, or claim state may +record credits. + +### Ignore Forced ETH In Tests + +Rejected. Forced ETH can change `address(this).balance` without calling a +payable function, so the surplus and emergency-withdrawal model must explicitly +handle it. + +## Security Impact + +This ADR addresses: + +- push-payment denial of service +- outbid refund reentrancy and recipient griefing +- auction settlement payout duplication +- curator claim transfer failure behavior +- emergency withdrawals that can drain owed balances +- direct and forced ETH accounting ambiguity +- missing total-owed and surplus views + +This ADR does not by itself fix drop authorization, auction custody, admin role +selection, randomness callbacks, metadata finalization, or Merkle leaf +encoding. It defines payment rules that those implementation PRs must satisfy. + +## Migration Impact + +This is a breaking payment behavior change before public beta. + +Expected migration consequences: + +- recipients must withdraw instead of receiving ETH synchronously +- frontends and indexers must read credit, owed, and withdrawal events +- emergency withdrawal scripts must request surplus amounts instead of sweeping + full balances +- existing characterization tests that assert push-payment behavior must be + replaced or converted into target-state tests when implementation lands +- deployment runbooks must include surplus and owed-balance checks before any + emergency action + +No production migration is promised while the repository remains pre-audit and +not production-ready. + +## Test Plan + +P0 implementation must add tests for: + +- fixed-price mint records poster, curator reserve, and protocol credits +- fixed-price mint odd-wei rounding credits exactly `msg.value` +- zero-address credit recipient rejection +- free fixed-price mint creates no positive payment credits +- auction outbid records previous bidder credit without calling the previous + bidder +- active highest bid remains reserved auction escrow until outbid, cancellation, + refund, or settlement +- reverting previous bidder cannot block a new highest bid +- with-bid settlement records poster, curator reserve, and protocol credits +- repeated settlement cannot duplicate credits +- failed NFT transfer does not create final payment credits +- curator claim validates Merkle proof and records curator credit +- duplicate curator claim fails +- curator reserve decreases when individual curator credit is recorded +- successful withdrawal decreases account credit and category totals exactly + once +- failed withdrawal preserves credit and category totals +- reentrant withdrawal cannot withdraw more than available credit +- withdrawal-to-recipient records account and recipient +- direct ETH increases surplus without changing credits +- forced ETH increases surplus without changing credits +- randomness provider reserves cannot be withdrawn as surplus +- emergency withdrawal succeeds only up to surplus +- emergency withdrawal cannot withdraw owed or reserved funds +- deficit state, if reachable only by test harness, exposes zero + emergency-withdrawable surplus +- events are emitted for credit creation, withdrawal, reserve movement, and + emergency surplus withdrawal + +Intended test files: + +- `test/StreamPayments.t.sol` +- `test/StreamDropsPayments.t.sol` +- `test/StreamPaymentsInvariant.t.sol` +- `test/StreamAuctionPayments.t.sol` +- `test/StreamCuratorsPool.t.sol` +- `test/StreamRandomizerPayments.t.sol` +- `test/StreamEmergencyWithdraw.t.sol` + +Existing characterization tests must remain useful as pre-refactor evidence but +must not be treated as target-state tests after the implementation lands. + +## Rollout Plan + +1. Merge this ADR and link it from the roadmap. +2. Implement shared payment ledger storage, views, events, custom errors, and + withdrawal behavior. +3. Convert fixed-price mint payouts to credits. +4. Convert auction outbid refunds and final settlement proceeds to credits. +5. Convert curator reward claims to credits. +6. Bound emergency withdrawals by surplus in each affected contract. +7. Add payment regression tests, invariant tests, and forced-ETH tests. +8. Update user, integrator, deployment, and security docs. +9. Include payment credit creation, withdrawal, failed withdrawal, and emergency + surplus checks in deployment rehearsal. + +## Non-Goals + +- Defining the final admin actor for pause or emergency controls. That belongs + to ADR 0004. +- Defining auction custody or NFT settlement mechanics. Those belong to ADR + 0002. +- Defining relayer reimbursement. Open relaying remains out of scope for P0. +- Changing current sale or auction fee percentages. +- Adding ERC-20 payments. +- Defining production migration for legacy deployed balances. + +## Accepted Risks + +- Pull payments add an extra user action for recipients. This is accepted + because it removes push-payment denial-of-service and reentrancy risk. +- Category totals require careful storage updates and invariant tests. This is + accepted because the totals are needed for emergency-withdrawable views and + auditability. +- A shared ledger module may require contract refactoring. This is accepted + because inconsistent local accounting would be harder to audit. +- If a withdrawal recipient always reverts, that account's ETH may remain + credited until the account chooses a working recipient path. This is accepted + as long as credits remain visible and cannot be swept as surplus. +- Exact pause behavior for withdrawals depends on the admin/governance ADR. + This ADR requires balances to remain preserved regardless of pause policy. diff --git a/docs/adr/README.md b/docs/adr/README.md index d57e8129..753fad93 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -8,7 +8,7 @@ Expected ADRs are tracked in `ops/ROADMAP.md`: | --- | --- | --- | | [`0001-drop-authorization.md`](0001-drop-authorization.md) | Accepted | [#17](https://github.com/6529-Collections/6529Stream/issues/17) | | [`0002-auction-custody.md`](0002-auction-custody.md) | Accepted | [#21](https://github.com/6529-Collections/6529Stream/issues/21) | -| `0003-payment-accounting.md` | Missing | `P0-PAY-ADR` | +| [`0003-payment-accounting.md`](0003-payment-accounting.md) | Accepted | [#24](https://github.com/6529-Collections/6529Stream/issues/24) | | `0004-admin-governance.md` | Missing | `P0-ADMIN-ADR` | | `0005-randomness.md` | Missing | [#14](https://github.com/6529-Collections/6529Stream/issues/14) | | `0006-metadata-freeze.md` | Missing | `P1-META-ADR` | diff --git a/ops/AUTONOMOUS_RUN.md b/ops/AUTONOMOUS_RUN.md index e33e8f05..fde8e468 100644 --- a/ops/AUTONOMOUS_RUN.md +++ b/ops/AUTONOMOUS_RUN.md @@ -29,11 +29,11 @@ tests, security hardening, deployment discipline, and release/audit readiness. | Field | Value | | --- | --- | | Remote | `https://github.com/6529-Collections/6529Stream.git` | -| Active PR branch | `codex/auction-custody-adr` | -| Last merged PR | `https://github.com/6529-Collections/6529Stream/pull/20` | +| Active PR branch | `codex/payment-accounting-adr` | +| Last merged PR | `https://github.com/6529-Collections/6529Stream/pull/23` | | Roadmap file | `ops/ROADMAP.md` | | State file | `ops/AUTONOMOUS_RUN.md` | -| Last updated | `2026-06-10 03:17 UTC` | +| Last updated | `2026-06-10 03:54 UTC` | ## Packaging Notes @@ -58,8 +58,8 @@ The queue will evolve as PRs merge and bot feedback arrives. | 5 | Slither baseline appendix/config | Gate A / Gate C foundation | Static analysis command/config and tracked baseline issue rows | Merged in PR #7 | | 6 | Slither baseline issue links | Gate C / Gate F foundation | Create canonical GitHub issues for open high/medium Slither groups and link them from roadmap/baseline docs | Merged in PR #16 | | 7 | Drop authorization ADR | Gate B1 | Accept `docs/adr/0001-drop-authorization.md` before P0 auth rewrites | Merged in PR #20 | -| 8 | Auction custody ADR | Gate B1 | Accept `docs/adr/0002-auction-custody.md` before P0 auction rewrites | Open as PR #23 | -| 9 | Payment accounting ADR | Gate B1 | Accept `docs/adr/0003-payment-accounting.md` before pull-payment rewrites | Pending | +| 8 | Auction custody ADR | Gate B1 | Accept `docs/adr/0002-auction-custody.md` before P0 auction rewrites | Merged in PR #23 | +| 9 | Payment accounting ADR | Gate B1 | Accept `docs/adr/0003-payment-accounting.md` before pull-payment rewrites | Merge-ready as PR #32 | | 10 | Admin/governance ADR | Gate B1 | Accept `docs/adr/0004-admin-governance.md` before permission/pause rewrites | Pending | | 11 | Randomness ADR | Gate B1 | Accept `docs/adr/0005-randomness.md` before callback/randomness rewrites | Pending | @@ -447,7 +447,7 @@ Outcome: ### PR #23: Auction custody ADR (Queue Item 8) -Status: Open; waiting for CI and bot review. +Status: Merged. Branch: `codex/auction-custody-adr`. Pull request: `https://github.com/6529-Collections/6529Stream/pull/23`. Claude review request: issue comment `4666002050`. @@ -491,9 +491,11 @@ Validation: `P0-AUCT-002`, `0002-auction-custody`, issue links, and canonical auction states. - `git diff --check` passed. -- `make check` passed with 17 tests and known compiler/NatSpec warnings. +- `make check` passed with 17 tests and known compiler/NatSpec warnings after + payment issue-link traceability updates. - `powershell -ExecutionPolicy Bypass -File scripts\check.ps1` passed with - 17 tests and known compiler/NatSpec warnings. + 17 tests and known compiler/NatSpec warnings after payment issue-link + traceability updates. - Late Claude PR #20 cleanup validation passed for canonical `DROP_ID_TYPEHASH`, sale-mode-specific payer/price/auction fields, zero poster, and poster/signature semantics. @@ -539,7 +541,103 @@ Review feedback: deferred auction `recipient` rule. ADR 0002 now keeps auction `recipient == address(0)`, rejects non-zero signed auction recipients, and derives settlement recipients from `poster` and `highestBidder`. -- Waiting for CI and bot review to rerun on the latest head. +- Final CI passed on run `27250789199`. +- CodeRabbit completed successfully on the final head. +- Claude review threads were addressed and resolved before merge. +- Late Claude PR #20 review threads were also addressed and resolved after PR + #23 merged, because PR #23 had already updated ADR 0001 with the requested + field semantics and canonical `DROP_ID_TYPEHASH`. + +Outcome: + +- Merged as PR #23 on `2026-06-10 03:22 UTC`. +- Squash merge commit: + `e65a6814e55c7638f55c6de0714fd56296480e51`. +- Latest head before merge: + `6bfa7c79e3565adf47dd8f7d9dbb0578594c067c`. +- GitHub CI run `27250789199` passed on the final head. +- CodeRabbit completed successfully with no actionable comments on the final + head. +- Claude review threads were addressed and resolved before merge. + +### PR #32: Payment accounting ADR (Queue Item 9) + +Status: Merge-ready under autonomous maintainer decision. +Branch: `codex/payment-accounting-adr`. +Pull request: `https://github.com/6529-Collections/6529Stream/pull/32`. +Claude review request: issue comment `4666247442`. + +Goal: + +- Accept `docs/adr/0003-payment-accounting.md` before any P0 pull-payment, + emergency-withdrawal, or auction payment rewrite. +- Decide credit categories, owed-balance totals, surplus, withdrawal semantics, + failed-withdrawal behavior, forced/direct ETH handling, emergency withdrawal + limits, events, invariants, and test requirements. +- Link the ADR from the roadmap and ADR index. + +Created GitHub issues: + +- [#24](https://github.com/6529-Collections/6529Stream/issues/24) + `P0-PAY-ADR`: accept payment accounting design. +- [#25](https://github.com/6529-Collections/6529Stream/issues/25) + `P0-PAY-001`: add pull-payment accounting. +- [#26](https://github.com/6529-Collections/6529Stream/issues/26) + `P0-PAY-002`: add credit ledger storage and total-owed views. +- [#27](https://github.com/6529-Collections/6529Stream/issues/27) + `P0-PAY-003`: convert fixed-price payouts to credits. +- [#28](https://github.com/6529-Collections/6529Stream/issues/28) + `P0-PAY-004`: convert auction outbid refunds to credits. +- [#29](https://github.com/6529-Collections/6529Stream/issues/29) + `P0-PAY-005`: convert curator reward claims to credits. +- [#30](https://github.com/6529-Collections/6529Stream/issues/30) + `P0-PAY-006`: add withdrawal functions and failed-withdrawal behavior. +- [#31](https://github.com/6529-Collections/6529Stream/issues/31) + `P0-PAY-007`: bound emergency withdrawals by surplus. + +Related existing issues: + +- [#8](https://github.com/6529-Collections/6529Stream/issues/8) + `P0-PAY-008`: bound emergency withdrawals and prove owed-balance invariants. +- [#12](https://github.com/6529-Collections/6529Stream/issues/12) + `P0-AUCT-002`: fix auction bidding reentrancy and outbid refunds. +- [#22](https://github.com/6529-Collections/6529Stream/issues/22) + `P0-AUCT-001`: formalize auction custody, settlement, and lifecycle state + machine. + +Candidate files: + +- `docs/adr/0003-payment-accounting.md` +- `docs/adr/README.md` +- `ops/ROADMAP.md` +- `ops/AUTONOMOUS_RUN.md` + +Validation: + +- Markdown heading scan passed for `docs/adr/0003-payment-accounting.md`, + `docs/adr/README.md`, `ops/ROADMAP.md`, and `ops/AUTONOMOUS_RUN.md`. +- Payment traceability scan passed for issue #24, `P0-PAY-ADR`, + `0003-payment-accounting`, `totalAuctionBidEscrow`, + `totalRandomnessReserved`, `emergencyWithdrawable`, failed withdrawals, + forced ETH, and the intended payment test files. +- `git diff --check` passed. +- `make check` passed with 17 tests and known compiler/NatSpec warnings. +- `powershell -ExecutionPolicy Bypass -File scripts\check.ps1` passed with + 17 tests and known compiler/NatSpec warnings. +- GitHub CI run `27251741547` passed on head + `e160bda2fb0c9898ff05b6f35f90333f86a2f479`. + +Review feedback: + +- CodeRabbit completed successfully on head + `e160bda2fb0c9898ff05b6f35f90333f86a2f479` with no actionable comments and + all five pre-merge checks passing. +- Claude review was explicitly requested in issue comment `4666247442`, but + Claude returned `Code review skipped` because the organization's Claude Code + overage spend limit was reached. This is an external billing/admin condition, + so PR #32 proceeds under autonomous maintainer decision after local, + sidecar, CI, and CodeRabbit review. +- No inline review threads are open. ## Decision Log @@ -605,6 +703,15 @@ Review feedback: | 2026-06-10 03:03 | Address late Claude PR #20 comments in PR #23 | ADR 0001 now pins `DROP_ID_TYPEHASH` and closes remaining signed-field semantic gaps for payer, sale-mode price fields, and poster attribution | | 2026-06-10 03:08 | Address Claude PR #23 state-machine review | ADR 0002 now clarifies custody-confirmation trigger and `Created -> Active` derivation | | 2026-06-10 03:17 | Address Claude PR #23 follow-up review | ADR 0002 now specifies no-bid NFT claim fallback, auction recipient policy, and first-bid cancellation guard | +| 2026-06-10 03:22 | Merge PR #23 | Final head was CI-clean, CodeRabbit-clean, and visible Claude review threads were resolved | +| 2026-06-10 03:25 | Create payment accounting ADR issue | Issue #24 defines the required payment accounting decisions before pull-payment rewrites | +| 2026-06-10 03:32 | Draft payment accounting ADR | ADR 0003 chooses pull-payment accounting, owed-balance totals, surplus-only emergency withdrawal, forced-ETH handling, and payment invariants | +| 2026-06-10 03:38 | Validate payment accounting ADR locally | Heading, traceability, whitespace, `make check`, and Windows wrapper validations pass | +| 2026-06-10 03:42 | Create payment implementation issues | Issues #25 through #31 now track the payment ledger, fixed-price, auction refund, curator claim, withdrawal, and emergency surplus workstreams | +| 2026-06-10 03:44 | Revalidate payment ADR traceability | Staged whitespace, `make check`, and Windows wrapper validations pass after issue-link updates | +| 2026-06-10 03:45 | Open PR #32 | Payment accounting ADR is published with validation evidence | +| 2026-06-10 03:46 | Request Claude review on PR #32 | Explicit review ping added in issue comment `4666247442` because Claude may not run automatically | +| 2026-06-10 03:54 | Mark PR #32 merge-ready | CI passed, CodeRabbit reported no actionable comments, no inline review threads are open, and Claude is unavailable due to organization overage limits | ## Resume Instructions diff --git a/ops/ROADMAP.md b/ops/ROADMAP.md index 4556196d..b7012706 100644 --- a/ops/ROADMAP.md +++ b/ops/ROADMAP.md @@ -94,7 +94,7 @@ Required evidence: ### Gate B1: P0 Protocol Decisions Accepted -Status: Not Started. +Status: In Progress. Owner: TBD. Blocking issues: TBD. Evidence: TBD. @@ -340,7 +340,7 @@ contract changes until the relevant ADR is accepted. | --- | --- | --- | --- | --- | | Drop authorization | [`P0-AUTH-ADR`](https://github.com/6529-Collections/6529Stream/issues/17) | `docs/adr/0001-drop-authorization.md` | Gate B1, `P0-AUTH-*` | EIP-712 schema, recipient/payer policy, nonce model, replay protection, signer rotation, ERC-1271 stance | | Auction custody | [`P0-AUCT-ADR`](https://github.com/6529-Collections/6529Stream/issues/21) | `docs/adr/0002-auction-custody.md` | Gate B1, `P0-AUCT-*` | Token custody, settlement actor, no-bid semantics, transfer method, cancellation | -| Payment accounting | `P0-PAY-ADR` | `docs/adr/0003-payment-accounting.md` | Gate B1, `P0-PAY-*` | Pull credits, owed balances, surplus, withdrawals, emergency withdrawal limits | +| Payment accounting | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24) | `docs/adr/0003-payment-accounting.md` | Gate B1, `P0-PAY-*` | Pull credits, owed balances, surplus, withdrawals, emergency withdrawal limits | | Admin/governance | `P0-ADMIN-ADR` | `docs/adr/0004-admin-governance.md` | Gate B1, `P0-ADMIN-*` | Global/function/collection roles, signer lifecycle, pause controls, multisig expectations | | Randomness | [`P0-RAND-ADR`](https://github.com/6529-Collections/6529Stream/issues/14) | `docs/adr/0005-randomness.md` | Gate B1, `P0-RAND-*` | Provider choice, pending state, callback validation, retries, stale callback handling | | Metadata/freeze | `P1-META-ADR` | `docs/adr/0006-metadata-freeze.md` | Gate B2, `P1-META-*` | Pending/final metadata, frozen state, dependency immutability, burn metadata | @@ -361,7 +361,7 @@ This is the recommended first batch of issues. 5. `P0/M0`: Add Slither baseline table. 6. `P0-AUTH-ADR / P0/DESIGN`: ADR for drop authorization. 7. [`P0-AUCT-ADR`](https://github.com/6529-Collections/6529Stream/issues/21) / P0/DESIGN: ADR for auction custody. -8. `P0-PAY-ADR / P0/DESIGN`: ADR for payment accounting. +8. [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24) / P0/DESIGN: ADR for payment accounting. 9. `P0-ADMIN-ADR / P0/DESIGN`: ADR for admin/governance. 10. `P0-RAND-ADR / P0/DESIGN`: ADR for randomness. 11. `P1-META-ADR / P1/DESIGN`: ADR for metadata/freeze. @@ -713,7 +713,7 @@ Acceptance criteria: - Priority/severity/type: `P0 / Critical / CODE+TEST+DOCS`. - Blocks: Gate C. - Issue: [#12](https://github.com/6529-Collections/6529Stream/issues/12). -- Dependencies: `P0-PAY-ADR`, ADR 0002. +- Dependencies: [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24), ADR 0002. Problem: @@ -759,7 +759,8 @@ Acceptance criteria: - Priority/severity/type: `P0 / High / CODE+TEST+DOCS`. - Blocks: Gate C. -- Dependencies: `P0-PAY-ADR`. +- Issue: [#25](https://github.com/6529-Collections/6529Stream/issues/25). +- Dependencies: [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24). Problem: @@ -778,21 +779,29 @@ Intended behavior: Required code changes: -- Add accounting for poster credits, bidder credits, curator credits, protocol - surplus, total poster owed, total bidder owed, total curator owed, total owed, - and emergency withdrawable balance. +- Add accounting for poster credits, bidder credits, curator credits, curator + reserves, protocol credits, active auction bid escrow, randomness reserves, + protocol surplus, total poster owed, total bidder owed, total curator owed, + total reserved, total owed, and emergency withdrawable balance. - Add withdrawal functions. - Ensure failed withdrawal does not erase credit. Child tickets: -- `P0-PAY-002`: Add credit ledger storage and total-owed views. -- `P0-PAY-003`: Convert fixed-price poster/platform payouts to credits. -- `P0-PAY-004`: Convert auction outbid refunds to credits. -- `P0-PAY-005`: Convert curator reward claims to credits. -- `P0-PAY-006`: Add withdrawal functions and failed-withdrawal behavior. -- `P0-PAY-007`: Bound emergency withdrawals by surplus. -- `P0-PAY-008`: Add payment invariants and forced-ETH tests. +- [`P0-PAY-002`](https://github.com/6529-Collections/6529Stream/issues/26): + Add credit ledger storage and total-owed views. +- [`P0-PAY-003`](https://github.com/6529-Collections/6529Stream/issues/27): + Convert fixed-price poster/platform payouts to credits. +- [`P0-PAY-004`](https://github.com/6529-Collections/6529Stream/issues/28): + Convert auction outbid refunds to credits. +- [`P0-PAY-005`](https://github.com/6529-Collections/6529Stream/issues/29): + Convert curator reward claims to credits. +- [`P0-PAY-006`](https://github.com/6529-Collections/6529Stream/issues/30): + Add withdrawal functions and failed-withdrawal behavior. +- [`P0-PAY-007`](https://github.com/6529-Collections/6529Stream/issues/31): + Bound emergency withdrawals by surplus. +- [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8): + Add payment invariants and forced-ETH tests. Required tests: @@ -810,8 +819,11 @@ Required docs: Acceptance criteria: -- `totalOwed == totalPosterOwed + totalBidderOwed + totalCuratorOwed`. -- `address(this).balance >= totalOwed` except where forced ETH creates surplus. +- `totalOwed == totalPosterOwed + totalBidderOwed + totalCuratorOwed + + totalCuratorReserved + totalProtocolOwed + totalAuctionBidEscrow + + totalRandomnessReserved + otherContractSpecificReserved`. +- `address(this).balance >= totalOwed`; direct or forced ETH may make + `address(this).balance > totalOwed` by creating surplus. - `emergencyWithdrawable == address(this).balance - totalOwed`. - No withdrawal can reduce another user's owed balance. @@ -1648,6 +1660,10 @@ Status values: `Missing`, `Planned`, `In Progress`, `Passing`, `Blocked`. | ERC-1271 decision | ERC-1271 mock signer passes or contract signer rejected | `test/StreamDropsERC1271.t.sol` | Missing | [`P0-AUTH-003`](https://github.com/6529-Collections/6529Stream/issues/19) | Gate B1/Gate C | TBD | | Auction reentrancy | Malicious bidder cannot reenter bid/withdraw flows | `test/StreamAuctionReentrancy.t.sol` | Missing | [`P0-AUCT-002`](https://github.com/6529-Collections/6529Stream/issues/12) | Gate C | TBD | | Outbid refund failure | Previous bidder credited even if receiver reverts | `test/StreamAuctionPayments.t.sol` | Missing | [`P0-AUCT-002`](https://github.com/6529-Collections/6529Stream/issues/12) | Gate C | TBD | +| Payment ledger totals | Poster, bidder, curator, curator reserve, protocol, total owed, surplus, and emergency-withdrawable views follow ADR 0003 | `test/StreamPayments.t.sol` | Missing | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24), [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8) | Gate C/Gate D | TBD | +| Withdrawal failure behavior | Failed withdrawal preserves account credit and category totals | `test/StreamPayments.t.sol` | Missing | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24), [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8) | Gate C | TBD | +| Emergency surplus boundary | Emergency withdrawal can withdraw only surplus and cannot withdraw owed or reserved funds | `test/StreamEmergencyWithdraw.t.sol` | Missing | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24), [`P0-PAY-007`](https://github.com/6529-Collections/6529Stream/issues/31), [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8) | Gate C/Gate D | TBD | +| Randomness reserve accounting | Randomizer provider reserves are not emergency-withdrawable surplus | `test/StreamRandomizerPayments.t.sol` | Missing | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24), [`P0-PAY-007`](https://github.com/6529-Collections/6529Stream/issues/31), [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8), [`P0-RAND-ADR`](https://github.com/6529-Collections/6529Stream/issues/14) | Gate C/Gate D | TBD | | Auction custody failure | Auction settlement succeeds only with explicit custody/approval | `test/StreamAuctionCustody.t.sol` | Initial auction mint custody characterization exists in `test/StreamDropsCharacterization.t.sol` and `test/StreamDropsIntegrationCharacterization.t.sol`; settlement tests missing | [`P0-AUCT-001`](https://github.com/6529-Collections/6529Stream/issues/22) | Gate B1/Gate C | TBD | | No-bid settlement ambiguity | No-bid settlement ownership follows ADR | `test/StreamAuctionSettlement.t.sol` | Missing | [`P0-AUCT-001`](https://github.com/6529-Collections/6529Stream/issues/22) | Gate B1/Gate C | TBD | | Admin selector mismatch | Wrong function selector cannot authorize mutation | `test/StreamAdminSelectors.t.sol` | Initial characterization exists in `test/StreamCoreAdminCharacterization.t.sol`; P0 fix tests missing | `P0-ADMIN-001` | Gate C | TBD | @@ -1659,7 +1675,7 @@ Status values: `Missing`, `Planned`, `In Progress`, `Passing`, `Blocked`. | Curator double claim | Valid claim succeeds once and second claim fails | `test/StreamCuratorsPool.t.sol` | Missing | `P1-CURATOR-*` | Gate D | TBD | | Merkle leaf ambiguity | Duplicate or ambiguous leaves cannot double claim | `test/StreamCuratorsMerkle.t.sol` | Missing | `P1-CURATOR-*` | Gate D | TBD | | Burn accounting | Burned-token supply and metadata follow ADR | `test/StreamCoreBurn.t.sol` | Missing | `P1-META-*` | Gate D | TBD | -| Forced ETH accounting | Forced/direct ETH does not corrupt owed/surplus accounting | `test/StreamPaymentsInvariant.t.sol` | Missing | [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8) | Gate C/Gate D | TBD | +| Forced ETH accounting | Forced/direct ETH does not corrupt owed/surplus accounting | `test/StreamPaymentsInvariant.t.sol` | Missing | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24), [`P0-PAY-008`](https://github.com/6529-Collections/6529Stream/issues/8) | Gate C/Gate D | TBD | ## Appendix C: ADR Index @@ -1667,7 +1683,7 @@ Status values: `Missing`, `Planned`, `In Progress`, `Passing`, `Blocked`. | --- | --- | --- | --- | --- | | 0001 Drop authorization | [`P0-AUTH-ADR`](https://github.com/6529-Collections/6529Stream/issues/17) | Accepted | `docs/adr/0001-drop-authorization.md` | Gate B1, `P0-AUTH-*` | | 0002 Auction custody | [`P0-AUCT-ADR`](https://github.com/6529-Collections/6529Stream/issues/21) | Accepted | `docs/adr/0002-auction-custody.md` | Gate B1, `P0-AUCT-*` | -| 0003 Payment accounting | `P0-PAY-ADR` | Missing | `docs/adr/0003-payment-accounting.md` | Gate B1, `P0-PAY-*` | +| 0003 Payment accounting | [`P0-PAY-ADR`](https://github.com/6529-Collections/6529Stream/issues/24) | Accepted | `docs/adr/0003-payment-accounting.md` | Gate B1, `P0-PAY-*` | | 0004 Admin/governance | `P0-ADMIN-ADR` | Missing | `docs/adr/0004-admin-governance.md` | Gate B1, `P0-ADMIN-*` | | 0005 Randomness | [`P0-RAND-ADR`](https://github.com/6529-Collections/6529Stream/issues/14) | Missing | `docs/adr/0005-randomness.md` | Gate B1, `P0-RAND-*` | | 0006 Metadata/freeze | `P1-META-ADR` | Missing | `docs/adr/0006-metadata-freeze.md` | Gate B2, `P1-META-*` |