Skip to content

New launch model: Shards — locked bonding-curve market for a fixed on-chain-art NFT collection - #43

Open
jesse-stahl wants to merge 18 commits into
programmablehq:mainfrom
jesse-stahl:model/shards-v1
Open

New launch model: Shards — locked bonding-curve market for a fixed on-chain-art NFT collection#43
jesse-stahl wants to merge 18 commits into
programmablehq:mainfrom
jesse-stahl:model/shards-v1

Conversation

@jesse-stahl

Copy link
Copy Markdown

Submission type

  • New launch model
  • Existing model change
  • Tests, documentation or tooling

Change

Shards is a launch model for on-chain generative art: each launch deploys a fresh hook, ERC-20, ERC-721 and renderer for one creator's fixed 10,000-piece collection, market-made on a single-sided bonding curve whose liquidity is permanently locked. Art regenerates on every pool acquisition and never on a wallet-to-wallet transfer, so a piece's history is part of the artwork. It serves creators who want a collection with a built-in, always-on market instead of a mint-then-hope drop, and traders who want continuous exit liquidity. It belongs in Programmable as a distinct model shape: an NFT-collection market rather than a fungible-token launch.

For a new launch model:

  • Model name: Shards
  • Builder GitHub identity: jesse-stahl
  • Builder beneficiary address: 0xceeBB3A6543CeBEB2ED66963897A0abEA52A50cC
  • Source paths: src/ShardHookV1.sol, src/ShardFeeDistributorV1.sol, src/ShardNFTV1.sol, src/ShardTokenV1.sol, src/GeometricRendererV1.sol, src/ShardSwapRouterV1.sol, src/ShardFeeForwarderV1.sol, src/ShardConstantsV1.sol, src/ShardErrorsV1.sol, src/interfaces/
  • Model documentation: models/shards/README.md, models/shards/SECURITY.md, models/shards/TEST_PLAN.md, models/shards/model.json, spec/shards-v1.json

Model behavior

  • Pool shape and assets: one native-ETH/SHARD v4 pool per launch; ETH is currency0. All 10,000 SHARD (each backing one NFT) are locked into a full-range plus concentrated-band single-sided position at initialise(). There is no withdrawal path for the liquidity.
  • Hook permissions and return deltas: beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. The swap deltas take the 1.00% fee on the ETH leg; beforeInitialize validates the pool key and exact start price so a front-runner cannot initialise the canonical pool on different terms.
  • Fee paths, rounding and accounting: a 1.00% inclusive fee on the ETH each swap or hook-market trade moves (exact-in: gross * 100 / 10000; exact-out: net * 100 / 9900). At accrual it splits: fee * 1000 / 10000 to the builder beneficiary, the same to Programmable's recipient, and the remainder — including all rounding dust — to collection holders via a running per-NFT accumulator. That is 0.80% / 0.10% / 0.10% of swap volume. Fees are native ETH, held by the hook until claimed. Donations via donate() reach holders whole and are never split.
  • External calls and dependencies: Uniswap v4 PoolManager, the model's own SHARD/NFT/renderer contracts, a gas-capped arbBlockNumber() staticcall used for entropy, and raw ETH transfers for refunds and claims. Dependencies are this repository's pinned libs; compiler fixed at solc 0.8.26 (spec records lib commits and bytecode size).
  • Privileged roles, upgradeability, autonomous actions: none of the latter two. Roles: the deployer (one-shot setNFT + initialise), builderFeeRecipient (claims its share; may hand the role on via setBuilderFeeRecipient), the immutable launcherFeeRecipient (claims Programmable's share), and holder claimants. No admin keys, no pausing, nothing upgradeable.
  • Expected invariants and failure modes: hook ETH custody covers escrow + holder claims + both beneficiary accruals; shard.balanceOf(hook) == circulating * 1e18 + seedDust; builderCut + launcherCut + holderAmount == fee exactly; beneficiary-only claims. Failure modes and boundaries (per-swap size cap, partial-fill rejection, 188 bytes of EIP-170 headroom, no factory yet) are documented in models/shards/SECURITY.md.

Verification

  • node scripts/verify-model-registry.mjs
  • node scripts/verify-release-evidence.mjs
  • forge fmt --check
  • forge build
  • FOUNDRY_PROFILE=ci forge test (--no-match-contract ClassicV3MainnetForkTest, as in verify.yml: 393 passed, 0 failed; the fork suite needs the CI RPC secret)
  • Gas snapshot changes are intentional and explained
  • Unit and integration coverage is included
  • Fuzz and invariant coverage is included where applicable
  • Compiler and dependency versions are fixed
  • Model documentation states trust assumptions and known limitations
  • Deployment evidence is updated when addresses or runtime code change (n/a — design status, no deployment; contracts: [], deployment: null)

Gas-snapshot note: all new entries are Shard*V1/GeometricRendererV1. Seven pre-existing fuzz entries changed only in their μ column (medians identical, deltas under 0.1%) because new contracts in the artifact set alter fuzz input sampling; regenerated with the pinned seed and the exact verify.yml command.

Test-plan notes recorded honestly in models/shards/TEST_PLAN.md: no suite yet asserts EthTransferFailed for a reverting recipient on the claim/refund paths, and the source repo's deploy-script tick-validation tests were not ported because no deploy script ships in this submission — both are listed as required before release, alongside a mainnet-fork lifecycle test.

Security

New hook permissions: the five listed above, all disclosed in models/shards/README.md with the return-delta quadrants. New accounting paths: the 80/10/10 split at _distributeFee, the two beneficiary accruals with beneficiary-only claims, and the holder accumulator with escrow/dust carry. External calls: PoolManager, the model's own contracts, the entropy staticcall, raw ETH sends. Trust assumptions: no admin keys; deployer power ends after setNFT + initialise; the builder payout address is builder-controlled; Programmable's recipient is immutable. Details in models/shards/SECURITY.md.

No undisclosed vulnerability is included.

Submission terms

For a new launch model:

  • I have read the Hook Builder Program
  • I used the model template or provided equivalent registry, security and test-plan records
  • I have the right to submit this code under the repository's MIT License
  • Required notices for third-party code are included
  • I understand that a pull request does not guarantee acceptance, deployment, volume or revenue
  • I understand that any builder allocation applies only after an acceptance record identifies the exact model version, commit and beneficiary

dependabot Bot and others added 8 commits July 29, 2026 13:01
Updates checkout and Foundry Action pins after Verify, Security and Ethereum Evidence passed.
## What changed

Adds the candidate Protocol Revenue Deepener for the canonical `$V4` /
ETH pool.

- converts bounded ETH batches into a balanced full-range position
- permanently keeps the position under an ownerless, add-only contract
- enforces a six-hour cooldown and delayed price observation
- validates the exact Mainnet pool, hook and fee disclosure in the
constructor
- accepts permissionless maintenance without caller-controlled amounts
- documents the control model, limitations and migration boundary

## Validation

- complete Forge suite
- CI fuzz tests
- stateful invariant suite
- Mainnet-fork compound against the canonical pool
- Slither review with no unresolved high-severity finding
- model registry and release-evidence checks

This pull request publishes a deployment candidate. It does not claim a
Mainnet deployment or active keeper.
## What changed

- Publish the exact seven-contract Classic Mainnet source set.
- Add unit, fuzz, invariant and Mainnet-fork lifecycle tests.
- Publish the fixed classic-v3 specification and complete deployment
evidence.
- Remove the cancelled Protocol Revenue Deepener candidate.
- Keep network-backed fork evidence separate from deterministic CI.
- Document every current Slither finding and its manual disposition.

This pull request publishes the source record only. Classic remains
pointed at classic-v2 until a follow-up pull request binds the model
registry to the resulting main-branch commit.

## Verification

- 120 deterministic tests passed.
- 1 complete Mainnet-fork lifecycle passed.
- All source and test files match the deployed release snapshot.
- Registry, release evidence, documentation links, gas snapshot and
workflow lint passed.

Classic has not received an independent smart-contract audit or public
security contest.
This marks the verified Classic Ethereum deployment as the current
available release.

The activation is bound to public source commit
ae5e757. It updates the model registry,
deployment record, security properties and release evidence without
changing deployed bytecode.

Verified before publication:

- 120 deterministic Foundry tests
- 1 complete Mainnet-fork lifecycle test
- 8 recorded runtime code hashes matched Ethereum
- model registry, release evidence, documentation links and SHA-256
checksums
- formatting, build sizes and gas snapshot

All seven Programmable release contracts are Etherscan exact matches and
Sourcify matches. No independent audit or public security contest is
claimed.
Records the live production interface, deployed source tag, verification status, lifecycle evidence and remaining external review boundaries.
Bonding-curve market for a fixed 10,000-piece on-chain-art NFT
collection. The 1.00% native-ETH swap fee splits at accrual:
0.80% to collection holders, 0.10% to the builder beneficiary,
0.10% to Programmable, with rounding dust kept by holders.
Donations reach the holder pool whole.
393 tests pass under the CI profile. Existing gas-snapshot entries
for other models drift only in fuzz means (medians unchanged): new
contracts in the artifact set alter fuzz input sampling.
Copilot AI review requested due to automatic review settings July 31, 2026 16:13

Copilot AI 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.

Pull request overview

Adds the new Shards launch model to the Programmable codebase: a fixed-supply ERC-20 (“SHARD”) + fixed 10,000-piece ERC-721 collection whose primary market is a locked, single-sided Uniswap v4 bonding-curve hook, with native-ETH fee capture and an 80/10/10 (holders/builder/Programmable) split.

Changes:

  • Introduces the Shards onchain contracts (hook, ERC-20, ERC-721, fee accumulator/splitter, router, donation forwarder) plus the v1 spec record.
  • Adds extensive unit/integration/fuzz/invariant coverage for market paths, inventory rules, fee accounting, and locked-liquidity properties.
  • Registers the model in the repo’s model registry and documentation index.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/ShardHookV1.sol Core Uniswap v4 hook implementing locked liquidity + market paths + fee capture/splitting.
src/ShardFeeDistributorV1.sol Holder fee accumulator with escrow/dust carry and same-block accrual guard; includes builder/launcher accrual bookkeeping.
src/ShardFeeForwarderV1.sol Minimal “plain ETH in → donate()” forwarder for holder donations.
src/ShardNFTV1.sol ERC-721 “archive” inventory + seed-based art lifecycle rules (regenerate on acquire, preserve on transfers).
src/ShardSwapRouterV1.sol Minimal ETH↔SHARD third-party router intended to hold no funds.
src/ShardTokenV1.sol Fixed-supply ERC-20 token contract (10,000e18 minted at construction).
src/ShardConstantsV1.sol Model constants (supply, fee bps, tick spacing, seed split, precision).
src/ShardErrorsV1.sol Centralized custom errors for Shards contracts.
src/interfaces/IShardHookV1.sol Minimal hook interface for NFT transfer settlement + claimable view.
src/interfaces/IShardNFTV1.sol Minimal NFT interface for hook-driven acquire/release + supply views.
src/interfaces/IShardRendererV1.sol Renderer interface for live/dormant SVG and attributes.
test/ShardTokenV1.t.sol ERC-20 supply/metadata/no-mint-no-burn tests.
test/ShardSwapRouterV1.t.sol Router swap/refund/deadline/minOut/approval tests.
test/ShardScaffoldV1.t.sol Dependency “surface area” compile checks (imports/types/constants).
test/ShardNFTV1.t.sol Archive mechanics, seed rules, transfer guards, tokenURI behaviors.
test/ShardLaunchSequenceV1.t.sol Launch ordering, tick price sanity, one-shot wiring/initialise checks.
test/ShardHookMarketV1.t.sol Hook-market buy/sell/redeem/claim behaviors and fee basis assertions.
test/ShardHookLiquidityV1.t.sol initialise(), seeding, hook permissions/address bits, lock/no-withdrawal assertions.
test/ShardHookExhaustionV1.t.sol Thin-curve exhaustion/short-fill behavior and buyMax fee-basis regression coverage.
test/ShardFeeSplitV1.t.sol 80/10/10 split arithmetic, beneficiary claims, recipient handover tests.
test/ShardFeeDonationV1.t.sol donate() and forwarder flush behavior; donation bypasses split.
test/ShardFeeDistributorV1.t.sol Accumulator/escrow/dust settlement paths and conservation/solvency checks.
test/ShardHookBatchV1.t.sol Batch buy/sell/redeem paths and MAX_BATCH behavior.
test/ShardHookFeesV1.t.sol Third-party swap fee capture across quadrants + swap cap behaviors.
test/ShardHookAttackV1.t.sol Adversarial-path tests (reentrancy/withdrawal/front-run/callback abuse, etc.).
test/invariant/ShardHandlerV1.sol Invariant-test handler driving randomized sequences.
test/invariant/ShardV1.t.sol Stateful invariants for backing, solvency/custody, and lock permanence.
test/GeometricRendererV1.t.sol Renderer determinism, external-reference avoidance, trait distribution tests.
spec/shards-v1.json Versioned spec record (parameters, permissions, fee math, pinned deps).
models/shards/README.md Model-level documentation of behavior, lifecycle, economics, and release gates.
models/shards/SECURITY.md Security assumptions/invariants/limitations for the model.
models/shards/TEST_PLAN.md Test-plan mapping from suites to owned behaviors and stated gaps.
models/shards/model.json Model manifest entry (status/design gates, docs pointers).
models/registry.json Adds shards model entry and updates registry timestamp.
MODELS.md Adds Shards section to the repo’s model index.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/ShardScaffoldV1.t.sol Outdated
Comment thread src/ShardFeeForwarderV1.sol
Comment thread src/ShardSwapRouterV1.sol
Comment thread src/ShardHookV1.sol Outdated
Comment thread src/ShardHookV1.sol Outdated
Comment thread models/shards/README.md Outdated
Comment thread models/shards/README.md
Comment thread models/shards/TEST_PLAN.md Outdated
Comment thread models/shards/SECURITY.md Outdated
Comment thread models/registry.json Outdated
- reword NatSpec references to files that only exist in the reference
  repo; document that arbBlockNumber() contributes zero entropy on
  Ethereum mainnet (fails closed) in both code and SECURITY.md
- correct the holder-claim payout description and the poolKey
  immutability wording in README/SECURITY
- state the fuzz bound of testFuzz_split_conserves accurately
- disclose the creator-share deviation for the acceptance record
- restore the pre-existing registry entry order (append shards only)
- drop the unused FlushFailed error; MIT-license the scaffold test
# Conflicts:
#	.gas-snapshot
#	README.md
#	models/registry.json
#	releases/classic-v3/manifest.json
Shards already declared Ethereum (chainId 1) and its Arbitrum-only entropy
source degrades to zero off Arbitrum, but nothing exercised the model against
the real Uniswap v4 PoolManager it would market-make on. Add
ShardV1MainnetForkTest, which mines and deploys the hook against the pinned
canonical v4 deployment on a Mainnet fork, wires and initialises the locked
position, then drives the full lifecycle: a first-batch buy, a capped
third-party swap, a redeem, a hook-market sell, all three claim paths and a
donation, asserting the backing invariant survives every step. It pins the
PoolManager runtime code hash and confirms art still regenerates when the
entropy precompile returns zero.

The suite needs an archive RPC, so exclude it from the default CI test and
gas-snapshot runs exactly as the Classic fork suite is excluded. Update the
model README, TEST_PLAN and release gates to record the coverage as delivered.
@jesse-stahl

Copy link
Copy Markdown
Author

Updated this branch to make the Shards model exercisable on Ethereum Mainnet and to clear the merge conflicts with main.

Mainnet v4 coverage

  • Added test/ShardV1MainnetFork.t.sol: mines and deploys the hook against the pinned canonical Uniswap v4 PoolManager on a Mainnet fork, wires + initialises the locked position, then drives the full lifecycle — first-batch buy, a capped third-party swap, redeem, hook-market sell, all three claim paths (holder / builder / launcher) and a donation — asserting the backing invariant after every step.
  • Pins the PoolManager runtime code hash so the fork can't silently swap in a different contract, and confirms art still regenerates when the Arbitrum-only entropy precompile returns zero on Mainnet.
  • The suite needs an archive RPC, so it is excluded from the default forge test and forge coverage runs exactly as ClassicV3MainnetForkTest is. Run it with forge test --match-contract ShardV1MainnetForkTest.
  • Updated the model README, TEST_PLAN and release gates to record this coverage as delivered.

Merge conflicts

  • Merged upstream/main; resolved README.md, models/registry.json, releases/classic-v3/manifest.json and regenerated .gas-snapshot deterministically (fixed fuzz seed).

Full local gate is green: forge fmt --check, forge build --sizes (hook 24,388 bytes / 188 bytes EIP-170 headroom), 393 tests, gas-snapshot check, registry/evidence/doc-link scripts, and actionlint.

The coverage job runs every non-fork test to measure source coverage; the
Shards fork suite needs an archive RPC and must be excluded there too, exactly
as ClassicV3MainnetForkTest already is.
Add the Shards fork suite to the scheduled-and-on-PR Ethereum Evidence
workflow, alongside the Classic one and behind the same public-RPC retry
loop, so the Mainnet-v4 lifecycle is exercised by CI rather than only on
demand. Rework the entropy test to assert the load-bearing facts — that the
acquisition mints at all (the absent ARB_SYS precompile degrades to zero
instead of reverting the gas-capped staticcall) and that the two pieces render
different on-chain art — since seeds always differ via the per-acquisition
nonce and so proved nothing on their own.

@programmable-infra programmable-infra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CHANGES REQUIRED — Reviewed PR head f3ec992. Build, 360/360 tests including fork coverage, 18 invariants, PoolManager authentication, settlement and the fee split passed. Required fixes: replace the manual two-step hook/NFT wiring with an atomic factory and exact bidirectional NFT-to-hook validation so setNFT cannot bind an unintended contract; complete the model, specification, release and deployment metadata; use checked ERC20 transfers; and provide reproducible mined-address and fork launch evidence. Platform routing and UI/indexer work remains separate. Every new commit requires re-review.

@jesse-stahl

Copy link
Copy Markdown
Author

Review fixes pushed — 0c32d57

This commit lands the review-hardening work directly on this PR (fast-forward over f3ec992). No new/stacked PR; all work is here.

No Mainnet deployment occurred. Shards remains design. No status was changed to candidate/available; no deployment, source-freeze binding, or release-evidence work is included. Those gates require separate maintainer authorization and external evidence.

What changed

  • Atomic ShardLaunchFactoryV1 — single-transaction launch: CREATE2-deploys SHARD, hash-pinned hook, and NFT, wires them, seeds the hook, and initialises the pool; any failure reverts the whole transaction.
  • Hash-pinned hook creation code — factory verifies keccak256(hookCreationCode) against an immutable constructor-supplied hash; prediction helpers take the actual creation-code bytes (no prefix-hash composition).
  • Full-configuration token salteffectiveTokenSalt binds the raw token salt, hook salt, ticks, start price, and builder recipient, so a changed configuration cannot consume the intended token address (P1 fix).
  • CREATE2 NFT deployment — NFT address is CREATE2(keccak256(hook)), stable across unrelated launches and no longer nonce-raceable (P1 fix). Prediction order token→hook→nft is acyclic.
  • Exact NFT↔hook wiring — normalized WrongNFT for missing getter, malformed returndata, or wrong hook.
  • Checked ERC20 returns on all five production transfer paths (TokenTransferFailed).
  • Ethereum-only art seed — ArbSys/_arbBlockNumber removed; NatSpec states this is not secure randomness.
  • Reproducible launch script + runbook, candidate release records, numbered security properties, reconciled prose (fixed-art rule, core-vs-helper scope).

Verification

  • Fixed-seed non-fork gate: 446 passed. CI profile: 446 passed.
  • Shards invariants: 19 passed (CI: 1,000 runs, 128,000 calls/invariant, zero handler reverts).
  • Mainnet fork: 4/4 passed at block 25,639,000.
  • Coverage: lines 92.99% (2003/2154), functions 90.48% (285/315), branches 58.24% (272/467).
  • Slither 0.11.5: 139 contracts, no new actionable finding after review. actionlint: passed.
  • Model registry, release evidence, doc links, forge fmt --check, build, gas snapshot, and existing Mainnet bytecode checks passed.
  • Sizes: hook runtime 24,352 B; factory runtime 18,493 B; factory deploy initcode 35,834 B (all under EIP-170 / EIP-3860).
  • Gas (local): factory 7,147,357; launch 9,156,093. Fork: factory 7,180,480; launch 8,667,331.
  • Local Anvil rehearsal: predictions run twice matched SHARD/hook/NFT/config; duplicate launch reverted without changing evidence.

Regression coverage for the two P1 fixes

  • A changed configuration no longer consumes the intended token address — covered by same-salt/changed-param token-divergence tests in test/ShardLaunchFactoryV1.t.sol.
  • NFT address/config hash no longer raceable — CREATE2 stability tests across unrelated launches in the same suite.

…ng verify.yml

The fork suite now self-skips when ETHEREUM_RPC_URL is unset, so the default
forge test/snapshot run no longer needs a --no-match-contract entry in the
shared verify.yml workflow. Restores verify.yml to the base-branch content and
regenerates the fixed-seed gas snapshot for the wider default test set.
@jesse-stahl

Copy link
Copy Markdown
Author

CI follow-up — bbe4dce

The public-intake check was red on 0c32d57 because the PR diff still carried a change to .github/workflows/verify.yml (from an earlier branch commit that excluded the new fork suite from default CI). The intake classifier rejects a PR that touches a builder-maintenance path alongside model source.

bbe4dce resolves it without losing coverage:

  • Reverted verify.yml to the base-branch content, so the PR no longer touches any builder-maintenance path — the classifier now returns no-op. public-intake is green.
  • ShardV1MainnetForkTest now self-skips when ETHEREUM_RPC_URL is unset, so the default forge test/forge snapshot run keeps it out of normal CI without editing the shared workflow. The dedicated mainnet-evidence.yml workflow (which sets ETHEREUM_RPC_URL) still runs the full fork suite — no fork coverage is lost.
  • Regenerated the fixed-seed .gas-snapshot for the wider default test set; forge snapshot --check passes.

Local CI-equivalent gate on bbe4dce, all green: forge fmt --check; forge build --sizes (hook 24,352 B, factory 18,493 B); FOUNDRY_PROFILE=ci forge test --no-match-contract ClassicV3MainnetForkTest → 446 passed, 1 skipped; forge snapshot --check clean; model-registry / release-evidence / doc-link validators pass. No production Solidity changed in this commit, so the prior Slither/coverage evidence still holds.

The Verify, Security, and Ethereum Evidence workflows show action_required — they need a maintainer to approve workflow runs on this fork PR. Still no Mainnet deployment; Shards remains design.

@programmable-infra programmable-infra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Programmable verification: CHANGES REQUIRED

Reviewed immutable PR head: bbe4dce

Shards is technically advanced and includes real atomic launch code, but two fee-policy gaps and an incomplete immutable launch binding remain.

Applicant changes, in priority order:

  1. Make the Programmable 10 bps entitlement cumulative and split-invariant. ShardHookV1.sol:422-425 and ShardFeeDistributorV1.sol:69-80 floor independently. Ten 900-wei swaps currently produce zero Programmable fee while one aggregated 9,000-wei swap produces nine wei. Add carried remainder and exact-output gross-up tests across all four quadrants.
  2. Bind the immutable recipient to 0x4957f49620AFf3Adbbe8195a4f633E49cc93376c. ShardLaunchFactoryV1.sol:85-93 accepts an arbitrary recipient and releases/shards-v1/mainnet-manifest.json still leaves it null. Keep the admin wallet separate.
  3. Close the exact data-only launch plan. models/shards/model.json still lists contracts as empty and the mainnet manifest contains null/not-run/releaseEligible:false fields. Bind deployer/factory, salts, expected addresses, configuration hash, builder, treasury, transaction order, and postconditions.
  4. Regenerate the fee specification and regression evidence, then provide a maintained pinned mainnet-fork lifecycle run. The local archive RPC limitation is tooling-blocked, not counted as a contract failure.

Passed: real CREATE2 factory/script, permission and occupancy checks, atomic rollback, PoolManager authentication, five exact permission bits, canonical PoolKey, all swap quadrants, partial-fill fail-closed, claims/settlement, permanent add-only liquidity, no admin/upgrade/withdraw path, 447 passing tests plus 19 invariants, and no reproduced rug/drain issue. One RPC fork test was skipped.

Estimated applicant work: 1-3 engineering days. Re-review: 3-6 hours with working archive RPC.

Platform work after applicant pass: custom model adapter/compiler, registry/UI/API/indexer/trading/claim integration, deployment authorization, runtime readback, monitoring, and signed final verification.

A normal user cannot launch a directly tradable custom coin from this exact revision. Any new commit invalidates this reviewed SHA and must be reverified.

Take the combined builder+launcher (20%) cut of each swap fee with a carried
remainder so a stream of tiny swaps can no longer floor the entitlement to zero;
split it evenly with the odd wei carried to the launcher. Bind the launcher
(Programmable 0.10%) recipient to an immutable constant and drop it from the
factory constructor. Deploy the factory through the canonical CREATE2 proxy so
its address is nonce-independent, and pin the full data-only launch plan. Add
four-quadrant split coverage, regenerate the fee spec, security properties,
runbook and mainnet-fork evidence.
@jesse-stahl

Copy link
Copy Markdown
Author

Review fixes pushed — 0bc629a

All four required changes are addressed. No Mainnet deployment occurred and Shards remains design. Every predicted address/salt/hash below is pinned to this exact revision; any further source change re-mines them.

1. Programmable 10 bps entitlement is now cumulative and split-invariant.
ShardFeeDistributorV1._distributeFee previously floored the builder and launcher cuts independently — confirmed: ten 900-wei-fee swaps paid 0, one 9,000-wei swap paid 9. It now takes the combined 20% operator cut with a carried remainder (operatorFeeRemainder) and splits it evenly, carrying the odd wei to the launcher (operatorSplitParity). Proven no-underflow: operatorCut = (amount·2000 + rem)/10000 ≤ amount for every amount, so the holder remainder never goes negative. Guarantees hold every call: builderCut + launcherCut + holderAmount == fee; both cuts stay within one wei of the ideal cumulative 10%; launcher never shorted below builder in absolute accrual.

  • Tests: ShardFeeSplitV1.t.sol::test_tinyFeesAccumulateToTheSameEntitlement (the exact evasion scenario, now equal), ::testFuzz_splitIsConservativeAndCumulative (conservation + ±1 over any stream, 1000 runs), ::testFuzz_split_conserves; ShardHookFeesV1.t.sol::test_operatorSplitHoldsAcrossAllFourQuadrants (exact-output gross-up across zeroForOne/oneForZero × exactIn/exactOut), ::testFuzz_theSplitConservesEveryFee; and the invariant ShardV1.t.sol::invariant_builderAndLauncherCutsMatch (1000 runs / 128k calls).

2. Launcher recipient bound to 0x4957f49620AFf3Adbbe8195a4f633E49cc93376c.
It is now a compile-time constant in ShardLaunchFactoryV1 (the same address Classic v3 uses) and was removed from the constructor — the factory cannot route the launcher share anywhere else. The builder 0.10% recipient stays per-launch. The admin/deployer wallet is separate (0x2Bb3…249E).

  • Test: ShardLaunchFactoryV1.t.sol::test_launcherRecipientIsBoundToTheProgrammableConstant.

3. Data-only launch plan closed in releases/shards-v1/mainnet-manifest.json (candidatePlan). The factory is deployed through the canonical CREATE2 proxy 0x4e59…4956C, so its address is nonce-independent. Pinned: deployer 0x2Bb3…249E, proxy, factory salt, expected factory 0x3624dd02…9B09, renderer 0x80c5E72d…Bd5E, launcher/treasury 0x4957…376c, builder 0xceeB…50cC, raw/effective token salt, hook salt 0x…5f0a, predicted SHARD 0xb9d88A…FD58 / hook 0x5625aE…A0cc / NFT 0x2af33A…07a5, configuration hash 0x97a77a…0712, transaction order and postconditions. Regenerated all factory/hook artifact sizes and hashes.

4. Fee spec, regression and maintained fork evidence regenerated. spec/shards-v1.json, models/shards/{SECURITY,README,TEST_PLAN}.md, and docs/security/SHARDS_PROPERTIES.md now describe the carried-remainder split and the immutable launcher (old round-down/dust wording removed). The runbook is rewritten for the CREATE2-proxy flow. Maintained Mainnet-fork lifecycle run (the local-archive-RPC skip in CI is tooling-blocked, not a contract failure):

ETHEREUM_RPC_URL=https://eth.drpc.org forge test --match-contract ShardV1MainnetForkTest
  4 passed; block 25639000; factory gas 7,176,738; launch gas 8,532,815
  keccak256(type(ShardHookV1).creationCode) = 0x64b5559584…3b1e53

Full gate on 0bc629a: forge fmt --check; forge build --sizes (hook runtime 24,420 B, factory 18,455 B); FOUNDRY_PROFILE=ci forge test → 449 passed, 1 skipped, 0 failed; invariants 19 (1000 runs / 128k calls, 0 handler reverts); gas snapshot --check clean; Slither 0.11.5 (139 contracts, no new actionable finding on the changed code); coverage lines 93.00% / functions 90.48% / branches 58.24% (all above floors).

Honest residual limitation: the pool-level 1% fee (_feeOn) still floors — a swap whose gross ETH is below 100 wei yields a zero total fee, because pool claims are integer wei. This is economically negligible and is not the split-flooring flagged; it is disclosed in spec/shards-v1.json and SECURITY.md.

@programmable-infra programmable-infra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Programmable verification: CHANGES REQUIRED

Reviewed immutable PR head: 0bc629a

The four changes requested on the prior SHA are substantively addressed: the Programmable recipient is fixed, the inner operator split carries its remainder, and the CREATE2 launch plan is pinned. Two applicant issues remain.

  1. Make the outer swap-fee calculation cumulative and transaction-frequency invariant. src/ShardHookV1.sol:422-425 still floors the 1% fee independently on every swap before _distributeFee receives it. A reviewer regression with eleven successful 99-wei exact-input swaps produced 1,089 wei of qualifying volume, zero total fee, and zero Programmable accrual, although cumulative 10 bps is already at least one wei. Carry the outer fee remainder across the supported paths and add fragmented-versus-aggregated regressions for all supported swap quadrants and hook-owned market paths.
  2. Restore the declared build-size gate. With the exact source and current stable Foundry, forge build --sizes exits nonzero because FeeHookHarness is 24,606 bytes, 30 bytes above EIP-170. The production ShardHookV1 is 24,420 bytes and remains below the limit; the required repository command itself must also exit successfully without hiding production-size failures.

Passed on this SHA: immutable 0x4957f49620AFf3Adbbe8195a4f633E49cc93376c binding, cumulative inner split, complete CREATE2 predictions and atomic launch, exact permission bits, PoolManager and PoolKey authentication, settlement and claim solvency, permanent add-only liquidity, no admin/upgrade/withdraw path, 449 passing tests, and 19 invariants with 128,000 calls each. The Mainnet fork suite was not independently rerun because no archive RPC was available. Slither completed with known/parser-limited findings and no new reproduced drain or rug path.

Platform work remains separate after applicant pass: model adapter/compiler, registry and product integration, deployment authorization, runtime verification, monitoring, and the signed final-verification path.

A normal user cannot launch this exact revision as a tradable Custom Launch. Any new commit is a new review target.

…nder EIP-170

Carry the outer 1% fee's per-swap remainder on both bases (feeCarryIn for
exact-input, feeCarryOut for exact-output) so a stream of tiny swaps accrues the
same total as one aggregated swap instead of each flooring to zero. beforeSwap
hands afterSwap the exact charged fee via a scratch slot so the partial-fill
reconstruction never double-consumes the carry; buyMax clamps to its reserve.

Reclaim runtime size (unified _chargeFee, inlined the pure peek, deduped the
repeated fee-held post-condition) and merge FeeHookHarness's three test wrappers
into one dispatcher so every contract stays under EIP-170 and forge build
--sizes exits zero. Re-pin the CREATE2 launch plan and regenerate the fee spec,
security notes, runbook and mainnet-fork evidence for the new bytecode.
@jesse-stahl

Copy link
Copy Markdown
Author

Both issues addressed — f278735

No Mainnet deployment; Shards remains design.

1. The outer 1% fee is now cumulative and transaction-frequency invariant.
_feeOn's per-swap floor is replaced by a stateful _chargeFee that carries the sub-wei remainder on both bases — feeCarryIn for exact-input (÷10000), feeCarryOut for exact-output (net·100/9900). Your regression is fixed: eleven 99-wei exact-input swaps now accrue the full cumulative 1% instead of zero. Design notes:

  • beforeSwap hands afterSwap the exact fee it charged via a scratch slot (pendingBeforeSwapFee), so the partial-fill reconstruction reads it rather than recomputing — recomputing would double-consume the carry. Verified by the existing test_partialFillIsRejected* tests.
  • buyMax clamps the inclusive fee to its exact-input reserve so the buyer is never overspent; it sheds at most one wei at that full-consumption rounding boundary (disclosed in SECURITY.md/spec).
  • Every charge site now carries: beforeSwap, afterSwap, buyNFT, buyMany, buyMax, sellNFT/sellMany. The pure floor remains only for buyMax's worst-case reserve sizing (must not consume the carry).

Fragmented-vs-aggregated regressions (both bases cover all quadrants and the hook-owned market paths): ShardFeeSplitV1.t.sol::test_outerFee_exactInFragmentedMatchesAggregated, ::test_outerFee_exactOutFragmentedMatchesAggregated, ::testFuzz_outerFeeIsCumulative (1000 runs), plus the end-to-end four-quadrant ShardHookFeesV1.t.sol::test_operatorSplitHoldsAcrossAllFourQuadrants.

2. forge build --sizes exits zero.
The carry initially pushed the hook 6 B over EIP-170. Reclaimed with no behaviour change: unified _chargeFee to one code path, inlined the single-use pure floor, and deduped the identical "fee still held" post-condition shared by the three buy paths into _verifyFeeHeld. The over-limit FeeHookHarness test contract had its three wrappers merged into one dispatcher. Every contract is now under 24,576 B — production ShardHookV1 24,422 (154 B margin), FeeHookHarness 24,548, FeeSplitHarness 24,469, ShardHookHarnessV1 24,462 — and forge build --sizes exits 0.

Re-pinned launch plan (hook bytecode changed, so all downstream CREATE2 values did): factory 0xaCeeCA2d…a91a, hook 0x42A73c00…E0cc, SHARD 0x9b29CbF6…9f93, NFT 0xbAcd4195…29cA, config hash 0x712cd30f…8cf7, hook creation-code hash 0x17f5bf72…1340. Fee spec, security notes, runbook and manifest regenerated for the new bytecode.

Full gate on f278735: forge fmt --check; forge build --sizes (exit 0); FOUNDRY_PROFILE=ci forge test → 452 passed, 1 skipped, 0 failed; invariants 19 (1000 runs / 128k calls, 0 reverts); gas snapshot --check clean; Slither 0.11.5 (139 contracts, 113 results — no new finding on the fee code); coverage lines 93.15% / functions 90.51% / branches 58.67%; Mainnet-fork lifecycle 4/4 on eth.drpc.org (factory gas 7,176,738, launch gas 8,533,215).

@programmable-infra programmable-infra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Programmable verification: CHANGES REQUIRED

Reviewed immutable PR head: f278735

The two previous blockers are substantially fixed: the repository size gate now passes, and the new carry logic preserves the outer fee on the ordinary exact-input and exact-output paths. One reproducible applicant issue remains.

  1. In src/ShardHookV1.sol:692-714, buyMax consumes the exact-output fee carry in _chargeFee and then discards the carried wei when fee > maxFee is clamped. Two fully consumed buyMax calls of 10 ether + 99 wei each collect 200000000000000000 wei of total fee instead of the cumulative 200000000000000001 wei. Repeating the boundary repeats the loss, so this is not one globally bounded rounding wei. Preserve cumulative charging against the actually executed qualifying volume without overspending the caller, and add a fragmented-versus-aggregated regression that validates both the outer fee and Programmable accrual.
  2. Update docs/security/SHARDS_PROPERTIES.md:89-93. Its claim that the outer fee is cumulative and transaction-frequency invariant is contradicted by the current buyMax behavior; the stated residual is per qualifying call, not globally bounded.

Passed on this SHA: immutable Programmable recipient 0x4957f49620AFf3Adbbe8195a4f633E49cc93376c; cumulative inner operator split; exact five v4 permission bits; PoolManager and PoolKey authentication; settlement, claims, locked add-only liquidity, atomic CREATE2 launch, and no admin, upgrade, or withdrawal path. The exact repository CI suite produced 452 passing tests and one skipped Mainnet-fork test, all 19 Shards invariants passed, the public intake/model/release verifiers passed, and forge build --sizes passed with ShardHookV1 at 24,422 bytes and both fee harnesses below EIP-170. Slither completed with known/parser-limited warnings and no additional reproduced drain or rug path.

Applicant responsibility is limited to the fee-path and matching evidence above. Platform model integration, deployment authorization, registry/product/indexer support, monitoring, and signed final verification remain separate platform work after the applicant revision passes.

A normal user cannot launch this exact revision as an immediately tradable Custom Launch. Any new commit is a new review target.

…edding it

When buyMax clamps the inclusive fee to its exact-input reserve so the buyer is
not overspent, return the uncollected wei to feeCarryOut instead of dropping it,
so the entitlement is preserved and collected on a later exact-output swap
rather than shed once per qualifying call. Reclaim the added size by extracting
the shared buy head (_chargeBuy) from buyNFT and buyMany, keeping every contract
under EIP-170. Add a boundary regression proving the clamped wei is carried and
Programmable still accrues, update the fee docs, and re-pin the launch plan.
@jesse-stahl

Copy link
Copy Markdown
Author

buyMax fee carry fixed — b8b3cb8

No Mainnet deployment; Shards remains design.

1. buyMax now preserves the uncollected wei instead of shedding it.
When the inclusive fee lands above the exact-input reserve (so charging it in full would overspend the buyer), buyMax still clamps to the reserve — but the wei it can't collect is now returned to feeCarryOut (src/ShardHookV1.sol), so the entitlement is preserved and collected on a later exact-output swap rather than lost once per qualifying call. Your repro (two 10 ether + 99 wei boundary calls) now retains the owed wei in the carry.

Regression: ShardFeeSplitV1.t.sol::test_buyMax_clampCarriesUncollectedWeiAndAccruesToProgrammable — a 100_099-wei call hits the exact clamp boundary (inclusive fee 1001 vs reserve 1000); it asserts each boundary call adds a whole carried wei to feeCarryOut and that Programmable accrues its share of the collected fee, across two calls. Without the carry-back the remainder stays below one whole wei and the assertion fails.

The added logic was offset by extracting the shared buy head (_chargeBuy) from buyNFT/buyMany, so every contract stays under EIP-170ShardHookV1 24,358 B (218 B margin), harnesses 24,398–24,540 B; forge build --sizes exits 0.

2. docs/security/SHARDS_PROPERTIES.md (P14) corrected — the outdated "≤1-wei shed at buyMax" residual is replaced with the carry-back behavior and the new test citation; SECURITY.md and spec/shards-v1.json updated to match.

Re-pinned for the new bytecode: factory 0xDc1Aae9A…bf39, hook 0x8Add9914…a0cC, SHARD 0x92541FCE…1D94, NFT 0x62D7bD7F…762b, config hash 0x475e4ee8…639c, hook creation-code hash 0x34df1ce9…8a2f.

Full gate on b8b3cb8: fmt --check; build --sizes exit 0; FOUNDRY_PROFILE=ci forge test → 453 passed, 1 skipped, 0 failed; invariants 19 (1000 runs / 128k calls, 0 reverts); gas snapshot --check clean; coverage 93.15% / 90.54% / 58.71%; fork lifecycle 4/4 (factory gas 7,176,677, launch 8,520,072).

One Slither informational note appears on the carry-back (divide-before-multiply): it is a false positive — fee - maxFee is an exact whole-wei count whose sub-wei remainder is separately held in feeCarryOut, so the multiplication by the constant denominator is an exact unit conversion with no precision loss (commented inline). No new drain/rug path; Slither runs --fail-none.

@programmable-infra programmable-infra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Programmable verification: READY FOR FINAL VERIFICATION

Reviewed immutable PR head: b8b3cb8

The previous buyMax fee blocker is fixed. An independent reviewer regression exercised ten clamp-boundary calls and a later exact-output charge: the carried whole wei were released exactly once, the cumulative outer fee remained exact, and the Programmable and builder shares each matched their cumulative entitlement. The buyer is never overspent.

Passed on this exact revision: formatting; build and EIP-170 size gates; 454 local tests; all 19 Shards stateful invariants; the 4-test pinned Ethereum Mainnet v4 lifecycle; registry, release-evidence and documentation verifiers; and coverage floors at 93.15% lines, 90.54% functions and 58.71% branches. PoolManager authentication, the exact five hook permission bits, all four swap quadrants, partial-fill fail-closed behavior, ERC-6909 settlement and claims, backing and solvency, atomic CREATE2 launch, permanent add-only liquidity, and the absence of an admin, upgrade or liquidity-withdrawal path were rechecked. The immutable Programmable recipient is 0x4957f49620AFf3Adbbe8195a4f633E49cc93376c; the admin/deployer wallet remains separate.

No applicant change is required for this decision. Keep this commit unchanged; any new commit is a new review target.

This approval prepares the exact revision for the signed final-verification path. It is not a Mainnet deployment or immediate launch authorization. A normal user cannot launch it as a tradable product today; platform merge, deployment, product integration, monitoring and final authorization remain Programmable work.

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.

3 participants