frame_suite/assets.rs
1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// ````````````````````````````````` ASSETS SUITE ````````````````````````````````
14// ===============================================================================
15
16//! A collection of composable, type-driven balance models built on
17//! [`virtual`](crate::virtuals) abstractions and
18//! [`plugin`](crate::plugins)-based execution.
19//!
20//! ## Motivation
21//!
22//! Traditional balance systems tightly couple:
23//! - storage layout
24//! - accounting logic
25//! - and update propagation
26//!
27//! This leads to:
28//! - costly updates across all dependent states
29//! - rigid data structures that are hard to evolve
30//! - difficulty composing new behaviors or extending models
31//!
32//! This module addresses these limitations by:
33//!
34//! - **decoupling structure, storage, and behavior**
35//! - **deferring computation until it is actually needed**
36//! - **allowing logic to be injected via plugins**
37//!
38//! ## Lazy Balance Model
39//!
40//! The primary model is [`LazyBalance`], a **receipt-based,
41//! lazily evaluated accounting system**.
42//!
43//! ```text
44//! deposit -> issue receipt
45//! mutate balance -> affect global state only
46//! withdraw -> resolve receipt value lazily
47//! ```
48//!
49//! In this model:
50//!
51//! - deposits create **receipts (claims)**
52//! - balance mutations affect **global state only**
53//! - receipt value is computed **at withdrawal time**
54//!
55//! This avoids eagerly updating all receipts while preserving correctness.
56//!
57//! ## Design
58//!
59//! ### Virtual Architecture
60//!
61//! All balance models are built on shared virtual primitives:
62//!
63//! - [`VirtualDynField`] -> defines structured components (asset, rational, time)
64//! - [`VirtualDynExtension`] -> enables extensibility via addons
65//! - [`VirtualDynBound`] -> provides external constraints (e.g. capacity)
66//! - [`VirtualNMap`] -> externalizes heavy or dynamic storage
67//!
68//! These abstractions allow:
69//!
70//! - structure to remain **representation-agnostic**
71//! - storage to be **externalized or optimized independently**
72//! - components to be **composed across contexts**
73//!
74//! ### Plugin-Driven Execution
75//!
76//! Behavior is defined through plugin families via:
77//!
78//! - [`declare_family`]
79//! - [`plugin_types`]
80//! - [`plugin_output`]
81//!
82//! This enables:
83//!
84//! - **compile-time dispatch of operations**
85//! - **modular and replaceable logic**
86//! - **context-driven customization**
87//!
88//! Each operation (deposit, withdraw, etc.) is selected via a discriminant
89//! and resolved through the plugin family.
90//!
91//! ## Summary
92//!
93//! This module provides a foundation for building diverse balance systems where:
94//!
95//! - accounting is **lazy and efficient**
96//! - structure is **flexible and composable**
97//! - behavior is **modular and replaceable**
98//!
99//! enabling multiple accounting strategies to coexist under a unified,
100//! type-driven architecture.
101
102// ===============================================================================
103// ``````````````````````````````````` IMPORTS ```````````````````````````````````
104// ===============================================================================
105
106// --- Local crate imports ---
107use crate::{
108 base::{Asset, Delimited, Fractional, RuntimeEnum, Time},
109 declare_family, discriminants, impl_discriminants,
110 misc::{Directive, Extent},
111 mutation::MutHandle,
112 plugin_output, plugin_types,
113 plugins::ModelContext,
114 virtuals::*,
115};
116
117// --- Substrate primitives ---
118use sp_runtime::Cow;
119
120// ===============================================================================
121// ``````````````````````````````````` ALIASES ```````````````````````````````````
122// ===============================================================================
123
124/// Resolved context type for a given [`LazyBalance`] implementation.
125///
126/// Extracts the concrete context provided by `BalanceContext`.
127pub type Context<T> = <<T as LazyBalance>::BalanceContext as ModelContext>::Context;
128
129/// Resolved error type for a given [`LazyBalance`] implementation.
130///
131/// Provided by the context via [`VirtualError`].
132pub type Error<T> = <Context<T> as VirtualError<LazyBalanceError>>::Error;
133
134// ===============================================================================
135// ````````````````````````````````` LAZY BALANCE ````````````````````````````````
136// ===============================================================================
137
138/// A composable, plugin-driven balance system with **lazy
139/// receipt-based accounting**.
140///
141/// `LazyBalance` defines a generic balance abstraction where value is managed
142/// through deposits, receipts, and deferred redemption semantics.
143///
144/// The system integrates with the plugin framework to express all behavior
145/// (validation, mutation, and queries) as pluggable operations resolved at
146/// compile time.
147///
148/// ## Core Semantics
149///
150/// The defining property of `LazyBalance` is its *lazy propagation model*:
151///
152/// - Balance updates (e.g. `mint`, `reap`) are applied **immediately** to the
153/// underlying balance state.
154/// - Issued receipts are **not eagerly updated** when such changes occur.
155/// - Instead, the effect of these updates is **deferred and applied at the time
156/// of receipt redemption (`withdraw`)**.
157///
158/// In other words:
159///
160/// ```text
161/// Deposits -> Issue Receipts
162/// Balance Mutations -> Affect Global State Only
163/// Withdrawals -> Resolve Final Value Lazily
164/// ```
165///
166/// This allows the system to:
167/// - avoid costly updates across all outstanding receipts
168/// - maintain correctness through deferred evaluation
169/// - support dynamic balance adjustments without recomputation overhead
170///
171/// ## Conceptual Model
172///
173/// The system is composed of three primary components:
174///
175/// - **Balance**
176/// - Canonical mutable state
177/// - Receives all direct updates (`deposit`, `mint`, `reap`, `drain`)
178///
179/// - **Receipt**
180/// - Represents a claim issued at deposit time
181/// - Encodes entitlement, not fixed value
182/// - Final value is determined lazily at withdrawal
183///
184/// - **SnapShot**
185/// - Time-indexed projection of balance state
186/// - Enables historical queries and deferred computations
187///
188/// This forms a *claim-based accounting model*:
189/// - deposits create claims
190/// - withdrawals resolve claims
191/// - intermediate balance changes influence claim outcomes
192///
193/// ## Storage Model
194///
195/// Snapshot data is externalized via [`VirtualNMap`] using
196/// `SnapShotStorage`, allowing:
197///
198/// - efficient storage of time-indexed data
199/// - separation of heavy state from encoded balance representation
200/// - iteration and prefix queries over historical state
201///
202/// ## Plugin-Driven Behavior
203///
204/// All operations are expressed through a plugin family [`declare_family`]:
205///
206/// Each operation:
207/// - is type-driven via [`Self::Input`] and [`Self::Output`]
208/// - is resolved through the [`Self::BalanceFamily`] plugin
209/// - may be customized by runtime configuration
210///
211/// ## Limits
212///
213/// Operations may expose dynamic limits (e.g. minimum, maximum, optimal)
214/// via dedicated limit queries. These limits are derived from current
215/// balance state and provide bounded inputs for safe execution.
216///
217/// ## Execution DispatchPolicy
218///
219/// Operations may be parameterized by a [`Self::Subject`], which encodes
220/// qualitative behavior such as precision and fortitude,
221/// influencing how operations are evaluated and executed.
222///
223/// ## Design Properties
224///
225/// - **Lazy evaluation**: receipt value is computed at redemption time
226/// - **Efficient**: avoids eager updates across all outstanding claims
227/// - **Composable**: behavior is modular via plugin families
228/// - **Decoupled**: storage, logic, and types evolve independently
229/// - **Type-safe**: all invariants enforced at compile time
230///
231pub trait LazyBalance:
232 VirtualNMap<
233 Self::Balance,
234 SnapShotStorage,
235 Key = (Self::Id, Self::Variant, Self::Time),
236 Value = Self::SnapShot,
237 Query = Option<Self::SnapShot>,
238>
239where
240 Self: Sized,
241{
242 /// The underlying unit of value tracked by the system.
243 ///
244 /// Represents tokens, points, or any fungible asset.
245 type Asset: Asset;
246
247 /// The numeric representation used for value calculations.
248 ///
249 /// Typically a fixed-point or fractional type ensuring precise arithmetic.
250 type Rational: Fractional;
251
252 /// The temporal index of the system.
253 ///
254 /// A monotonically increasing counter (e.g. block number or timestamp)
255 /// used for snapshotting and time-based computations.
256 type Time: Time;
257
258 /// Logical partition of a balance.
259 ///
260 /// Allows multiple independent balance states per `Id`
261 /// (e.g. free, locked, reserved).
262 type Variant: Delimited + RuntimeEnum + Default;
263
264 /// Identifier of a balance owner or entity.
265 ///
266 /// Combined with `Variant` and `Time` to uniquely address balance state.
267 type Id: Delimited;
268
269 /// Represents the directive / intent of an operation.
270 ///
271 /// Encodes behavioral characteristics such as precision and fortitude,
272 /// influencing how operations are evaluated and executed.
273 ///
274 /// Passed alongside operation inputs to allow context-aware behavior
275 /// (e.g. exact vs approximate, polite vs forceful execution).
276 type Subject: Delimited + Directive + Default;
277
278 /// Primary mutable balance state.
279 ///
280 /// Represents the canonical stored value and is modified by all
281 /// state-transition operations.
282 type Balance: LazyBalanceComponent<
283 Self,
284 BalanceAsset,
285 BalanceRational,
286 BalanceTime,
287 Context<Self>,
288 BalanceAddon,
289 >;
290
291 /// Time-indexed projection of balance state.
292 ///
293 /// Used for historical queries and deferred computations.
294 type SnapShot: LazyBalanceComponent<
295 Self,
296 SnapShotAsset,
297 SnapShotRational,
298 SnapShotTime,
299 Context<Self>,
300 SnapShotAddon,
301 >;
302
303 /// Claim over deposited value.
304 ///
305 /// Issued on deposit and consumed on withdrawal.
306 ///
307 /// A receipt represents the right to redeem value from the balance.
308 /// Its redeemable value may differ from its original deposit value
309 /// due to balance adjustments (e.g. minting or reaping).
310 type Receipt: LazyBalanceComponent<
311 Self,
312 ReceiptAsset,
313 ReceiptRational,
314 ReceiptTime,
315 Context<Self>,
316 ReceiptAddon,
317 >;
318
319 /// Abstract representation of operation limits.
320 ///
321 /// Encapsulates derived values across different extents
322 /// (e.g. minimum, maximum, optimal) for balance operations.
323 ///
324 /// Used by limit query operations to provide safe bounded values
325 /// under current conditions.
326 type Limits: LazyBalanceLimits<Self>;
327
328 /// Input carrier for all operations.
329 ///
330 /// Encodes operation-specific arguments while supporting both
331 /// mutable and immutable access patterns.
332 type Input<'a>: LazyBalanceInput<
333 'a,
334 Self::Balance,
335 Self::Variant,
336 Self::Id,
337 Self::Asset,
338 Self::Receipt,
339 Self,
340 >;
341
342 /// Output carrier for all operations.
343 ///
344 /// Encodes operation-specific results and error handling.
345 type Output<'a>: LazyBalanceOutput<
346 'a,
347 Self::Asset,
348 Self::Receipt,
349 Self::SnapShot,
350 Self::Time,
351 Self::Limits,
352 Self,
353 >;
354
355 // Plugin Family defining all balance behavior.
356 //
357 // The family groups all operations (validation, mutation, queries)
358 // under a single type-level interface.
359 //
360 // Resolution flow:
361 // - `root` defines the set of operation discriminants
362 // - `family` binds those discriminants to concrete implementations
363 // - `context` provides environment, constraints, and dependencies
364 //
365 // Together, they form a compile-time dispatch system where
366 // behavior is selected by discriminant and resolved through context.
367 plugin_types! {
368 input: Self::Input<'a>,
369 output: Self::Output<'a>,
370 borrow: ['a],
371 root: LazyBalanceRoot,
372 /// [`Plugin Family`](crate::plugins) implementing all [`LazyBalance`] operations.
373 ///
374 /// Binds each operation discriminant (defined in [`LazyBalanceRoot`])
375 /// to a concrete plugin model, determining how validation, mutation,
376 /// and query logic is executed.
377 ///
378 /// Combined with the resolved context, this enables compile-time
379 /// dispatch of behavior while keeping logic modular and replaceable.
380 family: BalanceFamily,
381
382 /// Context provider for all [`LazyBalance`] operations via [`Self::BalanceFamily`].
383 ///
384 /// Implements [`ModelContext`], producing a concrete execution
385 /// context whose associated `Context` type must satisfy
386 /// [`LazyBalanceContext`].
387 ///
388 /// Since [`LazyBalance`] components (e.g. Balance, SnapShot, Receipt)
389 /// are modeled as *[`virtual`](crate::virtuals) structures*
390 /// (see [`VirtualDynField`]) and support extensibility
391 /// (see [`VirtualDynExtension`]), they do not
392 /// define their bounds, schemas, or error types internally.
393 ///
394 /// Instead, these are supplied by the resolved context, which is
395 /// required (via [`LazyBalanceContext`]) to provide:
396 ///
397 /// - dynamic bounds for all core dimensions through [`VirtualDynBound`]
398 /// (asset, rational, and time for balance, snapshot, and receipt)
399 /// - extension schemas via [`VirtualDynExtensionSchema`] for addon
400 /// composition across components
401 /// - a unified error type via [`VirtualError`] for all operations
402 ///
403 /// This enforces that all [`plugins`](crate::plugins) in this
404 /// family execute within a fully specified environment where structure,
405 /// constraints, and extensibility are externally defined yet statically guaranteed.
406 context: BalanceContext,
407 provides: [LazyBalanceContext],
408 }
409
410 // ---------- Capability checks ------------
411
412 plugin_output! {
413 /// Returns whether a deposit is permitted.
414 fn can_deposit,
415 input: Self::Input<'a>,
416 output: Self::Output<'a>,
417 borrow: ['a],
418 root: LazyBalanceRoot,
419 family: Self::BalanceFamily<'a>,
420 child: CanDeposit,
421 context: Self::BalanceContext
422 }
423
424 plugin_output! {
425 /// Returns whether a withdrawal is permitted.
426 fn can_withdraw,
427 input: Self::Input<'a>,
428 output: Self::Output<'a>,
429 borrow: ['a],
430 root: LazyBalanceRoot,
431 family: Self::BalanceFamily<'a>,
432 child: CanWithdraw,
433 context: Self::BalanceContext
434 }
435
436 plugin_output! {
437 /// Returns whether minting is permitted.
438 fn can_mint,
439 input: Self::Input<'a>,
440 output: Self::Output<'a>,
441 borrow: ['a],
442 root: LazyBalanceRoot,
443 family: Self::BalanceFamily<'a>,
444 child: CanMint,
445 context: Self::BalanceContext
446 }
447
448 plugin_output! {
449 /// Returns whether reaping is permitted.
450 fn can_reap,
451 input: Self::Input<'a>,
452 output: Self::Output<'a>,
453 borrow: ['a],
454 root: LazyBalanceRoot,
455 family: Self::BalanceFamily<'a>,
456 child: CanReap,
457 context: Self::BalanceContext
458 }
459
460 // ---------- State transitions -------------
461
462 plugin_output! {
463 /// Deposits value into the balance and issues a receipt
464 /// along with depositted amount.
465 fn deposit,
466 input: Self::Input<'a>,
467 output: Self::Output<'a>,
468 borrow: ['a],
469 root: LazyBalanceRoot,
470 family: Self::BalanceFamily<'a>,
471 child: Deposit,
472 context: Self::BalanceContext
473 }
474
475 plugin_output! {
476 /// Withdraws value by consuming a receipt, returning withdrawed amount.
477 fn withdraw,
478 input: Self::Input<'a>,
479 output: Self::Output<'a>,
480 borrow: ['a],
481 root: LazyBalanceRoot,
482 family: Self::BalanceFamily<'a>,
483 child: Withdraw,
484 context: Self::BalanceContext
485 }
486
487 plugin_output! {
488 /// Introduces new value into the balance, returning minted amount.
489 fn mint,
490 input: Self::Input<'a>,
491 output: Self::Output<'a>,
492 borrow: ['a],
493 root: LazyBalanceRoot,
494 family: Self::BalanceFamily<'a>,
495 child: Mint,
496 context: Self::BalanceContext
497 }
498
499 plugin_output! {
500 /// Removes or adjusts value from the balance, returning reaped amount.
501 fn reap,
502 input: Self::Input<'a>,
503 output: Self::Output<'a>,
504 borrow: ['a],
505 root: LazyBalanceRoot,
506 family: Self::BalanceFamily<'a>,
507 child: Reap,
508 context: Self::BalanceContext
509 }
510
511 plugin_output! {
512 /// Clears the balance state, returning drained amount.
513 fn drain,
514 input: Self::Input<'a>,
515 output: Self::Output<'a>,
516 borrow: ['a],
517 root: LazyBalanceRoot,
518 family: Self::BalanceFamily<'a>,
519 child: Drain,
520 context: Self::BalanceContext
521 }
522
523 // ---------- Queries -------------
524
525 plugin_output! {
526 /// Returns the total value of the balance.
527 fn total_value,
528 input: Self::Input<'a>,
529 output: Self::Output<'a>,
530 borrow: ['a],
531 root: LazyBalanceRoot,
532 family: Self::BalanceFamily<'a>,
533 child: TotalValue,
534 context: Self::BalanceContext
535 }
536
537 plugin_output! {
538 /// Returns the current redeemable value of a receipt.
539 fn receipt_active_value,
540 input: Self::Input<'a>,
541 output: Self::Output<'a>,
542 borrow: ['a],
543 root: LazyBalanceRoot,
544 family: Self::BalanceFamily<'a>,
545 child: ReceiptActiveValue,
546 context: Self::BalanceContext
547 }
548
549 plugin_output! {
550 /// Returns whether the balance has any deposits.
551 fn has_deposits,
552 input: Self::Input<'a>,
553 output: Self::Output<'a>,
554 borrow: ['a],
555 root: LazyBalanceRoot,
556 family: Self::BalanceFamily<'a>,
557 child: HasDeposits,
558 context: Self::BalanceContext
559 }
560
561 plugin_output! {
562 /// Returns the original deposited value represented by a receipt.
563 fn receipt_deposit_value,
564 input: Self::Input<'a>,
565 output: Self::Output<'a>,
566 borrow: ['a],
567 root: LazyBalanceRoot,
568 family: Self::BalanceFamily<'a>,
569 child: ReceiptDepositValue,
570 context: Self::BalanceContext
571 }
572
573 plugin_output! {
574 /// Returns limits for deposit operations if any, else permissive.
575 fn deposit_limits,
576 input: Self::Input<'a>,
577 output: Self::Output<'a>,
578 borrow: ['a],
579 root: LazyBalanceRoot,
580 family: Self::BalanceFamily<'a>,
581 child: DepositLimits,
582 context: Self::BalanceContext
583 }
584
585 plugin_output! {
586 /// Returns limits for mint operations if any, else permissive.
587 fn mint_limits,
588 input: Self::Input<'a>,
589 output: Self::Output<'a>,
590 borrow: ['a],
591 root: LazyBalanceRoot,
592 family: Self::BalanceFamily<'a>,
593 child: MintLimits,
594 context: Self::BalanceContext
595 }
596
597 plugin_output! {
598 /// Returns limits for reap operations if any, else permissive.
599 fn reap_limits,
600 input: Self::Input<'a>,
601 output: Self::Output<'a>,
602 borrow: ['a],
603 root: LazyBalanceRoot,
604 family: Self::BalanceFamily<'a>,
605 child: ReapLimits,
606 context: Self::BalanceContext
607 }
608}
609
610// ===============================================================================
611// ```````````````````````````````` PLUGIN FAMILY ````````````````````````````````
612// ===============================================================================
613
614declare_family!(
615 /// [`Plugin Family`](crate::plugins) Root trait for [`LazyBalance`]
616 ///
617 /// Each child (associated-type) acts as a **discriminant** selecting
618 /// a specific operation within the lazy balance model.
619 root: pub LazyBalanceRoot,
620 child: [
621 // ----- Capability checks -----
622
623 /// Discriminant for deposit validation.
624 ///
625 /// Determines whether a deposit operation is permitted.
626 CanDeposit,
627
628 /// Discriminant for withdrawal validation.
629 ///
630 /// Determines whether a receipt can be redeemed.
631 CanWithdraw,
632
633 /// Discriminant for reap validation.
634 ///
635 /// Determines whether value can be removed or adjusted.
636 CanReap,
637
638 /// Discriminant for mint validation.
639 ///
640 /// Determines whether new value can be introduced.
641 CanMint,
642
643
644 // ----- Mutations -----
645
646 /// Discriminant for deposit execution.
647 ///
648 /// Deposits value and issues a receipt.
649 Deposit,
650
651 /// Discriminant for withdrawal execution.
652 ///
653 /// Redeems a receipt, resolving value lazily at withdrawal time.
654 Withdraw,
655
656 /// Discriminant for reap execution.
657 ///
658 /// Removes or adjusts balance value without mutating existing receipts.
659 Reap,
660
661 /// Discriminant for drain execution.
662 ///
663 /// Clears or resets the entire balance state.
664 Drain,
665
666 /// Discriminant for mint execution.
667 ///
668 /// Introduces new value affecting future receipt redemption.
669 Mint,
670
671
672 // ----- Queries -----
673
674 /// Discriminant for total balance query.
675 ///
676 /// Returns the total value held in the balance.
677 TotalValue,
678
679 /// Discriminant for active receipt value query.
680 ///
681 /// Returns the current redeemable value, reflecting lazy adjustments.
682 ReceiptActiveValue,
683
684 /// Discriminant for receipt deposit value query.
685 ///
686 /// Returns the original deposited value, ignoring later adjustments.
687 ReceiptDepositValue,
688
689 /// Discriminant for deposit presence query.
690 ///
691 /// Indicates whether any deposits exist in the balance.
692 HasDeposits,
693
694 // ----- Limits -----
695
696 /// Discriminant for deposit limits query.
697 ///
698 /// Provides limits such as the minimum and maximum deposit
699 /// allowed under given conditions.
700 DepositLimits,
701
702 /// Discriminant for mint limits query.
703 ///
704 /// Provides limits such as the minimum and maximum mint
705 /// allowed under given conditions.
706 MintLimits,
707
708 /// Discriminant for reap limits query.
709 ///
710 /// Provides limits such as the minimum and maximum reap
711 /// allowed under given conditions.
712 ReapLimits,
713 ]
714);
715
716// ===============================================================================
717// ````````````````````` PLUGIN FAMILY's CHILD DISCRIMINANTS `````````````````````
718// ===============================================================================
719
720impl_discriminants! {
721 CanDeposit,
722 CanWithdraw,
723 CanReap,
724 CanMint,
725 Deposit,
726 Withdraw,
727 Reap,
728 Drain,
729 Mint,
730 TotalValue,
731 ReceiptActiveValue,
732 ReceiptDepositValue,
733 HasDeposits,
734 DepositLimits,
735 ReapLimits,
736 MintLimits,
737}
738
739// ===============================================================================
740// ```````````````````````````````` TRAIT ALIASES ````````````````````````````````
741// ===============================================================================
742
743/// Execution context for the [`LazyBalance`] system.
744///
745/// `LazyBalanceContext` aggregates all external dependencies required to
746/// materialize a concrete lazy balance model. It serves as the **type-level
747/// environment** that binds:
748///
749/// - **Bounds** for all core components:
750/// - `Balance` (asset, rational, time)
751/// - `SnapShot` (asset, rational, time)
752/// - `Receipt` (asset, rational, time)
753///
754/// - **Error type** via [`VirtualError`], defining the unified failure mode
755/// for all balance operations.
756///
757/// - **Extension schemas** for:
758/// - [`BalanceAddon`]
759/// - [`SnapShotAddon`]
760/// - [`ReceiptAddon`]
761///
762/// ## Role in the System
763///
764/// This trait does not define behavior directly. Instead, it provides the
765/// **configuration layer** required by:
766///
767/// - [`VirtualDynField`] -> for allocation and capacity constraints
768/// - [`VirtualDynExtension`] -> for external extensibility
769/// - [`Plugin`](crate::plugins) execution -> for resolving context-dependent logic
770///
771/// In essence, it acts as a **dependency injection boundary** at the type level,
772/// allowing the same [`LazyBalance`] implementation to operate under different:
773///
774/// - capacity limits
775/// - extension schemas
776/// - error definitions
777///
778/// without changing its core logic.
779pub trait LazyBalanceContext:
780 VirtualDynBound<BalanceAsset>
781 + VirtualDynBound<BalanceRational>
782 + VirtualDynBound<BalanceTime>
783 + VirtualDynBound<SnapShotAsset>
784 + VirtualDynBound<SnapShotRational>
785 + VirtualDynBound<SnapShotTime>
786 + VirtualDynBound<ReceiptAsset>
787 + VirtualDynBound<ReceiptRational>
788 + VirtualDynBound<ReceiptTime>
789 + VirtualError<LazyBalanceError>
790 + VirtualDynExtensionSchema<BalanceAddon>
791 + VirtualDynExtensionSchema<SnapShotAddon>
792 + VirtualDynExtensionSchema<ReceiptAddon>
793{
794}
795
796impl<T> LazyBalanceContext for T where
797 T: VirtualDynBound<BalanceAsset>
798 + VirtualDynBound<BalanceRational>
799 + VirtualDynBound<BalanceTime>
800 + VirtualDynBound<SnapShotAsset>
801 + VirtualDynBound<SnapShotRational>
802 + VirtualDynBound<SnapShotTime>
803 + VirtualDynBound<ReceiptAsset>
804 + VirtualDynBound<ReceiptRational>
805 + VirtualDynBound<ReceiptTime>
806 + VirtualError<LazyBalanceError>
807 + VirtualDynExtensionSchema<BalanceAddon>
808 + VirtualDynExtensionSchema<SnapShotAddon>
809 + VirtualDynExtensionSchema<ReceiptAddon>
810{
811}
812
813/// Input contract for all [`LazyBalance`] operations.
814///
815/// This trait defines how operation-specific arguments are supplied
816/// through a unified input type.
817///
818/// Each operation (e.g. `Deposit`, `Withdraw`) declares the exact
819/// shape of input it requires via [`VirtualCollector`].
820///
821/// Key semantics:
822/// - Mutating operations receive `MutHandle<Balance>`
823/// - Read-only operations receive `Cow<Balance>`
824/// - Additional parameters (e.g. `Asset`, `Receipt`) are selectively required
825/// - Operations may include a `Subject` to influence execution behavior
826/// (e.g. precision and fortitude)
827///
828/// This enables a single input type to serve all operations while
829/// preserving strict typing per operation.
830pub trait LazyBalanceInput<'a, Balance, Variant, Id, Asset, Receipt, T: LazyBalance>:
831 VirtualCollector<
832 (
833 MutHandle<'a, Balance>,
834 Cow<'a, Variant>,
835 Cow<'a, Id>,
836 Cow<'a, Asset>,
837 Cow<'a, T::Subject>,
838 ),
839 Deposit,
840 > + VirtualCollector<
841 (
842 MutHandle<'a, Balance>,
843 Cow<'a, Variant>,
844 Cow<'a, Id>,
845 Cow<'a, Asset>,
846 Cow<'a, T::Subject>,
847 ),
848 Mint,
849 > + VirtualCollector<
850 (
851 MutHandle<'a, Balance>,
852 Cow<'a, Variant>,
853 Cow<'a, Id>,
854 Cow<'a, Asset>,
855 Cow<'a, T::Subject>,
856 ),
857 Reap,
858 > + VirtualCollector<
859 (
860 MutHandle<'a, Balance>,
861 Cow<'a, Variant>,
862 Cow<'a, Id>,
863 Cow<'a, Receipt>,
864 ),
865 Withdraw,
866 > + VirtualCollector<(MutHandle<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), Drain>
867 + VirtualCollector<
868 (
869 Cow<'a, Balance>,
870 Cow<'a, Variant>,
871 Cow<'a, Id>,
872 Cow<'a, Asset>,
873 Cow<'a, T::Subject>,
874 ),
875 CanDeposit,
876 > + VirtualCollector<
877 (
878 Cow<'a, Balance>,
879 Cow<'a, Variant>,
880 Cow<'a, Id>,
881 Cow<'a, Asset>,
882 Cow<'a, T::Subject>,
883 ),
884 CanMint,
885 > + VirtualCollector<
886 (
887 Cow<'a, Balance>,
888 Cow<'a, Variant>,
889 Cow<'a, Id>,
890 Cow<'a, Asset>,
891 Cow<'a, T::Subject>,
892 ),
893 CanReap,
894 > + VirtualCollector<
895 (
896 Cow<'a, Balance>,
897 Cow<'a, Variant>,
898 Cow<'a, Id>,
899 Cow<'a, Receipt>,
900 ),
901 CanWithdraw,
902 > + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), TotalValue>
903 + VirtualCollector<
904 (
905 Cow<'a, Balance>,
906 Cow<'a, Variant>,
907 Cow<'a, Id>,
908 Cow<'a, Receipt>,
909 ),
910 ReceiptActiveValue,
911 > + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), HasDeposits>
912 + VirtualCollector<Cow<'a, Receipt>, ReceiptDepositValue>
913 + VirtualCollector<
914 (
915 Cow<'a, Balance>,
916 Cow<'a, Variant>,
917 Cow<'a, Id>,
918 Cow<'a, T::Subject>,
919 ),
920 DepositLimits,
921 > + VirtualCollector<
922 (
923 Cow<'a, Balance>,
924 Cow<'a, Variant>,
925 Cow<'a, Id>,
926 Cow<'a, T::Subject>,
927 ),
928 MintLimits,
929 > + VirtualCollector<
930 (
931 Cow<'a, Balance>,
932 Cow<'a, Variant>,
933 Cow<'a, Id>,
934 Cow<'a, T::Subject>,
935 ),
936 ReapLimits,
937 >
938where
939 Balance: 'a + Clone,
940 Variant: 'a + Clone,
941 Id: 'a + Clone,
942 Asset: 'a + Clone,
943 Receipt: 'a + Clone,
944{
945}
946
947impl<'a, T, Balance, Variant, Id, Asset, Receipt, B>
948 LazyBalanceInput<'a, Balance, Variant, Id, Asset, Receipt, B> for T
949where
950 Balance: 'a + Clone,
951 Variant: 'a + Clone,
952 Id: 'a + Clone,
953 Asset: 'a + Clone,
954 Receipt: 'a + Clone,
955 B: LazyBalance,
956 T: VirtualCollector<
957 (
958 MutHandle<'a, Balance>,
959 Cow<'a, Variant>,
960 Cow<'a, Id>,
961 Cow<'a, Asset>,
962 Cow<'a, B::Subject>,
963 ),
964 Deposit,
965 > + VirtualCollector<
966 (
967 MutHandle<'a, Balance>,
968 Cow<'a, Variant>,
969 Cow<'a, Id>,
970 Cow<'a, Asset>,
971 Cow<'a, B::Subject>,
972 ),
973 Mint,
974 > + VirtualCollector<
975 (
976 MutHandle<'a, Balance>,
977 Cow<'a, Variant>,
978 Cow<'a, Id>,
979 Cow<'a, Asset>,
980 Cow<'a, B::Subject>,
981 ),
982 Reap,
983 > + VirtualCollector<
984 (
985 MutHandle<'a, Balance>,
986 Cow<'a, Variant>,
987 Cow<'a, Id>,
988 Cow<'a, Receipt>,
989 ),
990 Withdraw,
991 > + VirtualCollector<(MutHandle<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), Drain>
992 + VirtualCollector<
993 (
994 Cow<'a, Balance>,
995 Cow<'a, Variant>,
996 Cow<'a, Id>,
997 Cow<'a, Asset>,
998 Cow<'a, B::Subject>,
999 ),
1000 CanDeposit,
1001 > + VirtualCollector<
1002 (
1003 Cow<'a, Balance>,
1004 Cow<'a, Variant>,
1005 Cow<'a, Id>,
1006 Cow<'a, Asset>,
1007 Cow<'a, B::Subject>,
1008 ),
1009 CanMint,
1010 > + VirtualCollector<
1011 (
1012 Cow<'a, Balance>,
1013 Cow<'a, Variant>,
1014 Cow<'a, Id>,
1015 Cow<'a, Asset>,
1016 Cow<'a, B::Subject>,
1017 ),
1018 CanReap,
1019 > + VirtualCollector<
1020 (
1021 Cow<'a, Balance>,
1022 Cow<'a, Variant>,
1023 Cow<'a, Id>,
1024 Cow<'a, Receipt>,
1025 ),
1026 CanWithdraw,
1027 > + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), TotalValue>
1028 + VirtualCollector<
1029 (
1030 Cow<'a, Balance>,
1031 Cow<'a, Variant>,
1032 Cow<'a, Id>,
1033 Cow<'a, Receipt>,
1034 ),
1035 ReceiptActiveValue,
1036 > + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), HasDeposits>
1037 + VirtualCollector<Cow<'a, Receipt>, ReceiptDepositValue>
1038 + VirtualCollector<
1039 (
1040 Cow<'a, Balance>,
1041 Cow<'a, Variant>,
1042 Cow<'a, Id>,
1043 Cow<'a, B::Subject>,
1044 ),
1045 DepositLimits,
1046 > + VirtualCollector<
1047 (
1048 Cow<'a, Balance>,
1049 Cow<'a, Variant>,
1050 Cow<'a, Id>,
1051 Cow<'a, B::Subject>,
1052 ),
1053 MintLimits,
1054 > + VirtualCollector<
1055 (
1056 Cow<'a, Balance>,
1057 Cow<'a, Variant>,
1058 Cow<'a, Id>,
1059 Cow<'a, B::Subject>,
1060 ),
1061 ReapLimits,
1062 >,
1063{
1064}
1065
1066/// Output contract for all [`LazyBalance`] operations.
1067///
1068/// This trait defines how results are returned for each operation.
1069///
1070/// Each operation maps to a distinct result type via [`VirtualCollector`],
1071/// typically wrapped in `Result<_, Error<T>>`.
1072///
1073/// Key semantics:
1074/// - State-changing operations return success or error
1075/// - Value-producing operations return assets or receipts
1076/// - Validation operations return `Result<(), Error<T>>`
1077///
1078/// This forms a unified, type-indexed result space across all operations.
1079pub trait LazyBalanceOutput<'a, Asset, Receipt, SnapShot, Time, Limits, T>:
1080 VirtualCollector<Result<(Cow<'a, Asset>, Cow<'a, Receipt>), Error<T>>, Deposit>
1081 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Mint>
1082 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Reap>
1083 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Drain>
1084 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Withdraw>
1085 + VirtualCollector<Result<(), Error<T>>, CanDeposit>
1086 + VirtualCollector<Result<(), Error<T>>, CanMint>
1087 + VirtualCollector<Result<(), Error<T>>, CanReap>
1088 + VirtualCollector<Result<(), Error<T>>, CanWithdraw>
1089 + VirtualCollector<Result<(), Error<T>>, HasDeposits>
1090 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, TotalValue>
1091 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, ReceiptActiveValue>
1092 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, ReceiptDepositValue>
1093 + VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, ReceiptDepositValue>
1094 + VirtualCollector<Result<Cow<'a, Limits>, Error<T>>, DepositLimits>
1095 + VirtualCollector<Result<Cow<'a, Limits>, Error<T>>, MintLimits>
1096 + VirtualCollector<Result<Cow<'a, Limits>, Error<T>>, ReapLimits>
1097where
1098 T: LazyBalance,
1099 Asset: 'a + Clone,
1100 Receipt: 'a + Clone,
1101 SnapShot: 'a + Clone,
1102 Time: 'a + Clone,
1103 Limits: 'a + Clone,
1104{
1105}
1106
1107impl<'a, T, Asset, Receipt, SnapShot, Time, Limits, B>
1108 LazyBalanceOutput<'a, Asset, Receipt, SnapShot, Time, Limits, B> for T
1109where
1110 B: LazyBalance,
1111 Asset: 'a + Clone,
1112 Receipt: 'a + Clone,
1113 SnapShot: 'a + Clone,
1114 Time: 'a + Clone,
1115 Limits: 'a + Clone,
1116 T: VirtualCollector<Result<(Cow<'a, Asset>, Cow<'a, Receipt>), Error<B>>, Deposit>
1117 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Mint>
1118 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Reap>
1119 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Drain>
1120 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Withdraw>
1121 + VirtualCollector<Result<(), Error<B>>, CanDeposit>
1122 + VirtualCollector<Result<(), Error<B>>, CanMint>
1123 + VirtualCollector<Result<(), Error<B>>, CanReap>
1124 + VirtualCollector<Result<(), Error<B>>, CanWithdraw>
1125 + VirtualCollector<Result<(), Error<B>>, HasDeposits>
1126 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, TotalValue>
1127 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, ReceiptActiveValue>
1128 + VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, ReceiptDepositValue>
1129 + VirtualCollector<Result<Cow<'a, Limits>, Error<B>>, DepositLimits>
1130 + VirtualCollector<Result<Cow<'a, Limits>, Error<B>>, MintLimits>
1131 + VirtualCollector<Result<Cow<'a, Limits>, Error<B>>, ReapLimits>,
1132{
1133}
1134
1135/// A abstract-container representation of a structured value participating in the
1136/// lazy balance system (e.g. Balance, SnapShot, Receipt).
1137///
1138/// Look on to [`VirtualDynField`] or [`virtuals`](crate::virtuals) for contextual info.
1139///
1140/// It does not fix its internal representation directly. Instead, it:
1141/// - derives its core types (asset, rational, time) from context
1142/// - supports allocation via [`VirtualDynField`]
1143/// - allows extensibility through [`VirtualDynExtension`]
1144///
1145/// This keeps components lightweight, composable, and context-driven,
1146/// rather than self-contained or rigid.
1147pub trait LazyBalanceComponent<T, Asset, Rational, Time, Context, Addon>:
1148 Delimited
1149 + VirtualDynFieldWithDelegatedBounds<T::Asset, Context, Asset>
1150 + VirtualDynFieldWithDelegatedBounds<T::Rational, Context, Rational>
1151 + VirtualDynFieldWithDelegatedBounds<T::Time, Context, Time>
1152 + DelegateVirtualDynExtension<Context, Addon>
1153where
1154 T: LazyBalance,
1155 Context: VirtualDynExtensionSchema<Addon>
1156 + VirtualDynBound<Asset>
1157 + VirtualDynBound<Rational>
1158 + VirtualDynBound<Time>,
1159 Addon: DiscriminantTag,
1160 Rational: DiscriminantTag,
1161 Time: DiscriminantTag,
1162 Asset: DiscriminantTag,
1163{
1164}
1165
1166impl<B, T, Asset, Rational, Time, Context, Addon>
1167 LazyBalanceComponent<B, Asset, Rational, Time, Context, Addon> for T
1168where
1169 T: Delimited
1170 + VirtualDynFieldWithDelegatedBounds<B::Asset, Context, Asset>
1171 + VirtualDynFieldWithDelegatedBounds<B::Rational, Context, Rational>
1172 + VirtualDynFieldWithDelegatedBounds<B::Time, Context, Time>
1173 + DelegateVirtualDynExtension<Context, Addon>,
1174 B: LazyBalance,
1175 Context: VirtualDynExtensionSchema<Addon>
1176 + VirtualDynBound<Asset>
1177 + VirtualDynBound<Rational>
1178 + VirtualDynBound<Time>,
1179 Addon: DiscriminantTag,
1180 Rational: DiscriminantTag,
1181 Time: DiscriminantTag,
1182 Asset: DiscriminantTag,
1183{
1184}
1185
1186pub trait LazyBalanceLimits<T: LazyBalance>:
1187 Delimited
1188 + VirtualDynField<LimitsAsset, Some = T::Asset>
1189 + VirtualDynBound<LimitsAsset>
1190 + Extent<LimitsAsset, Scalar = T::Asset>
1191{
1192}
1193
1194impl<L, T> LazyBalanceLimits<L> for T
1195where
1196 T: Delimited
1197 + VirtualDynField<LimitsAsset, Some = L::Asset>
1198 + VirtualDynBound<LimitsAsset>
1199 + Extent<LimitsAsset, Scalar = L::Asset>,
1200 L: LazyBalance,
1201{
1202}
1203// ===============================================================================
1204// ```````````````````````````` VIRTUAL DISCRIMINANTS ````````````````````````````
1205// ===============================================================================
1206
1207discriminants! {
1208 /// Discriminant for the asset type within [`LazyBalance::Balance`].
1209 ///
1210 /// Used to resolve the concrete asset binding from context.
1211 BalanceAsset,
1212
1213 /// Discriminant for the numeric representation within [`LazyBalance::Balance`].
1214 ///
1215 /// Identifies how balance values are represented (e.g. fixed-point).
1216 BalanceRational,
1217
1218 /// Discriminant for the time dimension within [`LazyBalance::Balance`].
1219 ///
1220 /// Used to bind the temporal component associated with balance state.
1221 BalanceTime,
1222
1223 /// Discriminant for the asset type within [`LazyBalance::SnapShot`].
1224 ///
1225 /// Separates snapshot asset semantics from live balance state.
1226 SnapShotAsset,
1227
1228 /// Discriminant for the numeric representation within [`LazyBalance::SnapShot`].
1229 ///
1230 /// Defines how snapshot values are expressed.
1231 SnapShotRational,
1232
1233 /// Discriminant for the time dimension within [`LazyBalance::SnapShot`].
1234 ///
1235 /// Used to associate snapshots with specific points in time.
1236 SnapShotTime,
1237
1238 /// Discriminant for the asset type within [`LazyBalance::Receipt`].
1239 ///
1240 /// Identifies the asset representation for receipt claims.
1241 ReceiptAsset,
1242
1243 /// Discriminant for the numeric representation within [`LazyBalance::Receipt`].
1244 ///
1245 /// Defines how receipt values are quantified.
1246 ReceiptRational,
1247
1248 /// Discriminant for the time dimension within [`LazyBalance::Receipt`].
1249 ///
1250 /// Used to track temporal aspects of receipt validity or evolution.
1251 ReceiptTime,
1252
1253 /// Discriminant identifying the storage domain for snapshots within
1254 /// [`LazyBalance`] systems.
1255 ///
1256 /// Used to bind lazy storage behavior for snapshot data.
1257 SnapShotStorage,
1258
1259 /// Discriminant for error types within the [`LazyBalance`] system.
1260 ///
1261 /// Allows context to provide a concrete error type for all operations.
1262 LazyBalanceError,
1263
1264 /// Discriminant for additional balance-specific extensions.
1265 ///
1266 /// Used to inject custom logic or metadata into [`LazyBalance::Balance`].
1267 BalanceAddon,
1268
1269 /// Discriminant for additional snapshot-specific extensions.
1270 ///
1271 /// Used to extend snapshot behavior or attach auxiliary data.
1272 SnapShotAddon,
1273
1274 /// Discriminant for additional receipt-specific extensions.
1275 ///
1276 /// Used to extend receipt behavior or attach auxiliary data.
1277 ReceiptAddon,
1278
1279 /// Discriminant for the asset type used in operation limits.
1280 ///
1281 /// Binds the value representation used when deriving limits
1282 /// (e.g. minimum, maximum, optimal) for balance operations.
1283 LimitsAsset,
1284
1285}
1286
1287// ===============================================================================
1288// ````````````````````````` LAZY BALANCE MODEL CHECKER ``````````````````````````
1289// ===============================================================================
1290
1291#[cfg(feature = "std")]
1292pub use lazy_balance_model_checker::*;
1293
1294/// Model checker module for [`LazyBalance`] implementations.
1295///
1296/// Use [`LazyBalanceModelChecker`] as the **entrypoint** for all model
1297/// checking operations.
1298///
1299/// This module exposes the required types:
1300/// - [`ManualBalanceModel`]
1301/// - [`BalanceOp`]
1302/// - [`BalanceState`]
1303/// - [`BalanceGuards`]
1304/// - [`BalanceTraps`]
1305/// - [`BalanceModelResults`]
1306///
1307/// These types are used to define the testing environment:
1308/// - reference model behavior
1309/// - operation space
1310/// - state representation
1311/// - validity and trap conditions
1312///
1313/// The execution engine is internal; interact with it only through
1314/// the trait methods.
1315#[cfg(feature = "std")]
1316mod lazy_balance_model_checker {
1317
1318 // ===============================================================================
1319 // ``````````````````````````````````` IMPORTS ```````````````````````````````````
1320 // ===============================================================================
1321
1322 // --- Local crate imports ---
1323 use crate::{assets::*, base::Sortable};
1324
1325 // --- Core (Rust std replacement) ---
1326 use core::{fmt::Debug, hash::Hash};
1327
1328 // --- Substrate primitives ---
1329 use sp_runtime::{traits::One, Cow};
1330
1331 // --- Substrate std helpers ---
1332 use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet, vec_deque::VecDeque};
1333
1334 // --- Standard library ---
1335 use std::{
1336 fs::{create_dir_all, File},
1337 io::Write,
1338 path::PathBuf,
1339 time::{SystemTime, UNIX_EPOCH},
1340 };
1341
1342 // --- Randomness Provider ---
1343 use rand::{seq::SliceRandom, thread_rng};
1344
1345 // ===============================================================================
1346 // ```````````````````` LAZY BALANCE MODEL CHECKER ENTRYPOINT ````````````````````
1347 // ===============================================================================
1348
1349 /// Unified model checker interface over the lazy balance
1350 /// model checker module.
1351 ///
1352 /// This trait provides a **single entrypoint API** to access
1353 /// all model-checking utilities.
1354 ///
1355 /// By implementing this trait on a tester type, you bind:
1356 /// - the lazy model ([`Self::LazyBalance`]) under test
1357 /// - the manual reference model ([`Self::ManualBalance`])
1358 /// - guards, hashing, and execution semantics
1359 ///
1360 /// ## Underlying System
1361 ///
1362 /// - [`ManualBalanceModel`]: eager balance reference model
1363 /// - [`BalanceOp`]: balance operations set
1364 /// - [`BalanceState`]: execution state (lazy + manual + trace)
1365 /// - [`BalanceGuards`]: operation validity rules
1366 /// - [`BalanceTraps`]: controlled invalid execution
1367 /// - [`BalanceModelResults`]: result aggregation
1368 ///
1369 /// ## What happens during execution
1370 ///
1371 /// - sequences of [`BalanceOp`] are explored (breadth-first)
1372 /// - each step builds on a **trace** to form a [`BalanceState`]
1373 /// - operations are filtered using:
1374 /// - [`BalanceGuards`] -> define normal validity
1375 /// - [`BalanceTraps`] -> allow controlled invalid cases
1376 ///
1377 /// An operation is considered **valid for execution** if:
1378 /// - it passes the `guard`, OR
1379 /// - it is explicitly allowed by a `trap`
1380 ///
1381 /// Additionally, exploration is further controlled by `flow`:
1382 /// - both `guard flow` and `trap flow` must allow the operation
1383 /// - if either blocks it, the operation is not explored further
1384 ///
1385 /// - both models are executed:
1386 /// - lazy: [`Self::LazyBalance`] via [`LazyBalance`]
1387 /// - manual: [`Self::ManualBalance`] vi [`ManualBalanceModel`]
1388 ///
1389 /// - results are classified into:
1390 /// - [`BalanceFailure`]: execution error
1391 /// - [`BalanceExit`]: drift between models
1392 /// - `pass`: successful sequence
1393 /// - `trap`: expected failure via [`BalanceTraps`]
1394 ///
1395 /// ## Usage
1396 ///
1397 /// ```ignore
1398 /// struct Tester;
1399 ///
1400 /// impl LazyBalanceModelChecker for Tester {
1401 /// type LazyBalance = Balance;
1402 /// type ManualBalance = Manual;
1403 ///
1404 /// type TrapFn = fn(
1405 /// &BalanceState<Self::LazyBalance, Self::ManualBalance>,
1406 /// &BalanceOp<Self::LazyBalance, Self::ManualBalance>,
1407 /// ) -> bool;
1408 ///
1409 /// type FlowFn = fn(
1410 /// &BalanceState<Self::LazyBalance, Self::ManualBalance>,
1411 /// &BalanceOp<Self::LazyBalance, Self::ManualBalance>,
1412 /// ) -> bool;
1413 ///
1414 /// type Hasher = fn(
1415 /// &BalanceState<Self::LazyBalance, Self::ManualBalance>,
1416 /// ) -> u64;
1417 /// }
1418 ///
1419 /// let mut results = Tester::initiate_results();
1420 ///
1421 /// Tester::explore(..., &mut results);
1422 /// // or
1423 /// Tester::explore_with_traps(..., Some(traps), &mut results);
1424 /// // or
1425 /// Tester::explore_custom(..., Some(traps), &mut results);
1426 ///
1427 /// Tester::write_reports(path, &results, false, false, false);
1428 /// ```
1429 pub trait LazyBalanceModelChecker: Sized {
1430 /// Lazy balance implementation.
1431 type LazyBalance: LazyBalanceMarker;
1432
1433 /// Manual (reference) model used for validation.
1434 ///
1435 /// This type provides an **eager execution layer** over the lazy model:
1436 /// - consumes resolved outputs from [`Self::LazyBalance`] via [`LazyBalance`]
1437 /// - maintains a concrete, user-facing state
1438 ///
1439 /// Unlike the lazy model, this operates as a **direct global
1440 /// state model** via [`ManualBalanceModel`] methods:
1441 /// - no deferred receipts
1442 /// - no lazy resolution
1443 /// - updates are applied immediately to a single state
1444 ///
1445 /// It must also define:
1446 /// - [`BalanceStateHasher`]: for state deduplication during exploration
1447 /// - [`BalanceGuards`]: for operation validity and invariants
1448 ///
1449 /// Acts as the **reference baseline** against which the lazy model
1450 /// is evaluated for correctness and precision.
1451 type ManualBalance: ManualBalanceModel<Self::LazyBalance>
1452 + BalanceStateHasher<Self::LazyBalance, Self::ManualBalance>
1453 + BalanceGuards<Self::LazyBalance, Self::ManualBalance>
1454 + Clone;
1455
1456 /// Trap predicate used for trap-based testing.
1457 ///
1458 /// Represents a **single trap condition** applied across all operations:
1459 /// - evaluated per `(state, op)`
1460 /// - returns `true` to override guards defined in [`BalanceGuards`]
1461 /// via [`Self::ManualBalance`]
1462 ///
1463 /// This trap is **injected globally** during exploration, so it will be
1464 /// evaluated for every operation ([`BalanceOp`]). Implementations should therefore:
1465 /// - precisely target the intended operation/condition
1466 /// - return `false` for all unrelated operations
1467 ///
1468 /// Unlike [`BalanceGuards`], which define validity for all operations at once,
1469 /// this is defined **per invocation** to inject a specific invalid scenario.
1470 ///
1471 /// Used to enable controlled violations:
1472 /// ```text
1473 /// guard(...) || trap(...)
1474 /// ```
1475 type TrapFn: Fn(
1476 &BalanceState<Self::LazyBalance, Self::ManualBalance>,
1477 &BalanceOp<Self::LazyBalance, Self::ManualBalance>,
1478 ) -> bool;
1479
1480 /// Flow predicate used to control exploration.
1481 ///
1482 /// Applied globally across all operations:
1483 /// - evaluated per `(state, op)`
1484 /// - returns `true` to allow the candidate operation [`BalanceOp`] to proceed
1485 /// - returns `false` to block exploration of that operation
1486 ///
1487 /// Combined with the base guard flow as:
1488 /// ```text
1489 /// guard_flow(...) && trap_flow(...)
1490 /// ```
1491 ///
1492 /// Unlike [`Self::TrapFn`], this does **not override validity**;
1493 /// it only restricts whether an operation is explored.
1494 ///
1495 /// Since this is combined using `&&`, returning `false` will
1496 /// **prevent the operation from being explored entirely**.
1497 /// Therefore implementations should:
1498 /// - return `true` by default
1499 /// - only return `false` when explicitly pruning a specific case
1500 ///
1501 /// Used to shape the search space (e.g., pruning, targeting).
1502 type FlowFn: Fn(
1503 &BalanceState<Self::LazyBalance, Self::ManualBalance>,
1504 &BalanceOp<Self::LazyBalance, Self::ManualBalance>,
1505 ) -> bool;
1506
1507 /// Optional additional custom state hasher.
1508 ///
1509 /// Provides an additional hashing layer over [`BalanceState`] during exploration:
1510 /// - evaluated alongside [`BalanceStateHasher`] from [`Self::ManualBalance`]
1511 /// - used for further deduplication of states
1512 ///
1513 /// Since [`Self::ManualBalance`] already defines a primary hashing strategy via
1514 /// [`BalanceStateHasher`], this acts as an **optional second-stage filter**.
1515 ///
1516 /// This is particularly useful when:
1517 /// - multiple exploration strategies or model checkers are run
1518 /// - additional pruning is needed beyond the primary state hash
1519 ///
1520 /// In such cases, this hasher can be applied **on top of the base hasher**
1521 /// to refine state uniqueness and avoid revisiting equivalent states.
1522 ///
1523 /// If provided, both hashes are used during exploration.
1524 type Hasher: Fn(&BalanceState<Self::LazyBalance, Self::ManualBalance>) -> u64;
1525
1526 /// Creates a new [`BalanceModelResults`] container.
1527 fn initiate_results() -> BalanceModelResults<Self::LazyBalance, Self::ManualBalance> {
1528 BalanceModelResults::new()
1529 }
1530
1531 /// Runs model exploration under valid conditions (guards only).
1532 ///
1533 /// Explores sequences of operations using [`BalanceGuards`] without any
1534 /// trap overrides.
1535 ///
1536 /// ## Parameters
1537 ///
1538 /// - `users`: set of users participating in operations
1539 /// - `deposits`: input values used for deposit operations
1540 /// - `adjustments`: values used for mint / reap operations
1541 /// - `subjects`: contextual inputs (e.g. precision, forced flags)
1542 /// - `max_depth`: maximum sequence length to explore
1543 /// - `allowed_bps`: maximum allowed drift in basis points
1544 /// (e.g. 10 = 0.10%, 100 = 1%)
1545 /// - `allowed_diff`: maximum allowed absolute value difference
1546 /// (not percentage-based)
1547 /// - `results`: mutable container collecting outcomes
1548 ///
1549 /// ## Behavior
1550 ///
1551 /// - generates operation sequences up to `max_depth`
1552 /// - filters using [`BalanceGuards`] (`guard(...)`)
1553 /// - executes both lazy and manual models
1554 /// - compares results and records:
1555 /// - `pass`: valid execution
1556 /// - `fail`: error
1557 /// - `exit`: withdrawals drifts between balance models
1558 ///
1559 /// This is the standard correctness check without testing invalid scenarios.
1560 fn explore(
1561 users: &[<Self::ManualBalance as ManualBalanceModel<Self::LazyBalance>>::User],
1562 deposits: &[<Self::LazyBalance as LazyBalance>::Asset],
1563 adjustments: &[<Self::LazyBalance as LazyBalance>::Asset],
1564 subjects: &[<Self::LazyBalance as LazyBalance>::Subject],
1565 max_depth: u32,
1566 allowed_bps: u32,
1567 allowed_diff: u32,
1568 results: &mut BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
1569 ) where
1570 <Self::LazyBalance as LazyBalance>::Id: Default,
1571 <Self::LazyBalance as LazyBalance>::Asset: From<<Self::LazyBalance as LazyBalance>::Asset>
1572 + Into<<Self::LazyBalance as LazyBalance>::Asset>,
1573 {
1574 explore_explore_balance_states_default::<Self::LazyBalance, Self::ManualBalance>(
1575 users,
1576 deposits,
1577 adjustments,
1578 subjects,
1579 max_depth,
1580 allowed_bps,
1581 allowed_diff,
1582 results,
1583 )
1584 }
1585
1586 /// Runs exploration with trap overrides (`guard || trap`).
1587 ///
1588 /// Extends [`Self::explore`] by allowing controlled violations
1589 /// of guards using [`BalanceTraps`].
1590 ///
1591 /// ## Parameters
1592 ///
1593 /// - `users`: set of users participating in operations
1594 /// - `deposits`: input values used for deposit operations
1595 /// - `adjustments`: values used for mint / reap operations
1596 /// - `subjects`: contextual inputs (e.g. precision, forced flags)
1597 /// - `max_depth`: maximum sequence length to explore
1598 /// - `allowed_bps`: maximum allowed drift in basis points
1599 /// (e.g. 10 = 0.10%, 100 = 1%)
1600 /// - `allowed_diff`: maximum allowed absolute value difference
1601 /// - `traps`: optional trap configuration (trap, flow, reason)
1602 /// - `results`: mutable container collecting outcomes
1603 ///
1604 /// ## Behavior
1605 ///
1606 /// - operations are allowed using:
1607 /// ```text
1608 /// guard(...) || trap(...)
1609 /// ```
1610 ///
1611 /// - `trap` is applied globally to every operation:
1612 /// - should return `true` only for the intended operation(s)
1613 /// - should return `false` by default for all unrelated operations
1614 ///
1615 /// - `flow` further filters exploration:
1616 /// ```text
1617 /// guard_flow(...) && trap_flow(...)
1618 /// ```
1619 /// - should return `true` for the intended operation(s) or by default
1620 /// - return `false` **only to explicitly block** exploration of a case
1621 ///
1622 /// - typically used to:
1623 /// - force invalid transitions
1624 /// - trigger expected errors
1625 /// - validate edge-case behavior
1626 ///
1627 /// - executes both lazy and manual models and records:
1628 /// - pass / fail / exit / trap outcomes
1629 ///
1630 /// This is used for adversarial and trap-based testing.
1631 fn explore_traps(
1632 users: &[<Self::ManualBalance as ManualBalanceModel<Self::LazyBalance>>::User],
1633 deposits: &[<Self::LazyBalance as LazyBalance>::Asset],
1634 adjustments: &[<Self::LazyBalance as LazyBalance>::Asset],
1635 subjects: &[<Self::LazyBalance as LazyBalance>::Subject],
1636 max_depth: u32,
1637 allowed_bps: u32,
1638 allowed_diff: u32,
1639 traps: Option<BalanceTraps<Self::TrapFn, Self::FlowFn>>,
1640 results: &mut BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
1641 ) where
1642 <Self::LazyBalance as LazyBalance>::Id: Default,
1643 <Self::LazyBalance as LazyBalance>::Asset: From<<Self::LazyBalance as LazyBalance>::Asset>
1644 + Into<<Self::LazyBalance as LazyBalance>::Asset>,
1645 {
1646 explore_balance_trap_states_default::<
1647 Self::LazyBalance,
1648 Self::ManualBalance,
1649 Self::TrapFn,
1650 Self::FlowFn,
1651 >(
1652 users,
1653 deposits,
1654 adjustments,
1655 subjects,
1656 max_depth,
1657 allowed_bps,
1658 allowed_diff,
1659 traps,
1660 results,
1661 )
1662 }
1663
1664 /// Runs full custom exploration (custom traps + optional additional hasher).
1665 ///
1666 /// This is the most flexible entrypoint, allowing control over:
1667 /// - trap behavior
1668 /// - additional state hashing
1669 ///
1670 /// ## Parameters
1671 ///
1672 /// - `users`: set of users participating in operations
1673 /// - `deposits`: input values used for deposit operations
1674 /// - `adjustments`: values used for mint / reap operations
1675 /// - `subjects`: contextual inputs (e.g. precision, forced flags)
1676 /// - `max_depth`: maximum sequence length to explore
1677 /// - `allowed_bps`: maximum allowed drift in basis points
1678 /// (e.g. `10` = 0.10%, `100` = 1%)
1679 /// - `allowed_diff`: maximum allowed absolute value difference
1680 /// - `traps`: optional trap configuration (`guard || trap`)
1681 /// - `hasher`: optional additional state hasher (second-stage pruning)
1682 /// - `results`: mutable container collecting outcomes
1683 ///
1684 /// ## Behavior
1685 ///
1686 /// - combines:
1687 /// - [`BalanceGuards`] -> base validity
1688 /// - [`BalanceTraps`] -> override (`guard || trap`)
1689 /// - `flow` -> exploration control (`guard_flow && trap_flow`)
1690 ///
1691 /// - if `hasher` is provided:
1692 /// - used alongside [`BalanceStateHasher`] from [`Self::ManualBalance`]
1693 /// - enables additional state deduplication
1694 ///
1695 /// - executes both lazy and manual models and records:
1696 /// - pass / fail / exit / trap outcomes
1697 ///
1698 /// Use this when:
1699 /// - testing complex trap scenarios
1700 /// - adding custom state pruning logic
1701 /// - requiring full control over exploration behavior
1702 fn explore_custom(
1703 users: &[<Self::ManualBalance as ManualBalanceModel<Self::LazyBalance>>::User],
1704 deposits: &[<Self::LazyBalance as LazyBalance>::Asset],
1705 adjustments: &[<Self::LazyBalance as LazyBalance>::Asset],
1706 subjects: &[<Self::LazyBalance as LazyBalance>::Subject],
1707 max_depth: u32,
1708 allowed_bps: u32,
1709 allowed_diff: u32,
1710 traps: Option<BalanceTraps<Self::TrapFn, Self::FlowFn>>,
1711 hasher: Option<Self::Hasher>,
1712 results: &mut BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
1713 ) where
1714 <Self::LazyBalance as LazyBalance>::Id: Default,
1715 <Self::LazyBalance as LazyBalance>::Asset: From<<Self::LazyBalance as LazyBalance>::Asset>
1716 + Into<<Self::LazyBalance as LazyBalance>::Asset>,
1717 {
1718 explore_balance_states::<
1719 Self::LazyBalance,
1720 Self::ManualBalance,
1721 Self::TrapFn,
1722 Self::FlowFn,
1723 Self::Hasher,
1724 >(
1725 users,
1726 deposits,
1727 adjustments,
1728 subjects,
1729 max_depth,
1730 allowed_bps,
1731 allowed_diff,
1732 traps,
1733 hasher,
1734 results,
1735 )
1736 }
1737
1738 /// Writes CSV reports for analysis.
1739 ///
1740 /// Exports the contents of [`BalanceModelResults`] into CSV files
1741 /// under the given directory.
1742 ///
1743 /// ## Parameters
1744 ///
1745 /// - `dir`: output directory where CSV files will be created
1746 /// - `results`: collected model checking results
1747 /// - `write_pass`: whether to write successful sequences
1748 /// - `write_exit`: whether to write drift cases ([`BalanceExit`])
1749 /// - `write_trap`: whether to write trapped sequences
1750 ///
1751 /// ## Behavior
1752 ///
1753 /// - always writes **failures**
1754 /// - optionally writes:
1755 /// - `pass`: successful executions
1756 /// - `exit`: drift between lazy and manual models
1757 /// - `trap`: expected trap-triggered sequences
1758 ///
1759 /// - file names are timestamped to avoid overwriting
1760 /// - prints a summary of results after writing
1761 ///
1762 /// Useful for:
1763 /// - debugging failures
1764 /// - analyzing drift behavior
1765 /// - validating trap scenarios
1766 fn write_reports(
1767 dir: std::path::PathBuf,
1768 results: &BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
1769 write_pass: bool,
1770 write_exit: bool,
1771 write_trap: bool,
1772 ) {
1773 balance_states_csv_reports(dir, results, write_pass, write_exit, write_trap)
1774 }
1775 }
1776
1777 // ===============================================================================
1778 // ````````````````````` MODEL CHECKER ENUMS AND STRUCTURES ``````````````````````
1779 // ===============================================================================
1780
1781 /// Operation descriptor for driving both [`LazyBalance`] and
1782 /// [`ManualBalanceModel`] executions in a consistent manner.
1783 ///
1784 /// Each variant represents a high-level balance action along with
1785 /// the required inputs. These operations are typically used in
1786 /// testing or simulation to:
1787 /// - execute the lazy balance model
1788 /// - pass the resolved results into a manual (eager) model
1789 /// - compare and validate behavior across both systems
1790 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1791 pub enum BalanceOp<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
1792 /// Deposit operation.
1793 ///
1794 /// `(user, amount, subject)` where:
1795 /// - `user` is the target account
1796 /// - `amount` is the requested asset value
1797 /// - `subject` defines execution intent (e.g., precision/behavior)
1798 Deposit(M::User, T::Asset, T::Subject),
1799
1800 /// Withdraw operation.
1801 ///
1802 /// `(user)` where:
1803 /// - `user` is the account performing withdrawal
1804 Withdraw(M::User),
1805
1806 /// Mint operation.
1807 ///
1808 /// `(amount, subject)` where:
1809 /// - `amount` is the asset value to introduce
1810 /// - `subject` defines execution intent
1811 Mint(T::Asset, T::Subject),
1812
1813 /// Reap operation.
1814 ///
1815 /// `(amount, subject)` where:
1816 /// - `amount` is the asset value to remove or adjust
1817 /// - `subject` defines execution intent
1818 Reap(T::Asset, T::Subject),
1819
1820 /// Drain (Full-Reap) operation.
1821 ///
1822 /// Resets the entire balance state.
1823 Drain,
1824 }
1825
1826 /// Represents a failure encountered during execution of an operation sequence.
1827 ///
1828 /// This structure is used for:
1829 /// - validating expected failures (trap testing), where a known sequence
1830 /// must produce a specific error
1831 /// - diagnosing mismatches between lazy and manual models
1832 /// - providing reproducible execution traces for debugging
1833 #[derive(Clone, Debug, PartialEq, Eq)]
1834 pub struct BalanceFailure<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
1835 /// The error message, typically derived from the returned error
1836 /// (e.g. via debug formatting), describing why execution failed.
1837 pub reason: String,
1838 /// The trace of operations leading to the failure, where the
1839 /// final operation in the sequence is the one that triggered the error.
1840 pub sequence: Vec<BalanceOp<T, M>>,
1841 }
1842
1843 /// Represents a successful withdrawal where the lazy and manual models
1844 /// produce different outputs.
1845 ///
1846 /// A withdrawal has completed successfully in both models, but the
1847 /// resulting values are not equal, indicating **drift** between the
1848 /// lazy (deferred) model and the manual (eager reference) model.
1849 ///
1850 /// This occurs because withdrawals are state-dependent: prior balance
1851 /// adjustments (`mint`, `reap`) can change the effective value of a
1852 /// receipt, so the redeemed amount may differ from the original deposit.
1853 ///
1854 /// This structure captures:
1855 /// - the outputs from both models
1856 /// - their absolute difference (`diff`)
1857 /// - the difference in basis points (`bps`)
1858 /// - the full operation trace leading to the withdrawal
1859 ///
1860 /// The final operation in `sequence` is always the [`BalanceOp::Withdraw`] that
1861 /// produced this result.
1862 ///
1863 /// [`LazyBalanceModelChecker`] methods may apply tolerance thresholds (e.g. minimum
1864 /// `diff` or `bps`) to ignore negligible drift. If the deviation falls within
1865 /// acceptable bounds, this exit may be discarded.
1866 ///
1867 /// This is primarily used to evaluate whether the lazy model maintains
1868 /// acceptable precision relative to the manual reference model.
1869 #[derive(Clone, Debug, PartialEq, Eq)]
1870 pub struct BalanceExit<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
1871 /// Value returned by the lazy balance model.
1872 pub lazy: T::Asset,
1873
1874 /// Value returned by the manual (reference) model.
1875 pub manual: T::Asset,
1876
1877 /// Absolute difference between `lazy` and `manual`.
1878 pub diff: T::Asset,
1879
1880 /// Difference expressed in basis points.
1881 pub bps: T::Asset,
1882
1883 /// Trace of operations leading to this withdrawal.
1884 /// The last operation is the `Withdraw`.
1885 pub sequence: Vec<BalanceOp<T, M>>,
1886 }
1887
1888 /// Convenience container holding the owned state required to operate
1889 /// on a [`LazyBalance`] instance.
1890 ///
1891 /// This groups the core components needed for execution into a single
1892 /// structure with owned representations.
1893 ///
1894 /// Primarily used to simplify passing and managing balance state
1895 /// across operations and tests.
1896 #[derive(Clone, Debug, PartialEq, Eq)]
1897 pub struct LazyContainer<T: LazyBalanceMarker> {
1898 /// Underlying mutable balance state as owned.
1899 pub balance: T::Balance,
1900
1901 /// Logical partition of the balance.
1902 pub variant: T::Variant,
1903
1904 /// Identifier of the balance owner or context.
1905 pub id: T::Id,
1906 }
1907
1908 /// Primary state container for executing and tracking a sequence of operations.
1909 ///
1910 /// Holds both lazy and manual model states along with auxiliary data
1911 /// required for coordinated execution and validation.
1912 ///
1913 /// This can represent:
1914 /// - a full state snapshot at a given step in a sequence, or
1915 /// - a single evolving state where operations are applied incrementally
1916 ///
1917 /// Used by model checkers to:
1918 /// - execute operations step-by-step
1919 /// - maintain consistency between lazy and manual models
1920 /// - track execution history for validation and debugging
1921 #[derive(Clone, Debug, PartialEq, Eq)]
1922 pub struct BalanceState<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
1923 /// Lazy balance state container.
1924 pub lazy: LazyContainer<T>,
1925
1926 /// Manual (eager reference) model state.
1927 pub manual: M,
1928
1929 /// Latest receipts per user.
1930 pub receipts: BTreeMap<M::User, T::Receipt>,
1931
1932 /// Operation trace leading to this state.
1933 pub trace: Vec<BalanceOp<T, M>>,
1934 }
1935
1936 /// Defines trap behavior for state exploration.
1937 ///
1938 /// `BalanceTraps` provides mechanisms to intentionally allow and explore
1939 /// otherwise disallowed operations (as per [`BalanceGuards`]) for testing
1940 /// and validation purposes.
1941 ///
1942 /// - `trap` is a predicate over `(state, op)` that, when `true`,
1943 /// overrides the guard and allows the operation:
1944 ///
1945 /// ```text
1946 /// guard(...) || trap(...)
1947 /// ```
1948 ///
1949 /// This enables controlled exploration of invalid transitions.
1950 ///
1951 /// - `flow` is an additional exploration filter combined with the
1952 /// base flow using logical AND:
1953 ///
1954 /// ```text
1955 /// guard_flow && trap_flow
1956 /// ```
1957 ///
1958 /// Unlike `trap`, this does not override but restricts execution.
1959 ///
1960 /// - `reason` is the expected failure description i.e., debugged error
1961 /// associated with the trap, typically used for validation or comparison
1962 /// when the trapped operation results in an error.
1963 ///
1964 /// `G` and `F` are function/closure types using [`BalanceState`] and
1965 /// [`BalanceOp`]:
1966 /// - `G: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool`
1967 /// - `F: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool`
1968 pub struct BalanceTraps<G, F> {
1969 /// Predicate that allows an operation even if the guard rejects it.
1970 pub trap: G,
1971
1972 /// Additional flow filter applied with logical AND.
1973 pub flow: F,
1974
1975 /// Expected reason associated with the trapped execution.
1976 pub reason: String,
1977 }
1978
1979 // ===============================================================================
1980 // ```````````````````````````` MODEL CHECKER TRAITS `````````````````````````````
1981 // ===============================================================================
1982
1983 /// Convenience trait alias for a [`LazyBalance`] implementor used in
1984 /// model checking and testing.
1985 pub trait LazyBalanceMarker: LazyBalance + Clone + Debug {}
1986
1987 impl<T> LazyBalanceMarker for T where T: LazyBalance + Clone + Debug {}
1988
1989 /// A user-indexed **eager balance model** over a [`LazyBalance`] system,
1990 /// primarily intended for testing and validation.
1991 ///
1992 /// This model consumes the resolved outputs of lazy balance operations
1993 /// (`lazy_result`) and maintains a concrete, user-facing state without
1994 /// any deferred semantics (no receipts, no lazy evaluation).
1995 ///
1996 /// Unlike [`LazyBalance`], implementations are free to use the simplest
1997 /// possible accounting logic (e.g., a single global state machine or
1998 /// direct user balances), as correctness is derived from the provided
1999 /// results rather than internal computation.
2000 ///
2001 /// It is not a source of truth and must remain consistent with the
2002 /// underlying [`LazyBalance`] execution.
2003 ///
2004 /// This makes it suitable as a **reference model** for:
2005 /// - validating lazy execution behavior
2006 /// - testing invariants and edge cases
2007 /// - comparing expected vs actual outcomes
2008 pub trait ManualBalanceModel<T>: Clone + Debug
2009 where
2010 T: LazyBalanceMarker,
2011 {
2012 /// Simple user identifier type for the model.
2013 type User: Sortable + Hash + Copy;
2014
2015 /// Error type returned by model operations.
2016 type Error: Debug + Clone + 'static;
2017
2018 /// Creates a new instance with an empty manual balance state.
2019 fn new() -> Self;
2020
2021 /// Applies a deposit for `user`.
2022 ///
2023 /// `amount` is the requested input. The lazy model is assumed to have been
2024 /// executed with the same inputs, and `lazy_result` contains its resolved output:
2025 /// - actual deposited value
2026 /// - corresponding receipt
2027 ///
2028 /// This method consumes that result to update the manual balance state.
2029 fn deposit(
2030 &mut self,
2031 user: Self::User,
2032 amount: T::Asset,
2033 lazy_result: &(T::Asset, T::Receipt),
2034 ) -> Result<(), Self::Error>;
2035
2036 /// Applies a withdrawal for `user`.
2037 ///
2038 /// The lazy model is assumed to have been executed with the same inputs,
2039 /// and `lazy_result` contains the resolved asset produced by that withdrawal.
2040 ///
2041 /// This method consumes that result to update the manual balance state.
2042 ///
2043 /// Returns the withdrawn amount.
2044 fn withdraw(
2045 &mut self,
2046 user: Self::User,
2047 lazy_result: &T::Asset,
2048 ) -> Result<T::Asset, Self::Error>;
2049
2050 /// Applies a mint operation.
2051 ///
2052 /// The lazy model is assumed to have been executed with the same input,
2053 /// and `lazy_result` contains the resolved asset produced by that mint.
2054 ///
2055 /// This method consumes that result to update the manual balance state.
2056 fn mint(&mut self, amount: T::Asset, lazy_result: &T::Asset) -> Result<(), Self::Error>;
2057
2058 /// Applies a reap operation.
2059 ///
2060 /// The lazy model is assumed to have been executed with the same input,
2061 /// and `lazy_result` contains the resolved asset produced by that reap.
2062 ///
2063 /// This method consumes that result to update the manual balance state.
2064 fn reap(&mut self, amount: T::Asset, lazy_result: &T::Asset) -> Result<(), Self::Error>;
2065
2066 /// Applies a drain (full reap), resetting the manual balance state.
2067 ///
2068 /// Brings the model into a fully drained state, consistent with a
2069 /// corresponding lazy balance drain operation.
2070 fn drain(&mut self) -> Result<(), Self::Error>;
2071
2072 /// Returns the total aggregated value tracked by the model.
2073 ///
2074 /// This is a utility method for external use and is not used by
2075 /// the model checker during exploration.
2076 fn total(&self) -> T::Asset;
2077 }
2078
2079 /// Defines a primary hashing strategy for [`BalanceState`] instances.
2080 ///
2081 /// Used to uniquely identify a state during exploration or testing,
2082 /// allowing model checkers to avoid revisiting previously seen states.
2083 ///
2084 /// Implementors can choose any hashing scheme appropriate for their
2085 /// use case, as long as it provides sufficient uniqueness for pruning
2086 /// duplicate states during operation sequencing.
2087 ///
2088 /// This is typically used to:
2089 /// - detect already explored states
2090 /// - prevent redundant execution paths
2091 /// - guide search strategies over operation sequences
2092 pub trait BalanceStateHasher<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
2093 /// Computes a hash representing the given state.
2094 fn hash(state: &BalanceState<T, M>) -> u64;
2095 }
2096
2097 /// Defines validity checks for operations during state exploration.
2098 ///
2099 /// Each method determines whether a candidate operation ([`BalanceOp`]) can be
2100 /// appended to the current trace, i.e., whether the next step in the sequence is
2101 /// valid given the current state.
2102 ///
2103 /// - A **sequence** is an ordered list of operations (`Vec<BalanceOp>`) being explored.
2104 /// - The **trace** is the current prefix of that sequence already applied
2105 /// to reach the current [`BalanceState`].
2106 ///
2107 /// Each guard method receives the current state (derived from the trace)
2108 /// and the inputs of the next operation, and returns:
2109 /// - `true`: operation is allowed
2110 /// - `false`: operation is disallowed (guarded)
2111 ///
2112 /// During execution, an operation ([`BalanceOp`]) is permitted if:
2113 ///
2114 /// ```text
2115 /// guard(...) || trap(...)
2116 /// ```
2117 ///
2118 /// - `guard(...)` is the normal allow/disallow check (this trait)
2119 /// - [`BalanceTraps`] may override and allow a disallowed operation
2120 ///
2121 /// ## Flow
2122 ///
2123 /// `flow` is an additional filter applied after guards:
2124 ///
2125 /// ```text
2126 /// guard_flow(...) && trap_flow(...)
2127 /// ```
2128 ///
2129 /// - Both must return `true` for the operation to proceed
2130 /// - Unlike guards, flow is combined (`&&`), not overridden
2131 /// - Used only to control exploration, not correctness
2132 ///
2133 /// ## Invariant
2134 ///
2135 /// `invariant` must hold for every state reached via the
2136 /// trace evaluated at each step
2137 ///
2138 /// ## Example
2139 ///
2140 /// ```text
2141 /// trace: [Deposit(A, 100)]
2142 /// next: Withdraw(A)
2143 ///
2144 /// guard_withdraw(state, A) = true -> allowed (valid transition)
2145 ///
2146 /// --------
2147 ///
2148 /// trace: []
2149 /// next: Withdraw(A)
2150 ///
2151 /// guard_withdraw(state, A) = false -> disallowed (no receipt)
2152 ///
2153 /// trap_override(...) = true -> allowed (forced invalid case for testing)
2154 ///
2155 /// flow(...) = false -> blocked regardless of guard/trap
2156 /// ```
2157 ///
2158 /// BalanceGuards should only express validity conditions for extending the
2159 /// sequence. They should not include exploration logic or testing behavior.
2160 pub trait BalanceGuards<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
2161 /// Returns `true` if [`BalanceOp::Deposit`] is allowed to be appended
2162 /// to the current trace, given the current state and inputs.
2163 ///
2164 /// `false` disallows the operation.
2165 fn deposit(
2166 _state: &BalanceState<T, M>,
2167 _user: &M::User,
2168 _amount: &T::Asset,
2169 _subject: &T::Subject,
2170 ) -> bool {
2171 true
2172 }
2173
2174 /// Returns `true` if [`BalanceOp::Withdraw`] is allowed to be appended
2175 /// to the current trace, given the current state and inputs.
2176 ///
2177 /// `false` disallows the operation.
2178 fn withdraw(_state: &BalanceState<T, M>, _user: &M::User) -> bool {
2179 true
2180 }
2181
2182 /// Returns `true` if [`BalanceOp::Mint`] is allowed to be appended
2183 /// to the current trace, given the current state and inputs.
2184 ///
2185 /// `false` disallows the operation.
2186 fn mint(_state: &BalanceState<T, M>, _value: &T::Asset, _subject: &T::Subject) -> bool {
2187 true
2188 }
2189
2190 /// Returns `true` if [`BalanceOp::Reap`] is allowed to be appended
2191 /// to the current trace, given the current state and inputs.
2192 ///
2193 /// `false` disallows the operation.
2194 fn reap(_state: &BalanceState<T, M>, _value: &T::Asset, _subject: &T::Subject) -> bool {
2195 true
2196 }
2197
2198 /// Returns `true` if [`BalanceOp::Drain`] is allowed to be appended
2199 /// to the current trace, given the current state and inputs.
2200 ///
2201 /// `false` disallows the operation.
2202 fn drain(_state: &BalanceState<T, M>) -> bool {
2203 true
2204 }
2205
2206 /// Additional exploration filter applied after guards.
2207 ///
2208 /// Returns `true` if the candidate operation should be explored further.
2209 /// Combined with [`BalanceTraps`] `flow` using logical AND (`&&`) if traps
2210 /// are initiated.
2211 fn flow(_state: &BalanceState<T, M>, _next: &BalanceOp<T, M>) -> bool {
2212 true
2213 }
2214
2215 /// Global invariant over the current state.
2216 ///
2217 /// Must hold for every state reached via the trace:
2218 /// - `Ok(())` -> state is valid
2219 /// - `Err(reason)` -> invariant violated, path terminates
2220 fn invariant(_state: &BalanceState<T, M>) -> Result<(), String> {
2221 Ok(())
2222 }
2223 }
2224
2225 // ===============================================================================
2226 // `````````````````````````````` MODEL CHECKER API ``````````````````````````````
2227 // ===============================================================================
2228
2229 /// Default state exploration entrypoint for [`LazyBalance`].
2230 ///
2231 /// Uses:
2232 /// - [`BalanceGuards`] for validity
2233 /// - no [`BalanceTraps`] (strict valid execution only)
2234 /// - Utilizes state hashing using [`BalanceStateHasher`]
2235 ///
2236 /// Explores sequences up to max_depth, applying operations that pass:
2237 /// - `guard(...)`
2238 /// - `flow(...)`
2239 ///
2240 /// Records:
2241 /// - successful sequences
2242 /// - failures
2243 /// - exits (drift between lazy and manual models)
2244 /// - asserts [`BalanceGuards::invariant`] at every state
2245 ///
2246 /// This is the standard model check under valid system behavior only.
2247 fn explore_explore_balance_states_default<T, M>(
2248 users: &[M::User],
2249 deposits: &[T::Asset],
2250 adjustments: &[T::Asset],
2251 subjects: &[T::Subject],
2252 max_depth: u32,
2253 allowed_bps: u32,
2254 allowed_diff: u32,
2255 results: &mut BalanceModelResults<T, M>,
2256 ) where
2257 T: LazyBalanceMarker + Clone,
2258 M: ManualBalanceModel<T> + BalanceStateHasher<T, M> + BalanceGuards<T, M> + Clone,
2259 T::Id: Default,
2260 T::Asset: From<T::Asset> + Into<T::Asset>,
2261 {
2262 explore_balance_states::<
2263 T,
2264 M,
2265 fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
2266 fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
2267 fn(&BalanceState<T, M>) -> u64,
2268 >(
2269 users,
2270 deposits,
2271 adjustments,
2272 subjects,
2273 max_depth,
2274 allowed_bps,
2275 allowed_diff,
2276 None,
2277 None,
2278 results,
2279 );
2280 }
2281
2282 // BalanceState exploration with trap overrides for [`LazyBalance`].
2283 ///
2284 /// Extends [`explore_explore_balance_states_default`] by allowing controlled
2285 /// violations of [`BalanceGuards`] via [`BalanceTraps`].
2286 ///
2287 /// Uses:
2288 /// - [`BalanceGuards`] for baseline validity
2289 /// - [`BalanceTraps`] to override guards (`guard || trap`)
2290 /// - trap-specific flow filtering (`guard_flow && trap_flow`)
2291 ///
2292 /// This enables exploration of:
2293 /// - invalid transitions
2294 /// - edge cases
2295 /// - expected failure scenarios
2296 ///
2297 /// `reason` from [`BalanceTraps`] is used to validate or tag resulting errors.
2298 ///
2299 /// Suitable for trap testing and adversarial validation.
2300 fn explore_balance_trap_states_default<T, M, G, F>(
2301 users: &[M::User],
2302 deposits: &[T::Asset],
2303 adjustments: &[T::Asset],
2304 subjects: &[T::Subject],
2305 max_depth: u32,
2306 allowed_bps: u32,
2307 allowed_diff: u32,
2308 overrides: Option<BalanceTraps<G, F>>,
2309 results: &mut BalanceModelResults<T, M>,
2310 ) where
2311 T: LazyBalanceMarker + Clone,
2312 M: ManualBalanceModel<T> + BalanceStateHasher<T, M> + BalanceGuards<T, M> + Clone,
2313 T::Id: Default,
2314 T::Asset: From<T::Asset> + Into<T::Asset>,
2315 G: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
2316 F: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
2317 {
2318 explore_balance_states::<T, M, G, F, fn(&BalanceState<T, M>) -> u64>(
2319 users,
2320 deposits,
2321 adjustments,
2322 subjects,
2323 max_depth,
2324 allowed_bps,
2325 allowed_diff,
2326 overrides,
2327 None,
2328 results,
2329 );
2330 }
2331
2332 /// Core state exploration engine for [`LazyBalance`].
2333 ///
2334 /// Performs **breadth-first** exploration over operation sequences,
2335 /// starting from an initial empty state.
2336 ///
2337 /// ```text
2338 /// -> [Deposit] -> [Deposit, Mint] -> [Deposit, Mint, Withdraw]
2339 /// -> [Mint] -> [Mint, Reap]
2340 /// -> [Reap] -> ...
2341 /// -> [Withdraw] -> ...
2342 /// -> [Drain] -> ...
2343 /// ```
2344 ///
2345 /// Exploration proceeds level by level:
2346 /// - all sequences of length `n` are explored before `n+1`
2347 /// - each step extends the current **trace** by one [`BalanceOp`]
2348 /// - guards / traps / flow determine whether a branch continues
2349 ///
2350 /// At each step:
2351 /// - checks [`BalanceGuards::invariant`] on the current state
2352 /// - generates candidate operations (vector of [`BalanceOp`])
2353 /// - filters using:
2354 /// - `guard_{op}(...) || trap(...)`
2355 /// - `guard_flow_{op}(...) && trap_flow(...)`
2356 ///
2357 /// For each valid operation:
2358 /// - executes lazy model
2359 /// - applies manual model
2360 /// - updates state (receipts, trace)
2361 /// - evaluates withdrawal drift ([`compare_balances_values`])
2362 ///
2363 /// Uses:
2364 /// - visited set with [`BalanceStateHasher`] to avoid revisiting states
2365 /// - optional custom hasher for additional deduplication
2366 ///
2367 /// Records:
2368 /// - errors (failures)
2369 /// - exits (drift beyond tolerance)
2370 /// - successful paths
2371 ///
2372 /// This function defines the full execution semantics for:
2373 /// - guarded exploration
2374 /// - trap-based overrides
2375 /// - state-space traversal
2376 fn explore_balance_states<T, M, G, F, H>(
2377 users: &[M::User],
2378 deposits: &[T::Asset],
2379 adjustments: &[T::Asset],
2380 subjects: &[T::Subject],
2381 max_depth: u32,
2382 allowed_bps: u32,
2383 allowed_diff: u32,
2384 overrides: Option<BalanceTraps<G, F>>,
2385 hasher: Option<H>,
2386 results: &mut BalanceModelResults<T, M>,
2387 ) where
2388 T: LazyBalanceMarker + Clone,
2389 M: ManualBalanceModel<T> + BalanceStateHasher<T, M> + BalanceGuards<T, M> + Clone,
2390 T::Id: Default,
2391 T::Asset: From<T::Asset> + Into<T::Asset>,
2392 G: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
2393 F: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
2394 H: Fn(&BalanceState<T, M>) -> u64,
2395 {
2396 let mut visited = BTreeSet::new();
2397 let mut queue = VecDeque::new();
2398 let mut rng = thread_rng();
2399
2400 let traps = overrides.as_ref().map(|v| &v.reason);
2401
2402 let flow_ok = |state: &BalanceState<T, M>, op: &BalanceOp<T, M>| {
2403 let base = <M as BalanceGuards<T, M>>::flow(state, op);
2404
2405 if let Some(o) = &overrides {
2406 base && (o.flow)(state, op)
2407 } else {
2408 base
2409 }
2410 };
2411
2412 let guard_ok = |state: &BalanceState<T, M>, op: &BalanceOp<T, M>| {
2413 let base = match op {
2414 BalanceOp::Deposit(u, v, l) => <M as BalanceGuards<T, M>>::deposit(state, u, v, l),
2415 BalanceOp::Withdraw(u) => <M as BalanceGuards<T, M>>::withdraw(state, u),
2416 BalanceOp::Mint(v, l) => <M as BalanceGuards<T, M>>::mint(state, v, l),
2417 BalanceOp::Reap(v, l) => <M as BalanceGuards<T, M>>::reap(state, v, l),
2418 BalanceOp::Drain => <M as BalanceGuards<T, M>>::drain(state),
2419 };
2420
2421 let trap_override = overrides
2422 .as_ref()
2423 .map(|o| (o.trap)(state, op))
2424 .unwrap_or(false);
2425
2426 base || trap_override
2427 };
2428
2429 let push_trace = |trace: &[BalanceOp<T, M>], op| {
2430 let mut t = trace.to_vec();
2431 t.push(op);
2432 t
2433 };
2434
2435 queue.push_back(BalanceState {
2436 lazy: LazyContainer::<T> {
2437 balance: Default::default(),
2438 variant: Default::default(),
2439 id: Default::default(),
2440 },
2441 manual: M::new(),
2442 receipts: BTreeMap::new(),
2443 trace: Vec::new(),
2444 });
2445
2446 while let Some(state) = queue.pop_front() {
2447 if let Err(reason) = <M as BalanceGuards<T, M>>::invariant(&state) {
2448 let reason = reason.to_string();
2449 record_err(results, &state.trace, reason, traps);
2450 continue;
2451 }
2452
2453 if state.trace.len() == max_depth as usize {
2454 record_pass(results, &state.trace, traps);
2455 continue;
2456 }
2457
2458 if !visited.insert(<M as BalanceStateHasher<T, M>>::hash(&state)) {
2459 continue;
2460 }
2461
2462 if let Some(ref h) = hasher {
2463 if !visited.insert(h(&state)) {
2464 continue;
2465 }
2466 }
2467
2468 // ---------- DEPOSIT ----------
2469 for &u in users {
2470 for _ in 0..deposits.len() {
2471 if let Some(&amount) = deposits.choose(&mut rng) {
2472 let sub = subjects.choose(&mut rng).cloned().unwrap_or_default();
2473 if !guard_ok(&state, &BalanceOp::Deposit(u, amount, sub.clone())) {
2474 continue;
2475 }
2476
2477 if !flow_ok(&state, &BalanceOp::Deposit(u, amount, sub.clone())) {
2478 continue;
2479 }
2480
2481 let mut next = state.clone();
2482
2483 match deposit::<T>(&mut next.lazy, amount, &sub) {
2484 Ok(pass) => {
2485 if let Err(e) = next.manual.deposit(u, amount, &pass) {
2486 let trace = push_trace(
2487 &state.trace,
2488 BalanceOp::Deposit(u, amount, sub),
2489 );
2490 record_err(results, &trace, format!("{:?}", e), traps);
2491 continue;
2492 }
2493
2494 let (actual, receipt) = pass;
2495 next.receipts.insert(u, receipt);
2496 next.trace.push(BalanceOp::Deposit(u, actual, sub));
2497 queue.push_back(next);
2498 }
2499
2500 Err(e) => {
2501 let trace =
2502 push_trace(&state.trace, BalanceOp::Deposit(u, amount, sub));
2503 record_err(results, &trace, format!("{:?}", e), traps);
2504 }
2505 }
2506 }
2507 }
2508 }
2509
2510 // ---------- WITHDRAW ----------
2511 for &u in users {
2512 if !guard_ok(&state, &BalanceOp::Withdraw(u)) {
2513 continue;
2514 }
2515
2516 if !flow_ok(&state, &BalanceOp::Withdraw(u)) {
2517 continue;
2518 }
2519
2520 let mut next = state.clone();
2521
2522 let Some(receipt) = next.receipts.remove(&u) else {
2523 let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
2524 record_err(
2525 results,
2526 &trace,
2527 "ModelChecker::WithdrawReceiptMissing".into(),
2528 traps,
2529 );
2530 continue;
2531 };
2532
2533 match withdraw::<T>(&mut next.lazy, receipt) {
2534 Ok(lazy_value) => {
2535 let manual_value = match next.manual.withdraw(u, &lazy_value) {
2536 Ok(v) => v,
2537 Err(e) => {
2538 let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
2539 record_err(results, &trace, format!("{:?}", e), traps);
2540 continue;
2541 }
2542 };
2543
2544 let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
2545
2546 if let Err(exit) = compare_balances_values::<T, M>(
2547 lazy_value,
2548 manual_value,
2549 &trace,
2550 allowed_bps,
2551 allowed_diff,
2552 ) {
2553 record_exit(results, exit);
2554 continue;
2555 }
2556
2557 next.trace = trace;
2558 queue.push_back(next);
2559 }
2560
2561 Err(e) => {
2562 let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
2563 record_err(results, &trace, format!("{:?}", e), traps);
2564 }
2565 }
2566 }
2567
2568 // ---------- MINT ----------
2569 for _ in 0..adjustments.len() {
2570 if let Some(&v) = adjustments.choose(&mut rng) {
2571 let sub = subjects.choose(&mut rng).cloned().unwrap_or_default();
2572 if !guard_ok(&state, &BalanceOp::Mint(v, sub.clone())) {
2573 continue;
2574 }
2575
2576 if !flow_ok(&state, &BalanceOp::Mint(v, sub.clone())) {
2577 continue;
2578 }
2579
2580 let mut next = state.clone();
2581
2582 match mint::<T>(&mut next.lazy, v, &sub) {
2583 Ok(pass) => {
2584 if let Err(e) = next.manual.mint(v, &pass) {
2585 let trace = push_trace(&next.trace, BalanceOp::Mint(v, sub));
2586 record_err(results, &trace, format!("{:?}", e), traps);
2587 continue;
2588 }
2589
2590 next.trace.push(BalanceOp::Mint(pass, sub));
2591 queue.push_back(next);
2592 }
2593
2594 Err(e) => {
2595 let trace = push_trace(&next.trace, BalanceOp::Mint(v, sub));
2596 record_err(results, &trace, format!("{:?}", e), traps);
2597 }
2598 }
2599 }
2600 }
2601
2602 // ---------- REAP ----------
2603 for _ in 0..adjustments.len() {
2604 if let Some(&v) = adjustments.choose(&mut rng) {
2605 let sub = subjects.choose(&mut rng).cloned().unwrap_or_default();
2606 if !guard_ok(&state, &BalanceOp::Reap(v, sub.clone())) {
2607 continue;
2608 }
2609
2610 if !flow_ok(&state, &BalanceOp::Reap(v, sub.clone())) {
2611 continue;
2612 }
2613
2614 let mut next = state.clone();
2615
2616 match reap::<T>(&mut next.lazy, v, &sub) {
2617 Ok(pass) => {
2618 if let Err(e) = next.manual.reap(v, &pass) {
2619 let trace = push_trace(&next.trace, BalanceOp::Reap(v, sub));
2620 record_err(results, &trace, format!("{:?}", e), traps);
2621 continue;
2622 }
2623
2624 next.trace.push(BalanceOp::Reap(pass, sub));
2625 queue.push_back(next);
2626 }
2627
2628 Err(e) => {
2629 let trace = push_trace(&next.trace, BalanceOp::Reap(v, sub));
2630 record_err(results, &trace, format!("{:?}", e), traps);
2631 }
2632 }
2633 }
2634 }
2635
2636 // ---------- DRAIN ----------
2637 if flow_ok(&state, &BalanceOp::Drain) && guard_ok(&state, &BalanceOp::Drain) {
2638 let mut next = state.clone();
2639
2640 match drain::<T>(&mut next.lazy) {
2641 Ok(_) => {
2642 if let Err(e) = next.manual.drain() {
2643 let trace = push_trace(&next.trace, BalanceOp::Drain);
2644 record_err(results, &trace, format!("{:?}", e), traps);
2645 continue;
2646 }
2647
2648 next.trace.push(BalanceOp::Drain);
2649 queue.push_back(next);
2650 }
2651
2652 Err(e) => {
2653 let trace = push_trace(&next.trace, BalanceOp::Drain);
2654 record_err(results, &trace, format!("{:?}", e), traps);
2655 }
2656 }
2657 }
2658 }
2659 }
2660
2661 /// Writes model exploration results to CSV files.
2662 ///
2663 /// Creates a directory (if not present) and writes categorized outputs:
2664 ///
2665 /// - **fail** (always written) sequences that resulted in errors
2666 /// - **pass** (optional) successful sequences
2667 /// - **trap** (optional) sequences matching expected trap failures
2668 /// - **exit** (optional) sequences with drift between models
2669 ///
2670 /// File names are timestamped to avoid overwrites.
2671 ///
2672 /// Each CSV contains:
2673 /// - a unique ID (`sl_no`)
2674 /// - relevant fields (reason, values, sequence)
2675 ///
2676 /// If all result categories are empty, no files are written.
2677 ///
2678 /// Also prints a summary of counts:
2679 /// - trap mode includes trapped count
2680 /// - normal mode includes fail / pass / exit counts
2681 fn balance_states_csv_reports<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
2682 dir: PathBuf,
2683 results: &BalanceModelResults<T, M>,
2684 write_pass: bool,
2685 write_exit: bool,
2686 write_trap: bool,
2687 ) {
2688 if results.fail.is_empty()
2689 && results.pass.is_empty()
2690 && results.trap.is_empty()
2691 && results.exit.is_empty()
2692 {
2693 return;
2694 }
2695
2696 let fuzz_dir = dir;
2697
2698 create_dir_all(&fuzz_dir).unwrap();
2699
2700 let now = SystemTime::now()
2701 .duration_since(UNIX_EPOCH)
2702 .unwrap()
2703 .as_secs();
2704
2705 // ---------- FAIL CSV (MANDATORY) ----------
2706 if !results.fail.is_empty() {
2707 let fail_path = fuzz_dir.join(format!("fail_{}.csv", now));
2708 let mut fail_file = File::create(&fail_path).unwrap();
2709
2710 writeln!(fail_file, "sl_no,reason,step,sequence").unwrap();
2711
2712 for (id, f) in &results.fail {
2713 writeln!(fail_file, "{},\"{}\",\"{:?}\"", id, f.reason, f.sequence).unwrap();
2714 }
2715
2716 // println!("Fail CSV: {}", fail_path.display());
2717 }
2718
2719 // ---------- PASS CSV (OPTIONAL) ----------
2720 if write_pass && !results.pass.is_empty() {
2721 let pass_path = fuzz_dir.join(format!("pass_{}.csv", now));
2722 let mut pass_file = File::create(&pass_path).unwrap();
2723
2724 writeln!(pass_file, "sl_no,sequence").unwrap();
2725
2726 for (id, seq) in &results.pass {
2727 writeln!(pass_file, "{},\"{:?}\"", id, seq).unwrap();
2728 }
2729
2730 // println!("Pass CSV: {}", pass_path.display());
2731 }
2732
2733 // ---------- TRAP CSV (OPTIONAL) ----------
2734 if write_trap && !results.trap.is_empty() {
2735 let trap_path = fuzz_dir.join(format!("trap_{}.csv", now));
2736 let mut trap_file = File::create(&trap_path).unwrap();
2737
2738 writeln!(trap_file, "sl_no,reason,sequence").unwrap();
2739
2740 for (id, reason, seq) in &results.trap {
2741 writeln!(trap_file, "{},\"{}\",\"{:?}\"", id, reason, seq).unwrap();
2742 }
2743
2744 // println!("Trap CSV: {}", trap_path.display());
2745 }
2746
2747 // ---------- EXIT CSV (OPTIONAL) ----------
2748 if write_exit && !results.exit.is_empty() {
2749 let exit_path = fuzz_dir.join(format!("exit_{}.csv", now));
2750 let mut exit_file = File::create(&exit_path).unwrap();
2751
2752 writeln!(exit_file, "sl_no,lazy,manual,diff,bps,sequence").unwrap();
2753
2754 for (id, exit) in &results.exit {
2755 writeln!(
2756 exit_file,
2757 "{},{:?},{:?},{:?},{:?},\"{:?}\"",
2758 id, exit.lazy, exit.manual, exit.diff, exit.bps, exit.sequence
2759 )
2760 .unwrap();
2761 }
2762
2763 // println!("BalanceExit CSV: {}", exit_path.display());
2764 }
2765
2766 // ---------- SUMMARY ----------
2767 if !results.trap.is_empty() {
2768 println!(
2769 "Trap check completed: {} failed, {} passed, {} exited, {} trapped",
2770 results.fail.len(),
2771 results.pass.len(),
2772 results.exit.len(),
2773 results.trap.len(),
2774 );
2775 } else {
2776 println!(
2777 "Model check completed: {} failed, {} passed, {} exited",
2778 results.fail.len(),
2779 results.pass.len(),
2780 results.exit.len(),
2781 );
2782 }
2783 }
2784
2785 // ===============================================================================
2786 // ```````````````````````` MODEL CHECKER INTERNAL HELPERS ```````````````````````
2787 // ===============================================================================
2788
2789 /// Compares lazy and manual withdrawal results and evaluates drift.
2790 ///
2791 /// Computes the absolute difference (`diff`) between lazy and manual
2792 /// values and checks it against allowed tolerance thresholds:
2793 ///
2794 /// - Ignores negligible differences (diff <= 1) (rounding differences)
2795 /// - Computes basis points (bps) relative to the larger value
2796 /// - Accepts results within `allowed_bps` or `allowed_diff` thresholds
2797 ///
2798 /// If the deviation exceeds both thresholds, returns a [`BalanceExit`]
2799 /// containing:
2800 /// - lazy and manual values
2801 /// - absolute difference
2802 /// - basis points difference
2803 /// - execution trace (sequence)
2804 ///
2805 /// This is used to validate that the lazy model maintains acceptable
2806 /// precision relative to the manual reference model.
2807 fn compare_balances_values<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
2808 lazy: T::Asset,
2809 manual: T::Asset,
2810 seq: &[BalanceOp<T, M>],
2811 allowed_bps: u32,
2812 allowed_diff: u32,
2813 ) -> Result<(), BalanceExit<T, M>> {
2814 let lazy = lazy.into();
2815 let diff = if lazy > manual {
2816 lazy - manual
2817 } else {
2818 manual - lazy
2819 };
2820
2821 if diff <= One::one() {
2822 return Ok(());
2823 }
2824
2825 let base = lazy.max(manual).max(1u8.into());
2826
2827 if diff * 10_000u32.into() > base * allowed_bps.into() {
2828 if diff <= allowed_diff.into() {
2829 return Ok(());
2830 }
2831 return Err(BalanceExit::<T, M> {
2832 lazy,
2833 manual,
2834 diff,
2835 bps: diff * 10_000u32.into() / base,
2836 sequence: seq.to_vec(),
2837 });
2838 }
2839
2840 Ok(())
2841 }
2842
2843 /// Records a failed execution for the given trace.
2844 ///
2845 /// Wraps the reason and trace into a [`BalanceFailure`] and stores it.
2846 /// If traps are active, records it as a trapped failure.
2847 fn record_err<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
2848 results: &mut BalanceModelResults<T, M>,
2849 trace: &[BalanceOp<T, M>],
2850 reason: String,
2851 traps: Option<&String>,
2852 ) {
2853 let failure = BalanceFailure {
2854 reason,
2855 sequence: trace.to_vec(),
2856 };
2857
2858 match traps {
2859 Some(e) => results.record_trapped(trace, Err(failure), Some(e)),
2860 None => results.record(trace, Err(failure)),
2861 }
2862 }
2863
2864 /// Records a successful execution for the given trace.
2865 ///
2866 /// Marks the sequence as passed. Behavior is identical regardless of traps.
2867 fn record_pass<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
2868 results: &mut BalanceModelResults<T, M>,
2869 trace: &[BalanceOp<T, M>],
2870 traps: Option<&String>,
2871 ) {
2872 match traps {
2873 Some(_) => results.record(trace, Ok(())),
2874 None => results.record(trace, Ok(())),
2875 }
2876 }
2877
2878 /// Records an exit (drift) between lazy and manual models.
2879 ///
2880 /// Stores the [`BalanceExit`] for later analysis.
2881 fn record_exit<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
2882 results: &mut BalanceModelResults<T, M>,
2883 exit: BalanceExit<T, M>,
2884 ) {
2885 results.exit_record(exit)
2886 }
2887
2888 // ===============================================================================
2889 // ```````````````````````````` MODEL CHECKER RESULTS ````````````````````````````
2890 // ===============================================================================
2891
2892 /// Aggregates results produced during state exploration.
2893 ///
2894 /// Stores categorized outcomes for executed sequences:
2895 /// - `pass`: successful sequences
2896 /// - `fail`: sequences that resulted in an error ([`BalanceFailure`])
2897 /// - `exit`: sequences with drift between models ([`BalanceExit`])
2898 /// - `trap`: sequences that triggered trap conditions
2899 ///
2900 /// Each entry is paired with a unique identifier for tracking.
2901 ///
2902 /// The `next_*_id` fields maintain incremental IDs for each category.
2903 #[derive(Debug, Clone, Eq, PartialEq)]
2904 pub struct BalanceModelResults<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
2905 /// Successful sequences.
2906 pub pass: Vec<(usize, Vec<BalanceOp<T, M>>)>,
2907
2908 /// Failed sequences with associated error.
2909 pub fail: Vec<(usize, BalanceFailure<T, M>)>,
2910
2911 /// Drifted sequences between lazy and manual models.
2912 pub exit: Vec<(usize, BalanceExit<T, M>)>,
2913
2914 /// Trap-triggered sequences with reason.
2915 pub trap: Vec<(usize, String, Vec<BalanceOp<T, M>>)>,
2916
2917 /// Next identifier for pass entries.
2918 pub next_pass_id: usize,
2919
2920 /// Next identifier for exit entries.
2921 pub next_exit_id: usize,
2922
2923 /// Next identifier for fail entries.
2924 pub next_fail_id: usize,
2925
2926 /// Next identifier for trap entries.
2927 pub next_trap_id: usize,
2928 }
2929
2930 impl<T: LazyBalanceMarker, M: ManualBalanceModel<T>> BalanceModelResults<T, M> {
2931 /// Creates a new empty result container with initialized IDs.
2932 fn new() -> Self {
2933 Self {
2934 pass: Vec::new(),
2935 fail: Vec::new(),
2936 exit: Vec::new(),
2937 trap: Vec::new(),
2938 next_pass_id: 1,
2939 next_exit_id: 1,
2940 next_fail_id: 1,
2941 next_trap_id: 1,
2942 }
2943 }
2944
2945 /// Records an exit (drift) with a unique ID.
2946 fn exit_record(&mut self, exit: BalanceExit<T, M>) {
2947 let id = self.next_exit_id;
2948 self.next_exit_id += 1;
2949 self.exit.push((id, exit));
2950 }
2951
2952 /// Records a result without trap context.
2953 /// Delegates to [`Self::record_trapped`] with no trap expectation.
2954 fn record(&mut self, seq: &[BalanceOp<T, M>], result: Result<(), BalanceFailure<T, M>>) {
2955 self.record_trapped(seq, result, None);
2956 }
2957
2958 /// Records a result with optional trap expectation.
2959 /// - `Ok`: stored as pass
2960 /// - `Err`:
2961 /// - if matches expected trap reason, stored as trap
2962 /// - otherwise, stored as failure
2963 fn record_trapped(
2964 &mut self,
2965 seq: &[BalanceOp<T, M>],
2966 result: Result<(), BalanceFailure<T, M>>,
2967 traps: Option<&String>,
2968 ) {
2969 match result {
2970 Ok(_) => {
2971 let id = self.next_pass_id;
2972 self.next_pass_id += 1;
2973
2974 self.pass.push((id, seq.to_vec()));
2975 }
2976
2977 Err(f) => {
2978 if let Some(trap) = traps {
2979 if *trap == f.reason {
2980 // Expected trap
2981 let id = self.next_trap_id;
2982 self.next_trap_id += 1;
2983
2984 self.trap.push((id, f.reason.clone(), seq.to_vec()));
2985
2986 return;
2987 }
2988 }
2989
2990 let id = self.next_fail_id;
2991 self.next_fail_id += 1;
2992
2993 self.fail.push((id, f.clone()));
2994 }
2995 }
2996 }
2997 }
2998
2999 // ===============================================================================
3000 // `````````````````````` LAZY BALANCE CONVENIENCE CALLERS ```````````````````````
3001 // ===============================================================================
3002
3003 /// Executes a deposit on the lazy balance model using the provided container.
3004 ///
3005 /// Constructs the required tagged input, invokes [`LazyBalance::deposit`], and
3006 /// extracts the resolved (asset, receipt) output.
3007 ///
3008 /// Returns the actual deposited value and issued receipt.
3009 fn deposit<'a, T: LazyBalanceMarker>(
3010 model: &'a mut LazyContainer<T>,
3011 value: T::Asset,
3012 subject: &'a T::Subject,
3013 ) -> Result<(T::Asset, T::Receipt), Error<T>> {
3014 let input = <T::Input<'_> as FromTag<_, Deposit>>::from_tag((
3015 MutHandle::Borrowed(&mut model.balance),
3016 Cow::Borrowed(&model.variant),
3017 Cow::Borrowed(&model.id),
3018 Cow::Owned(value),
3019 Cow::Borrowed(subject),
3020 ));
3021
3022 let raw = T::deposit(input);
3023
3024 let Ok(result) = TryIntoTag::<_, Deposit>::try_into_tag(raw) else {
3025 unreachable!()
3026 };
3027
3028 match result {
3029 Ok((asset, receipt)) => Ok((asset.into_owned(), receipt.into_owned())),
3030 Err(e) => Err(e),
3031 }
3032 }
3033
3034 /// Executes a withdrawal on the lazy balance model.
3035 ///
3036 /// Consumes the provided receipt, invokes [`LazyBalance::withdraw`], and
3037 /// returns the resolved asset value.
3038 fn withdraw<'a, T: LazyBalanceMarker>(
3039 model: &'a mut LazyContainer<T>,
3040 receipt: T::Receipt,
3041 ) -> Result<T::Asset, Error<T>> {
3042 let input = <T::Input<'_> as FromTag<_, Withdraw>>::from_tag((
3043 MutHandle::Borrowed(&mut model.balance),
3044 Cow::Borrowed(&model.variant),
3045 Cow::Borrowed(&model.id),
3046 Cow::Owned(receipt),
3047 ));
3048
3049 let raw = T::withdraw(input);
3050
3051 let Ok(result) = TryIntoTag::<_, Withdraw>::try_into_tag(raw) else {
3052 unreachable!()
3053 };
3054
3055 match result {
3056 Ok(v) => Ok(v.into_owned()),
3057 Err(e) => Err(e),
3058 }
3059 }
3060
3061 /// Executes a mint operation on the lazy balance model.
3062 ///
3063 /// Constructs the tagged input, invokes [`LazyBalance::mint`], and returns
3064 /// the resolved asset value added to the balance.
3065 fn mint<'a, T: LazyBalanceMarker>(
3066 model: &'a mut LazyContainer<T>,
3067 value: T::Asset,
3068 subject: &'a T::Subject,
3069 ) -> Result<T::Asset, Error<T>> {
3070 let input = <T::Input<'_> as FromTag<_, Mint>>::from_tag((
3071 MutHandle::Borrowed(&mut model.balance),
3072 Cow::Borrowed(&model.variant),
3073 Cow::Borrowed(&model.id),
3074 Cow::Owned(value),
3075 Cow::Borrowed(subject),
3076 ));
3077
3078 let raw = T::mint(input);
3079
3080 let Ok(result) = TryIntoTag::<_, Mint>::try_into_tag(raw) else {
3081 unreachable!()
3082 };
3083
3084 match result {
3085 Ok(v) => Ok(v.into_owned()),
3086 Err(e) => Err(e),
3087 }
3088 }
3089
3090 /// Executes a reap operation on the lazy balance model.
3091 ///
3092 /// Constructs the tagged input, invokes [`LazyBalance::reap`], and returns
3093 /// the resolved asset value removed or adjusted from the balance.
3094 fn reap<'a, T: LazyBalanceMarker>(
3095 model: &'a mut LazyContainer<T>,
3096 value: T::Asset,
3097 subject: &'a T::Subject,
3098 ) -> Result<T::Asset, Error<T>> {
3099 let input = <T::Input<'_> as FromTag<_, Reap>>::from_tag((
3100 MutHandle::Borrowed(&mut model.balance),
3101 Cow::Borrowed(&model.variant),
3102 Cow::Borrowed(&model.id),
3103 Cow::Owned(value),
3104 Cow::Borrowed(subject),
3105 ));
3106
3107 let raw = T::reap(input);
3108
3109 let Ok(result) = TryIntoTag::<_, Reap>::try_into_tag(raw) else {
3110 unreachable!()
3111 };
3112
3113 match result {
3114 Ok(v) => Ok(v.into_owned()),
3115 Err(e) => Err(e),
3116 }
3117 }
3118
3119 /// Executes a drain operation on the lazy balance model.
3120 ///
3121 /// Constructs the tagged input, invokes [`LazyBalance::drain`].
3122 ///
3123 /// Clears the balance state and returns the resolved drained value.
3124 fn drain<'a, T: LazyBalanceMarker>(
3125 model: &'a mut LazyContainer<T>,
3126 ) -> Result<T::Asset, Error<T>> {
3127 let input = <T::Input<'_> as FromTag<_, Drain>>::from_tag((
3128 MutHandle::Borrowed(&mut model.balance),
3129 Cow::Borrowed(&model.variant),
3130 Cow::Borrowed(&model.id),
3131 ));
3132
3133 let raw = <T as LazyBalance>::drain(input);
3134
3135 let Ok(result) = TryIntoTag::<_, Drain>::try_into_tag(raw) else {
3136 unreachable!()
3137 };
3138
3139 match result {
3140 Ok(v) => Ok(v.into_owned()),
3141 Err(e) => Err(e),
3142 }
3143 }
3144}