← demo0x4db854…b88dSucceededblock 90
No position yet0 / 71
FetchingOpeningPositioning
noir
Narrow session: Code, Call Trace and Values only, read-only. The event log and stepping need a wider viewport.
Code
1 opening = "120"
2 transfer_amount = "30"
1 // The module holding the account type and the solvency check whose constraint
2 // the recording ends on.
3
4 pub struct Account {
5 pub id: u32,
6 pub balance: u64,
7 }
8
9 // Returns the amount by which the two accounts together exceed `floor`.
10 //
11 // `main` calls this with a floor no pair of accounts in that program can reach,
12 // so the assertion on its result is unsatisfiable BY CONSTRUCTION. That is the
13 // point: a reader who steps into this frame can see the two balances and the
14 // floor side by side, and work out for themselves why the next line stops the
15 // execution — rather than being told.
16 pub unconstrained fn solvency_margin(a: Account, b: Account, floor: u64) -> u64 {
17 let combined = a.balance + b.balance;
18 if combined > floor {
19 combined - floor
20 } else {
21 0
22 }
23 }
1 // TOUR: constraints, and an execution that fails one
2 //
3 // The subject is what a recording looks like when the program does NOT finish.
4 // This is the case a zero-knowledge debugger exists for: a constraint did not
5 // hold, and the question is which one, on which values, reached from where.
6 //
7 // The recording therefore ENDS at the failing assertion. Everything before it
8 // is ordinary stepping; the last thing in the event log is the assertion's own
9 // message. A tour that only ever showed successful executions would leave the
10 // most useful thing this product does undemonstrated.
11 //
12 // Assertions that HOLD come first and are stepped over normally, so the
13 // contrast between "checked and passed" and "checked and stopped" is visible
14 // inside one recording.
15
16 mod ledger;
17
18 use ledger::Account;
19
20 unconstrained fn transfer(from: Account, to: Account, amount: u64) -> (Account, Account) {
21 // These three hold for every call below, so a reader sees an assertion
22 // being satisfied and stepped past before they see one that is not.
23 assert(amount > 0, "transfer amount must be positive");
24 assert(from.id != to.id, "cannot transfer to the same account");
25 assert(from.balance >= amount, "insufficient balance");
26
27 let debited = Account { id: from.id, balance: from.balance - amount };
28 let credited = Account { id: to.id, balance: to.balance + amount };
29
30 // The invariant that makes this a ledger: no value is created or destroyed.
31 let before = from.balance + to.balance;
32 let after = debited.balance + credited.balance;
33 assert(before == after, "transfer must conserve the total balance");
34
35 (debited, credited)
36 }
37
38 unconstrained fn main(opening: u64, transfer_amount: pub u64) -> pub u64 {
39 let alice = Account { id: 1, balance: opening };
40 let bob = Account { id: 2, balance: 50 };
41
42 // A format string interpolates bindings, not field accesses, so each
43 // balance is bound before it is printed.
44 let a0 = alice.balance;
45 let b0 = bob.balance;
46 println(f"opening: alice={a0} bob={b0}");
47
48 // Two transfers that succeed. Every assertion in `transfer` is evaluated
49 // and holds, twice.
50 let (alice2, bob2) = transfer(alice, bob, transfer_amount);
51 let a1 = alice2.balance;
52 let b1 = bob2.balance;
53 println(f"after 1: alice={a1} bob={b1}");
54
55 let (bob3, alice3) = transfer(bob2, alice2, 10);
56 let a2 = alice3.balance;
57 let b2 = bob3.balance;
58 println(f"after 2: alice={a2} bob={b2}");
59
60 let total = alice3.balance + bob3.balance;
61 assert(total == opening + 50, "the ledger total is unchanged by transfers");
62
63 // ── the failing constraint ────────────────────────────────────────────
64 //
65 // `ledger::solvency_margin` returns the amount by which the accounts
66 // exceed the reserve floor. The floor is set higher than the accounts can
67 // possibly hold, so this assertion CANNOT hold — it is not a bug that
68 // happens to be here, it is the subject of this program.
69 //
70 // The recording stops on the next line. Its last event log entry is this
71 // message, and the position where the session ends is this assertion's.
72 let margin = ledger::solvency_margin(alice3, bob3, 1_000_000);
73 assert(margin > 0, "accounts must clear the reserve floor");
74
75 total
76 }
1 [package]
2 name = "tour_constraints"
3 type = "bin"
4 authors = ["BlockTracer capability tour"]
5
6 [dependencies]
Event Log

The recorded event stream is in the published recording. Reading it needs the replay engine, which this page has not started.

Call Trace

The call structure is in the published recording. Reading it needs the replay engine, which this page has not started.

Values

The recorded values are in the published recording. Reading them needs the replay engine, which this page has not started.