Repository navigation
Conversation
dispatch now opens the next batch in the same instruction, as EVM's dispatchBatch does, and initialize_batcher opens batch 0, so a batcher always has exactly one pending batch. open_batch, its previous_batch account and its three errors are removed; the open logic is one internal helper both instructions call. The demo, seed and e2e follow: the dispatch builder derives both batches from the roots and the batch index, the seeder initializes each batcher with batch 0, and settleBatch takes the batch index it settles.
Measurements come from the solana-cost-snapshots artifact of run 38156066804. The run regenerates in update mode, which never deletes keys, so the artifact still carried the open_batch and redeem_open_batch profiles whose test no longer exists. Those two keys are removed with jq. A local regeneration from empty measurements produces the same key set.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dispatchnow opens the next batch in the same instruction, andinitialize_batcheropens batch 0. A batcher therefore always has exactly onePendingbatch. Before this change, a baredispatchleft the batcher with no pending batch until someone paid for a separateopen_batch.open_batchis deleted, along with itsprevious_batchaccount and the errorsPreviousBatchStillPending,PreviousBatchMismatchandBatchIndexMismatch. The open logic is now one internal helper (instructions/open_batch.rs) that both instructions call.Research (EVM first)
dispatchBatchcalls the base dispatch and then opens the next batch, and the constructor opens the first batch.zama-ai/confidential-defi@fc5e876e04 contracts/src/batcher/VaultBatcherConfidential.sol:240-254and:114;5980d9ab contracts/src/VaultBatcherConfidential.sol:281-291.OpenZeppelin/openzeppelin-confidential-contracts@4a4f6c71 contracts/finance/BatcherConfidential.sol:167-168and:468-470.dispatchis the only way out ofPending, becausecancel_dispatchandsettlerequire a dispatched batch. The invariant therefore holds by construction.Changes
confidential-batcher)dispatch(authority_funding_lamports)takes the next batch's accounts. These are the batch atbatcher.next_batch_index, its authority, its two confidential token accounts and stores, its two SPL underlyings, the payout mint and its underlying, the SPL token program, and the payout mint's HCU accounts.initialize_batcher(..., authority_funding_lamports)takes the same set for batch 0 and becomes an FHE-executing instruction.open_batchmade still applies on both paths, through the helper: both mints pinned to the batcher, and both underlying mints pinned to their confidential mints.BATCH_SEEDis exported as an IDL constant. No instruction declares the batch's index as a seed any more:dispatchderives it from account data, which Codama cannot read. The codegen therefore types thebatchPDA from that constant, as it already does for the host'shcuBlockMeter, andpda-golden.test.tschecks it. The codegen also linksdispatch's threenextBatch*defaults to the batch PDAs keyed onnextBatch.buildDispatchBatchInstructiontakesroots,batchIndexandauthorityFundingLamports, and derives both batches.openBatchForBatcheris replaced bybuildInitializeBatcherInstruction, which the seeder calls once per batcher.prepareNextBatchonly reads the pending batch and runs the reclaim pass.settleBatchrequiresbatchIndex. Its default, "the current batch", can no longer be a dispatched batch.getCurrentBatchreturns the pending batch. Its guard for "no batch opened yet" was dead and is removed.initialize_batcheris sent throughsendFheTransactionwith the host policy, and the explicit first open is gone.deposit-arc)Pendingright after dispatch.batchIndextosettleBatch.demo-dapp/tsconfig.node.json:src/demoClient.tsis now the first root.@solana/*8.4.0, and tsc keeps whichever copy it loads first.batchProvisioning.tsimported@fhevm/sdk/solanaon its first line. Removing that import made tsc pick the zama-host client's nested copy, and declaration emit failed with TS2883.Side effects
dispatchnow also needs the payout mint to be usable. A denied payout mint, or a full HCU meter on either mint, blocks dispatch. Pending quits still work, so no funds are locked.reclaim_batch_authority, not to the dispatcher.Pending(pinned).findExistingDeposit's fallback (with no saved position) reads only the pending batch. Right after a dispatch it no longer finds a join in the dispatched batch. The saved position remains the main path. This fallback already missed such joins once the next batch had been opened.Measured against the estimate
The worst case has the deny list on and both mints' HCU accounts supplied.
dispatchtransaction sizefits one v1 transaction with every witness)dispatchCUredeem_dispatchCUinitialize_batcherCUThe estimate was the old
dispatchplus the oldopen_batch(211,730 + 248,175 = 459,905 CU). The folded instruction costs about 37k CU less than that sum. The redeem direction follows the same pattern: 183,231 + 246,676 = 429,907, against 392,428 measured. Both stay well under the 1.4M CU transaction limit.Tests
mollusk_initialize_batcher_opens_batch_zero;mollusk_a_batcher_has_exactly_one_pending_batch: scans every batch after each step of initialize → dispatch → settle → dispatch → cancel → dispatch;mollusk_dispatch_opens_the_next_batch: a join lands in N+1 while N settles and pays out;mollusk_dispatch_funds_the_next_batch_authority: the exact funding with a non-default amount, and 0 reverts with N stillPending;mollusk_dispatch_rejects_another_payout_mint;mollusk_dispatch_rejects_a_next_batch_at_another_index;mollusk_dispatch_accepts_precreated_next_batch_token_accounts.BatchNotPendingrather than failing on seeds first.pda-golden.test.tschecks theinitializeBatcheranddispatchdefaults under the default and an overridden program address.Mutations (rule 15): each mutation was built into the program and the batcher suite was run against it.
dispatchreturns before opening the next batchmollusk_dispatch_opens_the_next_batch,mollusk_a_batcher_has_exactly_one_pending_batch,..._funds_the_next_batch_authority,..._accepts_precreated_...and every flow that dispatches (34 failing, the 2 snapshot tests included)initialize_batcherreturns before opening batch 0mollusk_initialize_batcher_opens_batch_zero,mollusk_a_batcher_has_exactly_one_pending_batchand every lifecycle test (42 failing, the 2 snapshot tests included)mollusk_dispatch_funds_the_next_batch_authority(only)mollusk_dispatch_rejects_another_payout_mint(only)Ran locally
cargo fmt --check;-D warnings;batcher_mollusk: 42 pass, and the 2 cost-snapshot tests fail locally by design;check-zama-host-idl.sh,dead-surface-check.sh,check-path-filters.sh.codegen:solana:check;check,lintandvitest: 232 pass;checkandbun test src/solana demo: 185 pass;check.deposit-arc). CI runs it.Generated (rule 18): the IDL,
AUTHORITY.md(unchanged) and the Codama clients come fromsync-zama-host-idl.shand the SDK codegen. The cost snapshots come from the CI artifact of run 38156066804. That run regenerates in update mode, which never deletes keys, so the artifact still held theopen_batchandredeem_open_batchprofiles. Those two keys were removed withjq; a local regeneration from empty measurements gives the same key set.Non-goals
Rejected alternatives
[dispatch(N), open_batch(N+1)]in one transaction. This needs no program change, but a third party's bare dispatch would still leave no pending batch. Only the program can make the property hold.findBatchPdain the demo. Rejected: the PDA stays generated, with the seed prefix taken from the IDL constant and checked by the golden test.