@@ -329,6 +329,83 @@ the post-deposit `DRep` state. (In practice, `applyDirectDeposits`{.AgdaFunctio
329329only modifies rewards, so ` rmOrphanDRepVotes ` would produce the same result either
330330way, but using ` certStateFinal ` is semantically correct.)
331331
332+ ### Design Rationale: Batch-wide Direct Deposit Application
333+
334+ A natural alternative to applying direct deposits batch-wide (as above) is to
335+ interleave them with the per-sub-transaction processing performed by
336+ ` SUBLEDGERS ` {.AgdaDatatype}; that is, in each ` SUBLEDGER-V ` {.AgdaInductiveConstructor}
337+ step, apply that sub-transaction's ` DirectDepositsOf stx ` to the threaded
338+ ` CertState ` {.AgdaRecord} after running ` CERTS ` {.AgdaDatatype}/` GOVS ` {.AgdaDatatype}
339+ for the sub-transaction.
340+
341+ ** The CIP forbids this alternative, and the rule structure
342+ adopted here makes the prohibition manifest rather than imposing it as an extra
343+ premise.**
344+
345+ + ** CIP-159 rules out per-sub-transaction application** .
346+
347+ CIP-159 states that, "to prevent ... * phantom asset* attacks, transactions can
348+ only withdraw funds that exist in the account * before* the overall transaction
349+ is run. This means later sub-txs cannot withdraw assets that were deposited
350+ by prior sub-txs in the same overall transaction." Per-sub-transaction
351+ application would let sub-transaction ` N ` 's ` PRE-CERT ` {.AgdaDatatype} step
352+ authorize withdrawals against a ` rewards ` {.AgdaField} map already inflated by
353+ sub-transactions ` 1..N−1 ` 's deposits — exactly the situation the CIP forbids.
354+
355+ + ** Phantom asset prevention follows structurally** .
356+
357+ Because ` applyDirectDeposits ` {.AgdaFunction} never runs against the
358+ ` CertState ` {.AgdaRecord} threaded through ` SUBLEDGERS ` {.AgdaDatatype}, every
359+ ` PRE-CERT ` {.AgdaDatatype} step in the batch checks each withdrawal ` amt ` against
360+ ` rewards ` {.AgdaField} as updated only by ` applyWithdrawals ` {.AgdaFunction} and
361+ by registration/deregistration certificates. Summed across the batch this
362+ yields, for every reward address, total withdrawals bounded by the pre-batch
363+ balance (i.e., the ` NoPhantomWithdrawals ` {.AgdaFunction} property) * as a
364+ consequence of the rule structure* rather than as a separate premise that would
365+ need to be added and discharged.
366+
367+ + ** Alignment with CIP-118 script context isolation** .
368+
369+ CIP-118 requires that "Plutus scripts in one sub-transaction do not see other
370+ sub-transactions or the top-level transaction in their context." Holding the
371+ ` accountBalances ` {.AgdaField} field of ` SubLedgerEnv ` {.AgdaRecord} and
372+ ` SubUTxOEnv ` {.AgdaRecord} fixed at the pre-batch snapshot ` RewardsOf certState₀ `
373+ ensures that every sub-transaction's balance-interval check (and any future Plutus
374+ context derived from this field) sees the same balances independently of the
375+ surrounding sub-transactions. Per-sub-transaction application would make these
376+ views order-dependent across sub-transactions.
377+
378+ + ** Direct deposits are a diff, not a sequence of state updates** .
379+
380+ CIP-159 specifies that the ` direct_deposits ` field carries only the diff to be
381+ applied (e.g., ` 0.1 ADA ` , not the resulting ` 100.1 ADA ` balance). Aggregating
382+ each sub-transaction's diff via ` ∪⁺ ` (union-with-addition) and applying the
383+ sum once is the natural reading: the batch-level
384+ ` allDirectDeposits ` {.AgdaFunction} is the diff from pre-batch to post-batch.
385+ Per-sub-transaction application would impose an artificial sequential
386+ interpretation on what is logically a commutative aggregation.
387+
388+ + ** Determinism and reorderability** .
389+
390+ CIP-159 motivates direct deposits in part by their freedom from contention:
391+ deposits into the same account from different parties can be processed "in any
392+ order and fully parallel." Batch-wide application makes this manifest at the
393+ rule level: the order of sub-transactions in the batch does not affect the
394+ ` accountBalances ` {.AgdaField} visible to any sub-transaction's
395+ ` SUBUTXO ` {.AgdaDatatype} or ` CERTS ` {.AgdaDatatype} step, nor the final
396+ ` rewards ` {.AgdaField} reached after the deposit step.
397+
398+ + ** ` depositsChange ` {.AgdaFunction} remains orthogonal** .
399+
400+ ` calculateDepositsChange ` {.AgdaFunction} measures * protocol* deposit movements
401+ (registration/deregistration of credentials, DReps, pools), which live in the
402+ ` deposits ` {.AgdaField} fields of ` DState ` {.AgdaRecord}/` PState ` {.AgdaRecord}/
403+ ` GState ` {.AgdaRecord} — not in ` rewards ` {.AgdaField}. Because
404+ ` applyDirectDeposits ` {.AgdaFunction} touches only ` rewards ` {.AgdaField} and
405+ runs * after* ` calculateDepositsChange ` {.AgdaFunction}, the two notions of
406+ "deposit" remain cleanly disjoint without case analysis.
407+
408+
332409``` agda
333410data _⊢_⇀⦇_,LEDGER⦈_ : LedgerEnv → LedgerState → TopLevelTx → LedgerState → Type where
334411
0 commit comments