audits: 2026-05-09 escrow E2E preprod results

5 of 6 builders proven on chain on preprod_test2 against the
post-audit validator (a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d).

Veto path (4 builders):
- open       0972963e8eb46597b37dfa7e1fa15c97fea4e39d55b29c54f723ba1b155bc7cb
- deposit    0519cb7dbb8c2c1632db5baee90d5440d2905c151f910a085c78d9050c2d6175  ← first Plutus V3 spend on chain
- agree      f6664079d96c453e1a6333c33c65744984953c474e57cdb331e78ad3e5429cc7
- veto       14a4be9f233ab02e518356e5963d30517986984f6fd3520151c924057db9c661  ← HIGH-2 fix proven

Refund-timeout path (5th builder):
- open(short) 0669ef61b8695bedcb4eb4c38fda2bd66c68c9384b57839329a01a745db75305
- refund      41590ac6ed069586e650da58858436cfe6be51a865069a7a4b40f795dfcdbff9  ← strict-> time gate proven, MED-2/3 fix held

Settle deferred — needs 30-min lock_period_ms wait. Builder + validator
branch unit-tested (5/5 tests pass), on-chain validation in next session.

Both HIGH validator fixes from the 2026-05-09 internal audit confirmed
running on chain. Multi-party signing flow + slot↔ms boundary math
both held up under real preprod chain race conditions.
This commit is contained in:
Kayos 2026-05-09 18:05:56 -07:00
parent eb192fa676
commit af4cfd7f97

View file

@ -0,0 +1,92 @@
# Escrow v1 — preprod E2E results, 2026-05-09
End-to-end execution of the escrow_wip surface on `preprod_test2`,
post-internal-audit (validator hash
`a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d`).
## Setup
- **Image:** `lucy-registry:5000/aldabra/mcp:escrow-wip-v1`
- **Validator script address:** `addr_test1wz5qsxkw7f5ntkd4739eypfp0rshxqdk6mngpry3ck6k7hgcgah4g`
- **Validator path inside container:** `/etc/aldabra/escrow/validator.cbor.hex` (8414 hex chars)
- **party_a (preprod wallet):** `4cd61bd67ed72c1cec160bf7de6103c6bddb397da6a500dc4ff805f8` (`addr_test1qpxdvx7k...`)
- **party_b / recipient (bob, fresh test wallet):** `fc7533eb154263a154fb40f1acc4fc40e847cac98fe45da4e948c4f6` (`addr_test1qr782vltz...`)
## Veto path (4 of 6 builders)
| Step | tx hash | What it proves |
|------|---------|----------------|
| Open | `0972963e8eb46597b37dfa7e1fa15c97fea4e39d55b29c54f723ba1b155bc7cb` | escrow_open builder; deposits=[(preprod, 5 ADA)] at script |
| Deposit (bob) | `0519cb7dbb8c2c1632db5baee90d5440d2905c151f910a085c78d9050c2d6175` | **first Plutus V3 spend on chain.** Validator's Deposit branch ran — including the new HIGH-1 `value_geq_value(new_value, in_value)` check. Continuing-output deposits-list update verified via `cbor.serialise` byte-equality. |
| Agree | `f6664079d96c453e1a6333c33c65744984953c474e57cdb331e78ad3e5429cc7` | both-party multi-sig (driver=preprod, co-signer=bob). State Open→Agreed{at=upper}. validity_upper ≤ open_deadline_ms enforced. |
| Veto | `14a4be9f233ab02e518356e5963d30517986984f6fd3520151c924057db9c661` | HIGH-2 fix active: `value_eq(deposits_to_value(d.deposits), in_value)`. Multi-output refund (5 ADA each to enterprise addresses of party_a + party_b). |
Total tADA cycled through validator: 10 ADA (5 from each party). Refunded to enterprise addresses of both contributors.
## Refund-timeout path (5th builder)
| Step | tx hash | What it proves |
|------|---------|----------------|
| Open (short deadline) | `0669ef61b8695bedcb4eb4c38fda2bd66c68c9384b57839329a01a745db75305` | escrow_open with `open_deadline = tip_ms + 5 min`, initial_lovelace=2 ADA |
| (wait) | — | tip elapsed past open_deadline (~165s past) before next step |
| Refund-timeout | `41590ac6ed069586e650da58858436cfe6be51a865069a7a4b40f795dfcdbff9` | escrow_refund_timeout builder; `lower > open_deadline_ms` strict-`>` time gate proven; HIGH-2 deposits-sum invariant proven on a single-deposit escrow; single-output refund (2 ADA back to party_a's enterprise address). |
## Settle path (6th builder, NOT YET TESTED)
Deferred — requires a 30-minute `lock_period_ms` to elapse between Agree
and Settle. Setup: open + 2 deposits + Agree + wait 30 min + Settle.
Run as a separate session when the window is convenient.
The Settle builder + validator branch are unit-tested in
`crates/aldabra-dao/src/builder/escrow_settle.rs` (5 tests covering
not-Agreed reject, lock-not-elapsed reject, empty-escrow reject,
happy-path full payout, anyone-can-drive). On-chain validation is the
final gap.
## What this E2E proved on chain
- ✅ Plutus V3 validator deploys at the bech32-derived script address
per validator hash + testnet header byte.
- ✅ Inline V3 validator CBOR (~8 KB) bakes into the runtime image at
`/etc/aldabra/escrow/validator.cbor.hex` and the MCP `validator_script_path`
arg correctly resolves it.
- ✅ Conway-era inline-datum + script-output min-utxo (2 ADA floor)
matches MED-5 fix expectations.
- ✅ HIGH-1 fix (`value_geq_value(new_value, in_value)` on Deposit)
ran on chain — validator accepted a clean deposit, no token drain
attempt was made (v1 ADA-only).
- ✅ HIGH-2 fix (`value_eq(deposits_to_value(d.deposits), in_value)`
on Veto and Refund) ran on chain — the new helper functions
`deposits_to_value` and `value_eq` execute within the per-tx UPLC
budget for a 1-deposit and a 2-deposit escrow.
- ✅ Multi-party signing flow works end-to-end: driver signs partial
→ co-signer signs partial → submit. wallet_sign_partial appends a
fresh VKeyWitness without disturbing existing ones.
- ✅ Slot↔ms math at the strict-`>` validator boundary (Refund
branch) didn't burn fees — the MED-2/3 fix to derive
`validity_lower_ms` from `slot_to_posix_ms(network, slot)` instead
of Koios `block_time*1000` held up under a real preprod chain race.
## v2 / future TODO (still open)
- Settle path on-chain validation.
- External third-party audit (TxPipe / Anastasia Labs / MLabs / Tweag).
- Reference-script deployment for the validator (~8 KB inline per
spend tx is non-trivial; ref-script optimization halves tx size).
- `aiken/fuzz` property tests in `escrow.ak` covering each redeemer
branch's invariant violations.
- Multi-asset escrows (the v2 frontier — needs the foldr-canonicality
golden test for `Pairs<>` serialisation, plus widening the spend
builders to take `escrow_in_assets` and emit non-empty asset entries
on every output).
- Read-side MCP tools (`escrow_show`, `escrow_list`).
## Wallet movement summary
party_a (preprod): pre-flow ~9844 tADA → post-flow ≈ 9844 - 100 (sent to bob) - ~5 (fees + topups) ≈ ~9739 tADA.
party_b (bob): pre-flow 0 → post-funding 100 → minus ~5 ADA fee/lockup → ≈ 95 tADA at end of flow.
Refunds landed at enterprise (null-stake) addresses, NOT the base
addresses of the wallets — this is by validator design (`pkh_to_base_address`
hard-codes `stake_credential: None`). The funds are spendable via the
same payment key; just a different bech32 prefix.