Skip to content

Commit 8216e6c

Browse files
committed
add justification for batch processing of direct deposits
1 parent 913c997 commit 8216e6c

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

src/Ledger/Dijkstra/Specification/Ledger.lagda.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,83 @@ the post-deposit `DRep` state. (In practice, `applyDirectDeposits`{.AgdaFunctio
329329
only modifies rewards, so `rmOrphanDRepVotes` would produce the same result either
330330
way, 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
333410
data _⊢_⇀⦇_,LEDGER⦈_ : LedgerEnv → LedgerState → TopLevelTx → LedgerState → Type where
334411

0 commit comments

Comments
 (0)