pallet_commitment/
balance.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// ```````````````````````````````` LAZY BALANCE `````````````````````````````````
14// ===============================================================================
15
16//! [`Pallet`] implementation of [`LazyBalance`] using [`virtual`](frame_suite::virtuals)
17//! structs and [`plugins`](frame_suite::plugins).
18//!
19//! Provides a generic, plugin-driven balance system where behavior is defined
20//! by [`LazyBalance::BalanceFamily`] thin-delegated to [`Config::BalanceFamily`],
21//! rather than being hardcoded.
22//!
23//! State is encoded using [`SumDynType`] and accessed through
24//! [`VirtualDynField`], enabling flexible virtual schemas.
25//!
26//! Supports:
27//! - lazy evaluation of balances
28//! - snapshot-based time tracking via [`VirtualNMap`]
29//! - typed dispatch through tagged input/output enums
30
31// ===============================================================================
32// ``````````````````````````````````` IMPORTS ```````````````````````````````````
33// ===============================================================================
34
35// --- Local crate imports ---
36use crate::{types::*, BalanceSnapShots, Config, Pallet};
37
38// --- Scale-codec crates ---
39use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
40use scale_info::TypeInfo;
41
42// --- FRAME Suite ---
43use frame_suite::{
44    assets::*,
45    base::Delimited,
46    misc::{Directive, Extent},
47    mutation::MutHandle,
48    virtuals::*,
49};
50
51// --- FRAME Support ---
52use frame_support::{
53    pallet_prelude::NMapKey,
54    traits::tokens::{Fortitude, Precision},
55    Blake2_128Concat,
56};
57
58// --- Substrate primitives ---
59use sp_core::{ConstU32, Get};
60use sp_runtime::{Cow, DispatchError, Vec};
61
62// ===============================================================================
63// `````````````````````` LAZY BALANCE OPERATIONS UTILITIES ``````````````````````
64// ===============================================================================
65
66/// Deposits value into a [`VirtualBalance`] via [`LazyBalance::deposit`] execution.
67///
68/// Wraps [`plugin`](frame_suite::plugins) dispatch using [`LazyInput::Deposit`]
69/// and returns (`effective_asset`, [`VirtualReceipt`]) on success.
70pub fn deposit<'a, T: Config<I>, I: 'static>(
71    balance: &'a mut VirtualBalance<T, I>,
72    variant: &'a T::Position,
73    id: &'a Digest<T>,
74    value: &'a AssetOf<T, I>,
75    qualify: &'a DispatchPolicy,
76) -> Result<(AssetOf<T, I>, VirtualReceipt<T, I>), DispatchError> {
77    let input = <LazyInput<'a, T, I> as FromTag<_, Deposit>>::from_tag((
78        MutHandle::Borrowed(balance),
79        Cow::Borrowed(variant),
80        Cow::Borrowed(id),
81        Cow::Borrowed(value),
82        Cow::Borrowed(qualify),
83    ));
84
85    let raw = Pallet::<T, I>::deposit(input);
86
87    let Ok(result) = TryIntoTag::<_, Deposit>::try_into_tag(raw) else {
88        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
89    };
90
91    match result {
92        Ok((asset, receipt)) => Ok((asset.into_owned(), receipt.into_owned())),
93        Err(e) => Err(e.into()),
94    }
95}
96
97/// Withdraws value from a [`VirtualBalance`] using a [`VirtualReceipt`] via
98/// [`LazyBalance::withdraw`] execution.
99///
100/// Wraps plugin dispatch using [`LazyInput::Withdraw`]
101/// and returns the actual withdrawn asset value.
102pub fn withdraw<'a, T: Config<I>, I: 'static>(
103    balance: &'a mut VirtualBalance<T, I>,
104    variant: &'a T::Position,
105    id: &'a Digest<T>,
106    receipt: &'a VirtualReceipt<T, I>,
107) -> Result<AssetOf<T, I>, DispatchError> {
108    let input = <LazyInput<'a, T, I> as FromTag<_, Withdraw>>::from_tag((
109        MutHandle::Borrowed(balance),
110        Cow::Borrowed(variant),
111        Cow::Borrowed(id),
112        Cow::Borrowed(receipt),
113    ));
114
115    let raw = Pallet::<T, I>::withdraw(input);
116
117    let Ok(result) = TryIntoTag::<_, Withdraw>::try_into_tag(raw) else {
118        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
119    };
120
121    match result {
122        Ok(v) => Ok(*v),
123        Err(e) => Err(e.into()),
124    }
125}
126
127/// Mints value into a [`VirtualBalance`] (e.g. rewards/inflation) via
128/// [`LazyBalance::mint`] execution.
129///
130/// Wraps plugin dispatch using [`LazyInput::Mint`]
131/// and returns the actual minted asset value.
132pub fn mint<'a, T: Config<I>, I: 'static>(
133    balance: &'a mut VirtualBalance<T, I>,
134    variant: &'a T::Position,
135    id: &'a Digest<T>,
136    value: &'a AssetOf<T, I>,
137    qualify: &'a DispatchPolicy,
138) -> Result<AssetOf<T, I>, DispatchError> {
139    let input = <LazyInput<'a, T, I> as FromTag<_, Mint>>::from_tag((
140        MutHandle::Borrowed(balance),
141        Cow::Borrowed(variant),
142        Cow::Borrowed(id),
143        Cow::Borrowed(value),
144        Cow::Borrowed(qualify),
145    ));
146
147    let raw = Pallet::<T, I>::mint(input);
148
149    let Ok(result) = TryIntoTag::<_, Mint>::try_into_tag(raw) else {
150        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
151    };
152
153    match result {
154        Ok(v) => Ok(v.into_owned()),
155        Err(e) => Err(e.into()),
156    }
157}
158
159/// Reaps (removes) value from a [`VirtualBalance`] (e.g. penalties/deflation)
160/// via [`LazyBalance::reap`] execution.
161///
162/// Wraps plugin dispatch using [`LazyInput::Reap`]
163/// and returns the actual reaped asset value.
164pub fn reap<'a, T: Config<I>, I: 'static>(
165    balance: &'a mut VirtualBalance<T, I>,
166    variant: &'a T::Position,
167    id: &'a Digest<T>,
168    value: &'a AssetOf<T, I>,
169    qualify: &'a DispatchPolicy,
170) -> Result<AssetOf<T, I>, DispatchError> {
171    let input = <LazyInput<'a, T, I> as FromTag<_, Reap>>::from_tag((
172        MutHandle::Borrowed(balance),
173        Cow::Borrowed(variant),
174        Cow::Borrowed(id),
175        Cow::Borrowed(value),
176        Cow::Borrowed(qualify),
177    ));
178
179    let raw = Pallet::<T, I>::reap(input);
180
181    let Ok(result) = TryIntoTag::<_, Reap>::try_into_tag(raw) else {
182        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
183    };
184
185    match result {
186        Ok(v) => Ok(v.into_owned()),
187        Err(e) => Err(e.into()),
188    }
189}
190
191/// Drains all value associated with a `(variant, digest)` pair via
192/// [`LazyBalance::drain`] execution.
193///
194/// Wraps plugin dispatch using [`LazyInput::Drain`]
195/// and performs full state cleanup.
196#[allow(unused)]
197pub fn drain<'a, T: Config<I>, I: 'static>(
198    balance: &'a mut VirtualBalance<T, I>,
199    variant: &'a T::Position,
200    id: &'a Digest<T>,
201) -> Result<(), DispatchError> {
202    let input = <LazyInput<'a, T, I> as FromTag<_, Drain>>::from_tag((
203        MutHandle::Borrowed(balance),
204        Cow::Borrowed(variant),
205        Cow::Borrowed(id),
206    ));
207
208    let raw = <Pallet<T, I> as LazyBalance>::drain(input);
209
210    let Ok(result) = TryIntoTag::<_, Drain>::try_into_tag(raw) else {
211        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
212    };
213
214    match result {
215        Ok(_) => Ok(()),
216        Err(e) => Err(e.into()),
217    }
218}
219
220/// Checks if a deposit is allowed under current constraints via
221/// [`LazyBalance::can_deposit`] execution.
222///
223/// Wraps plugin dispatch using [`LazyInput::CanDeposit`]
224/// and returns `Ok(())` if permitted.
225pub fn can_deposit<'a, T: Config<I>, I: 'static>(
226    balance: &'a VirtualBalance<T, I>,
227    variant: &'a T::Position,
228    id: &'a Digest<T>,
229    value: &'a AssetOf<T, I>,
230    qualify: &'a DispatchPolicy,
231) -> Result<(), DispatchError> {
232    let input = <LazyInput<'a, T, I> as FromTag<_, CanDeposit>>::from_tag((
233        Cow::Borrowed(balance),
234        Cow::Borrowed(variant),
235        Cow::Borrowed(id),
236        Cow::Borrowed(value),
237        Cow::Borrowed(qualify),
238    ));
239
240    let raw = Pallet::<T, I>::can_deposit(input);
241
242    let Ok(result) = TryIntoTag::<_, CanDeposit>::try_into_tag(raw) else {
243        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
244    };
245
246    match result {
247        Ok(_) => Ok(()),
248        Err(e) => Err(e.into()),
249    }
250}
251
252/// Checks if a withdrawal is allowed for a given [`VirtualReceipt`] via
253/// [`LazyBalance::can_withdraw`] execution.
254///
255/// Wraps plugin dispatch using [`LazyInput::CanWithdraw`]
256/// and returns `Ok(())` if permitted.
257pub fn can_withdraw<'a, T: Config<I>, I: 'static>(
258    balance: &'a VirtualBalance<T, I>,
259    variant: &'a T::Position,
260    id: &'a Digest<T>,
261    receipt: &'a VirtualReceipt<T, I>,
262) -> Result<(), DispatchError> {
263    let input = <LazyInput<'a, T, I> as FromTag<_, CanWithdraw>>::from_tag((
264        Cow::Borrowed(balance),
265        Cow::Borrowed(variant),
266        Cow::Borrowed(id),
267        Cow::Borrowed(receipt),
268    ));
269
270    let raw = Pallet::<T, I>::can_withdraw(input);
271
272    let Ok(result) = TryIntoTag::<_, CanWithdraw>::try_into_tag(raw) else {
273        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
274    };
275
276    match result {
277        Ok(_) => Ok(()),
278        Err(e) => Err(e.into()),
279    }
280}
281
282/// Checks if minting is allowed under current constraints via
283/// [`LazyBalance::can_withdraw`] execution.
284///
285/// Wraps plugin dispatch using [`LazyInput::CanMint`]
286/// and returns `Ok(())` if permitted.
287pub fn can_mint<'a, T: Config<I>, I: 'static>(
288    balance: &'a VirtualBalance<T, I>,
289    variant: &'a T::Position,
290    id: &'a Digest<T>,
291    value: &'a AssetOf<T, I>,
292    qualify: &'a DispatchPolicy,
293) -> Result<(), DispatchError> {
294    let input = <LazyInput<'a, T, I> as FromTag<_, CanMint>>::from_tag((
295        Cow::Borrowed(balance),
296        Cow::Borrowed(variant),
297        Cow::Borrowed(id),
298        Cow::Borrowed(value),
299        Cow::Borrowed(qualify),
300    ));
301
302    let raw = Pallet::<T, I>::can_mint(input);
303
304    let Ok(result) = TryIntoTag::<_, CanMint>::try_into_tag(raw) else {
305        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
306    };
307
308    match result {
309        Ok(_) => Ok(()),
310        Err(e) => Err(e.into()),
311    }
312}
313
314/// Checks if reaping is allowed under current constraints via
315/// [`LazyBalance::can_reap`] execution.
316///
317/// Wraps plugin dispatch using [`LazyInput::CanReap`]
318/// and returns `Ok(())` if permitted.
319pub fn can_reap<'a, T: Config<I>, I: 'static>(
320    balance: &'a VirtualBalance<T, I>,
321    variant: &'a T::Position,
322    id: &'a Digest<T>,
323    value: &'a AssetOf<T, I>,
324    qualify: &'a DispatchPolicy,
325) -> Result<(), DispatchError> {
326    let input = <LazyInput<'a, T, I> as FromTag<_, CanReap>>::from_tag((
327        Cow::Borrowed(balance),
328        Cow::Borrowed(variant),
329        Cow::Borrowed(id),
330        Cow::Borrowed(value),
331        Cow::Borrowed(qualify),
332    ));
333
334    let raw = Pallet::<T, I>::can_reap(input);
335
336    let Ok(result) = TryIntoTag::<_, CanReap>::try_into_tag(raw) else {
337        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
338    };
339
340    match result {
341        Ok(_) => Ok(()),
342        Err(e) => Err(e.into()),
343    }
344}
345
346/// Returns total value of a [`VirtualBalance`] for a `(variant, digest)` pair via
347/// [`LazyBalance::total_value`] execution.
348///
349/// Wraps plugin dispatch using [`LazyInput::TotalValue`].
350pub fn balance_total<'a, T: Config<I>, I: 'static>(
351    balance: &'a VirtualBalance<T, I>,
352    variant: &'a T::Position,
353    id: &'a Digest<T>,
354) -> Result<AssetOf<T, I>, DispatchError> {
355    let input = <LazyInput<'a, T, I> as FromTag<_, TotalValue>>::from_tag((
356        Cow::Borrowed(balance),
357        Cow::Borrowed(variant),
358        Cow::Borrowed(id),
359    ));
360
361    let raw = Pallet::<T, I>::total_value(input);
362
363    let Ok(result) = TryIntoTag::<_, TotalValue>::try_into_tag(raw) else {
364        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
365    };
366
367    match result {
368        Ok(v) => Ok(*v),
369        Err(e) => Err(e.into()),
370    }
371}
372
373/// Returns the current (lazy-evaluated) value of a [`VirtualReceipt`] via
374/// [`LazyBalance::receipt_active_value`] execution.
375///
376/// Wraps plugin dispatch using [`LazyInput::ReceiptActiveValue`].
377pub fn receipt_active_value<'a, T: Config<I>, I: 'static>(
378    balance: &'a VirtualBalance<T, I>,
379    variant: &'a T::Position,
380    id: &'a Digest<T>,
381    receipt: &'a VirtualReceipt<T, I>,
382) -> Result<AssetOf<T, I>, DispatchError> {
383    let input = <LazyInput<'a, T, I> as FromTag<_, ReceiptActiveValue>>::from_tag((
384        Cow::Borrowed(balance),
385        Cow::Borrowed(variant),
386        Cow::Borrowed(id),
387        Cow::Borrowed(receipt),
388    ));
389
390    let raw = Pallet::<T, I>::receipt_active_value(input);
391
392    let Ok(result) = TryIntoTag::<_, ReceiptActiveValue>::try_into_tag(raw) else {
393        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
394    };
395
396    match result {
397        Ok(v) => Ok(*v),
398        Err(e) => Err(e.into()),
399    }
400}
401
402/// Returns the original deposited value of a [`VirtualReceipt`] via
403/// [`LazyBalance::receipt_deposit_value`] execution.
404///
405/// Wraps plugin dispatch using [`LazyInput::ReceiptDepositValue`].
406pub fn receipt_deposit_value<'a, T: Config<I>, I: 'static>(
407    receipt: &'a VirtualReceipt<T, I>,
408) -> Result<AssetOf<T, I>, DispatchError> {
409    let input =
410        <LazyInput<'a, T, I> as FromTag<_, ReceiptDepositValue>>::from_tag(Cow::Borrowed(receipt));
411
412    let raw = Pallet::<T, I>::receipt_deposit_value(input);
413
414    let Ok(result) = TryIntoTag::<_, ReceiptDepositValue>::try_into_tag(raw) else {
415        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
416    };
417
418    match result {
419        Ok(v) => Ok(*v),
420        Err(e) => Err(e.into()),
421    }
422}
423
424/// Checks whether any deposits exist for a `(variant, digest)` pair via
425/// [`LazyBalance::has_deposits`] execution.
426///
427/// Wraps plugin dispatch using [`LazyInput::HasDeposits`].
428pub fn has_deposits<'a, T: Config<I>, I: 'static>(
429    balance: &'a VirtualBalance<T, I>,
430    variant: &'a T::Position,
431    id: &'a Digest<T>,
432) -> Result<(), DispatchError> {
433    let input = <LazyInput<'a, T, I> as FromTag<_, HasDeposits>>::from_tag((
434        Cow::Borrowed(balance),
435        Cow::Borrowed(variant),
436        Cow::Borrowed(id),
437    ));
438
439    let raw = Pallet::<T, I>::has_deposits(input);
440
441    let Ok(result) = TryIntoTag::<_, HasDeposits>::try_into_tag(raw) else {
442        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
443    };
444
445    match result {
446        Ok(_) => Ok(()),
447        Err(e) => Err(e.into()),
448    }
449}
450
451/// Returns deposit limits implementing [`Extent`] for a [`VirtualBalance`]
452/// context via [`LazyBalance::deposit_limits`] execution.
453///
454/// Wraps plugin dispatch using [`LazyInput::DepositLimits`]
455/// and returns [`LimitsProduct`].
456pub fn deposit_limits_of<'a, T: Config<I>, I: 'static>(
457    balance: &'a VirtualBalance<T, I>,
458    variant: &'a T::Position,
459    id: &'a Digest<T>,
460    qualify: &'a DispatchPolicy,
461) -> Result<LimitsProduct<T, I>, DispatchError> {
462    let input = <LazyInput<'a, T, I> as FromTag<_, DepositLimits>>::from_tag((
463        Cow::Borrowed(balance),
464        Cow::Borrowed(variant),
465        Cow::Borrowed(id),
466        Cow::Borrowed(qualify),
467    ));
468
469    let raw = Pallet::<T, I>::deposit_limits(input);
470
471    let Ok(result) = TryIntoTag::<_, DepositLimits>::try_into_tag(raw) else {
472        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
473    };
474
475    match result {
476        Ok(v) => Ok(v.into_owned()),
477        Err(e) => Err(e.into()),
478    }
479}
480
481/// Returns mint limits implementing [`Extent`] for a [`VirtualBalance`]
482/// context via [`LazyBalance::mint_limits`] execution.
483///
484/// Wraps plugin dispatch using [`LazyInput::MintLimits`]
485/// and returns [`LimitsProduct`].
486pub fn mint_limits_of<'a, T: Config<I>, I: 'static>(
487    balance: &'a VirtualBalance<T, I>,
488    variant: &'a T::Position,
489    id: &'a Digest<T>,
490    qualify: &'a DispatchPolicy,
491) -> Result<LimitsProduct<T, I>, DispatchError> {
492    let input = <LazyInput<'a, T, I> as FromTag<_, MintLimits>>::from_tag((
493        Cow::Borrowed(balance),
494        Cow::Borrowed(variant),
495        Cow::Borrowed(id),
496        Cow::Borrowed(qualify),
497    ));
498
499    let raw = Pallet::<T, I>::mint_limits(input);
500
501    let Ok(result) = TryIntoTag::<_, MintLimits>::try_into_tag(raw) else {
502        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
503    };
504
505    match result {
506        Ok(v) => Ok(v.into_owned()),
507        Err(e) => Err(e.into()),
508    }
509}
510
511/// Returns reap limits implementing [`Extent`] for a [`VirtualBalance`]
512/// context via [`LazyBalance::reap_limits`] execution.
513///
514/// Wraps plugin dispatch using [`LazyInput::ReapLimits`]
515/// and returns [`LimitsProduct`].
516pub fn reap_limits_of<'a, T: Config<I>, I: 'static>(
517    balance: &'a VirtualBalance<T, I>,
518    variant: &'a T::Position,
519    id: &'a Digest<T>,
520    qualify: &'a DispatchPolicy,
521) -> Result<LimitsProduct<T, I>, DispatchError> {
522    let input = <LazyInput<'a, T, I> as FromTag<_, ReapLimits>>::from_tag((
523        Cow::Borrowed(balance),
524        Cow::Borrowed(variant),
525        Cow::Borrowed(id),
526        Cow::Borrowed(qualify),
527    ));
528
529    let raw = Pallet::<T, I>::reap_limits(input);
530
531    let Ok(result) = TryIntoTag::<_, ReapLimits>::try_into_tag(raw) else {
532        return Err(crate::Error::<T, I>::CorruptedPlugin.into());
533    };
534
535    match result {
536        Ok(v) => Ok(v.into_owned()),
537        Err(e) => Err(e.into()),
538    }
539}
540
541// ===============================================================================
542// `````````````````````````````` LAZY BALANCE IMPL ``````````````````````````````
543// ===============================================================================
544
545impl<T, I> LazyBalance for Pallet<T, I>
546where
547    T: Config<I>,
548    I: 'static,
549{
550    type Asset = AssetOf<T, I>;
551    type Rational = T::Bias;
552    type Time = T::Time;
553
554    type Balance = VirtualBalance<T, I>;
555    type Variant = T::Position;
556    type Id = Digest<T>;
557    type Limits = LimitsProduct<T, I>;
558    type Subject = DispatchPolicy;
559
560    type SnapShot = VirtualSnapShot<T, I>;
561    type Receipt = VirtualReceipt<T, I>;
562
563    type Input<'a> = LazyInput<'a, T, I>;
564    type Output<'a> = LazyOutput<'a, T, I>;
565
566    type BalanceFamily<'a> = T::BalanceFamily<'a>;
567    type BalanceContext = T::BalanceContext;
568}
569
570// ===============================================================================
571// ````````````````````` BALANCE PLUGIN CONTEXT TRAIT BOUNDS `````````````````````
572// ===============================================================================
573
574/// Schema provider for [`ProductType`] typical via [`BalanceModelContext`].
575///
576/// Combines:
577/// - [`VirtualDynBound`] for core discriminants (`Asset`, `Rational`, `Time`)
578/// - [`VirtualDynExtensionSchema`] for extension layout (`Addon`)
579///
580/// Implementors define the **field bounds and extension schema**
581/// used to interpret a [`virtual`](frame_suite::virtuals) product.
582pub trait ProductProvider<Asset, Rational, Time, Addon>:
583    VirtualDynExtensionSchema<Addon>
584    + VirtualDynBound<Asset>
585    + VirtualDynBound<Rational>
586    + VirtualDynBound<Time>
587where
588    Addon: DiscriminantTag,
589    Rational: DiscriminantTag,
590    Time: DiscriminantTag,
591    Asset: DiscriminantTag,
592{
593}
594
595impl<T, Asset, Rational, Time, Addon> ProductProvider<Asset, Rational, Time, Addon> for T
596where
597    T: VirtualDynExtensionSchema<Addon>
598        + VirtualDynBound<Asset>
599        + VirtualDynBound<Rational>
600        + VirtualDynBound<Time>,
601    Addon: DiscriminantTag,
602    Rational: DiscriminantTag,
603    Time: DiscriminantTag,
604    Asset: DiscriminantTag,
605{
606}
607
608// ===============================================================================
609// ``````````````````````````` VIRTUAL STRUCT PRODUCT ````````````````````````````
610// ===============================================================================
611
612/// A generic **[`virtual`](frame_suite::virtuals) product structure** used
613/// by [`LazyBalance`] components.
614///
615/// Core backing type for:
616/// - [`VirtualBalance`]
617/// - [`VirtualReceipt`]
618/// - [`VirtualSnapShot`]
619///
620/// Each field is stored as a [`SumDynType`] and accessed via
621/// [`VirtualDynField`] or [`VirtualDynExtension`].
622///
623/// Enables a **schema-less, context-driven layout**, where:
624/// - field multiplicity is dynamic (`None | Some | Many`)
625/// - bounds are enforced via [`VirtualDynBound`]
626/// - extensions are defined via [`VirtualDynExtensionSchema`]
627///
628/// This allows reuse across multiple virtual types without redefining structs.
629///
630/// - [`ProductProvider`] implemented by [`BalanceModelContext`] supplies bounds and
631/// extension schema
632/// - generics (`Asset`, `Rational`, `Time`) resolve discriminant fields of the respective
633/// lazy balance virtual structs.
634/// - access is mediated through generic-traits, not direct struct usage
635#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen)]
636#[codec(encode_bound(
637    Provider: ProductProvider<Asset, Rational, Time, Addon>,
638    <Provider as VirtualDynExtensionSchema<Addon>>::Repr: Delimited + Default
639))]
640#[codec(decode_bound(
641    Provider: ProductProvider<Asset, Rational, Time, Addon>,
642    <Provider as VirtualDynExtensionSchema<Addon>>::Repr:  Delimited + Default
643))]
644#[codec(decode_with_mem_tracking_bound(
645    Provider: ProductProvider<Asset, Rational, Time, Addon>,
646    <Provider as VirtualDynExtensionSchema<Addon>>::Repr: Delimited + Default
647))]
648#[codec(mel_bound(
649    <Provider as VirtualDynExtensionSchema<Addon>>::Repr: MaxEncodedLen
650))]
651#[scale_info(skip_type_params(T, I, Provider, Asset, Rational, Time, Addon))]
652pub struct ProductType<T, I, Provider, Asset, Rational, Time, Addon>
653where
654    Provider: ProductProvider<Asset, Rational, Time, Addon>,
655    T: Config<I>,
656    I: 'static,
657    Addon: DiscriminantTag,
658    Rational: DiscriminantTag,
659    Time: DiscriminantTag,
660    Asset: DiscriminantTag,
661{
662    /// Asset-related fields resolved via [`VirtualDynField`].
663    ///
664    /// Uses [`SumDynType`] with capacity bounded by [`VirtualDynBound`] for `Asset`.
665    asset: SumDynType<AssetOf<T, I>, <Provider as VirtualDynBound<Asset>>::Bound>,
666
667    /// Rational fields (e.g. bias / price factors) resolved via [`VirtualDynField`].
668    ///
669    /// Backed by [`SumDynType`] and bounded by [`VirtualDynBound`] for `Rational`.
670    bias: SumDynType<T::Bias, <Provider as VirtualDynBound<Rational>>::Bound>,
671
672    /// Time-related fields (e.g. checkpoints) resolved via [`VirtualDynField`].
673    ///
674    /// Encoded as [`SumDynType`] with bounds provided by [`VirtualDynBound`] for `Time`.
675    time: SumDynType<T::Time, <Provider as VirtualDynBound<Time>>::Bound>,
676
677    /// Extension storage defined by [`VirtualDynExtensionSchema`].
678    ///
679    /// Layout and semantics are fully provided by [`ProductProvider`] via
680    /// the associated extension schema (`Addon`).
681    addon: <Provider as VirtualDynExtensionSchema<Addon>>::Repr,
682}
683
684impl<T, I, Provider, Asset, Rational, Time, Addon> Clone
685    for ProductType<T, I, Provider, Asset, Rational, Time, Addon>
686where
687    Provider: ProductProvider<Asset, Rational, Time, Addon>,
688    T: Config<I>,
689    I: 'static,
690    Addon: DiscriminantTag,
691    Rational: DiscriminantTag,
692    Time: DiscriminantTag,
693    Asset: DiscriminantTag,
694{
695    fn clone(&self) -> Self {
696        Self {
697            asset: self.asset.clone(),
698            bias: self.bias.clone(),
699            time: self.time.clone(),
700            addon: self.addon.clone(),
701        }
702    }
703}
704
705impl<T, I, Provider, Asset, Rational, Time, Addon> core::fmt::Debug
706    for ProductType<T, I, Provider, Asset, Rational, Time, Addon>
707where
708    Provider: ProductProvider<Asset, Rational, Time, Addon>,
709    T: Config<I>,
710    I: 'static,
711    Addon: DiscriminantTag,
712    Rational: DiscriminantTag,
713    Time: DiscriminantTag,
714    Asset: DiscriminantTag,
715{
716    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
717        f.debug_struct("ProductType")
718            .field("asset", &self.asset)
719            .field("bias", &self.bias)
720            .field("time", &self.time)
721            .field("addon", &self.addon)
722            .finish()
723    }
724}
725
726impl<T, I, Provider, Asset, Rational, Time, Addon> Default
727    for ProductType<T, I, Provider, Asset, Rational, Time, Addon>
728where
729    Provider: ProductProvider<Asset, Rational, Time, Addon>,
730    T: Config<I>,
731    I: 'static,
732    Addon: DiscriminantTag,
733    Rational: DiscriminantTag,
734    Time: DiscriminantTag,
735    Asset: DiscriminantTag,
736{
737    fn default() -> Self {
738        Self {
739            asset: Default::default(),
740            bias: Default::default(),
741            time: Default::default(),
742            addon: Default::default(),
743        }
744    }
745}
746
747impl<T, I, Provider, Asset, Rational, Time, Addon> PartialEq
748    for ProductType<T, I, Provider, Asset, Rational, Time, Addon>
749where
750    Provider: ProductProvider<Asset, Rational, Time, Addon>,
751    T: Config<I>,
752    I: 'static,
753    Addon: DiscriminantTag,
754    Rational: DiscriminantTag,
755    Time: DiscriminantTag,
756    Asset: DiscriminantTag,
757{
758    fn eq(&self, other: &Self) -> bool {
759        self.asset == other.asset
760            && self.bias == other.bias
761            && self.time == other.time
762            && self.addon == other.addon
763    }
764}
765
766impl<T, I, Provider, Asset, Rational, Time, Addon> Eq
767    for ProductType<T, I, Provider, Asset, Rational, Time, Addon>
768where
769    Provider: ProductProvider<Asset, Rational, Time, Addon>,
770    T: Config<I>,
771    I: 'static,
772    Addon: DiscriminantTag,
773    Rational: DiscriminantTag,
774    Time: DiscriminantTag,
775    Asset: DiscriminantTag,
776{
777}
778
779// ===============================================================================
780// ```````````````````````` VIRTUAL FIELD ALLOCATIONS ````````````````````````````
781// ===============================================================================
782
783/// Implements [`VirtualDynField`] for a [`ProductType`] field.
784///
785/// Maps a discriminant tag to a concrete virtual struct field
786/// using [`SumDynType`].
787///
788/// Bounds are enforced via [`VirtualDynBound`] provided by [`ProductProvider`].
789macro_rules! impl_v_field {
790    (
791        $tag:ty,
792        $field:ident,
793        $product:ty,
794        $asset:ty,
795        $rational:ty,
796        $time:ty,
797        $addon:ty,
798        $value:ty
799    ) => {
800        impl<T, I, Provider> VirtualDynField<$tag> for $product
801        where
802            Provider: ProductProvider<$asset, $rational, $time, $addon>,
803            T: Config<I>,
804            I: 'static,
805        {
806            type None = ();
807            type Some = $value;
808            type Many = Vec<$value>;
809            type Repr = SumDynType<$value, <Provider as VirtualDynBound<$tag>>::Bound>;
810
811            fn access(&self) -> Self::Repr {
812                self.$field.clone()
813            }
814
815            fn mutate(&mut self, v: Self::Repr) {
816                self.$field = v
817            }
818
819            fn len(&self) -> usize {
820                match &self.$field {
821                    SumDynType::None => 0,
822                    SumDynType::Some(_) => 1,
823                    SumDynType::Many(v) => v.len(),
824                }
825            }
826
827            fn min(&self) -> usize {
828                match &self.$field {
829                    SumDynType::None => 0,
830                    SumDynType::Some(_) => 1,
831                    SumDynType::Many(_) => 0,
832                }
833            }
834
835            fn max(&self) -> usize {
836                match &self.$field {
837                    SumDynType::None => 0,
838                    SumDynType::Some(_) => 1,
839                    SumDynType::Many(_) => {
840                        <Provider as VirtualDynBound<$tag>>::Bound::get() as usize
841                    }
842                }
843            }
844        }
845    };
846}
847
848/// Implements [`VirtualDynExtension`] for a [`ProductType`] extension field.
849///
850/// Maps an extension discriminant to the virtual field,
851/// with layout defined by [`VirtualDynExtensionSchema`] via [`ProductProvider`].
852macro_rules! impl_v_ext {
853    (
854        $tag:ty,
855        $field:ident,
856        $product:ty,
857        $asset:ty,
858        $rational:ty,
859        $time:ty,
860        $addon:ty
861    ) => {
862        impl<T, I, Provider> VirtualDynExtension<$tag> for $product
863        where
864            Provider: ProductProvider<$asset, $rational, $time, $addon>,
865            T: Config<I>,
866            I: 'static,
867        {
868            type TypesVia = Provider;
869
870            fn access(&self) -> <Provider as VirtualDynExtensionSchema<$addon>>::Repr {
871                self.$field.clone()
872            }
873
874            fn mutate(&mut self, v: <Provider as VirtualDynExtensionSchema<$addon>>::Repr) {
875                self.$field = v
876            }
877        }
878    };
879}
880
881/// Implements all virtual field and extension bindings for a [`ProductType`].
882///
883/// Expands [`VirtualDynField`] for core discriminants (`Asset`, `Rational`, `Time`)
884/// and [`VirtualDynExtension`] for the addon field, using [`ProductProvider`]
885/// for bounds and schema.
886macro_rules! impl_product_alloc {
887    (
888        $asset:ty,
889        $rational:ty,
890        $time:ty,
891        $addon:ty
892    ) => {
893
894        impl_v_field!(
895            $asset,
896            asset,
897            ProductType<T,I,Provider,$asset,$rational,$time,$addon>,
898            $asset,
899            $rational,
900            $time,
901            $addon,
902            AssetOf<T,I>
903        );
904
905        impl_v_field!(
906            $rational,
907            bias,
908            ProductType<T,I,Provider,$asset,$rational,$time,$addon>,
909            $asset,
910            $rational,
911            $time,
912            $addon,
913            T::Bias
914        );
915
916        impl_v_field!(
917            $time,
918            time,
919            ProductType<T,I,Provider,$asset,$rational,$time,$addon>,
920            $asset,
921            $rational,
922            $time,
923            $addon,
924            T::Time
925        );
926
927        impl_v_ext!(
928            $addon,
929            addon,
930            ProductType<T,I,Provider,$asset,$rational,$time,$addon>,
931            $asset,
932            $rational,
933            $time,
934            $addon
935        );
936    };
937}
938
939impl_product_alloc!(BalanceAsset, BalanceRational, BalanceTime, BalanceAddon);
940
941impl_product_alloc!(SnapShotAsset, SnapShotRational, SnapShotTime, SnapShotAddon);
942
943impl_product_alloc!(ReceiptAsset, ReceiptRational, ReceiptTime, ReceiptAddon);
944
945// ===============================================================================
946// `````````````````````````` CONVENIENCE TYPE ALIASES ```````````````````````````
947// ===============================================================================
948
949/// Convenient alias for the pallet's [`virtual`](frame_suite::virtuals) balance type.
950type LazyBalanceOf<T, I> = VirtualBalance<T, I>;
951
952/// Convenient alias for the pallet's [`virtual`](frame_suite::virtuals) receipt type.
953type LazyReceiptOf<T, I> = VirtualReceipt<T, I>;
954
955/// Convenient alias for the pallet's underlying asset type.
956type LazyAssetOf<T, I> = AssetOf<T, I>;
957
958/// Convenient alias for the variant (position) used in lazy balance.
959type LazyVariantOf<T, I> = <Pallet<T, I> as LazyBalance>::Variant;
960
961/// Convenient alias for the digest identifier used in lazy balance.
962type LazyIdOf<T, I> = <Pallet<T, I> as LazyBalance>::Id;
963
964/// Convenient alias for the error type resolved from the lazy balance context.
965type LazyErrorOf<T, I> = <Context<Pallet<T, I>> as VirtualError<LazyBalanceError>>::Error;
966
967// ===============================================================================
968// ````````````````````````` VIRTUAL COLLECTORS (ENUMS) ``````````````````````````
969// ===============================================================================
970
971/// Defines the [`LazyInput`] enum for [`LazyBalance`] operations.
972///
973/// Each variant represents an operation input, with typed fields encoded
974/// as tuples and mapped via [`FromTag`] / [`TryIntoTag`].
975///
976/// Enables type-safe, tag-driven dispatch into [`LazyBalance::BalanceFamily`]
977/// [`plugins`](frame_suite::plugins).
978macro_rules! lazy_input {
979    (
980        $(
981            $variant:ident (
982                $( $field:ident : $ty:ty ),* $(,)?
983            )
984        ),* $(,)?
985    ) => {
986
987        pub enum LazyInput<'a, T, I>
988        where
989            T: Config<I>,
990            I: 'static,
991        {
992            $(
993                $variant( $( $ty ),* ),
994            )*
995        }
996
997        $(
998            #[allow(unused_parens)]
999        impl<'a, T, I>
1000            FromTag<( $( $ty ),* ), $variant>
1001        for LazyInput<'a, T, I>
1002        where
1003            T: Config<I>,
1004            I: 'static,
1005        {
1006            fn from_tag(t: ( $( $ty ),* )) -> Self {
1007                let ( $( $field ),* ) = t;
1008                LazyInput::$variant( $( $field ),* )
1009            }
1010        }
1011
1012            #[allow(unused_parens)]
1013        impl<'a, T, I>
1014            TryIntoTag<( $( $ty ),* ), $variant>
1015        for LazyInput<'a, T, I>
1016        where
1017            T: Config<I>,
1018            I: 'static,
1019        {
1020            type Error = ();
1021
1022            fn try_into_tag(self)
1023            -> Result<( $( $ty ),* ), Self::Error>
1024            {
1025                match self {
1026                    LazyInput::$variant( $( $field ),* ) =>
1027                        Ok(( $( $field ),* )),
1028                    _ => Err(()),
1029                }
1030            }
1031        }
1032        )*
1033    };
1034}
1035
1036lazy_input! {
1037
1038    Deposit(
1039        balance: MutHandle<'a, LazyBalanceOf<T, I>>,
1040        variant: Cow<'a, LazyVariantOf<T, I>>,
1041        id: Cow<'a, LazyIdOf<T, I>>,
1042        asset: Cow<'a, LazyAssetOf<T, I>>,
1043        subject: Cow<'a, DispatchPolicy>,
1044    ),
1045
1046    Mint(
1047        balance: MutHandle<'a, LazyBalanceOf<T, I>>,
1048        variant: Cow<'a, LazyVariantOf<T, I>>,
1049        id: Cow<'a, LazyIdOf<T, I>>,
1050        asset: Cow<'a, LazyAssetOf<T, I>>,
1051        subject: Cow<'a, DispatchPolicy>,
1052    ),
1053
1054    Reap(
1055        balance: MutHandle<'a, LazyBalanceOf<T, I>>,
1056        variant: Cow<'a, LazyVariantOf<T, I>>,
1057        id: Cow<'a, LazyIdOf<T, I>>,
1058        asset: Cow<'a, LazyAssetOf<T, I>>,
1059        subject: Cow<'a, DispatchPolicy>,
1060    ),
1061
1062    Drain(
1063        balance: MutHandle<'a, LazyBalanceOf<T, I>>,
1064        variant: Cow<'a, LazyVariantOf<T, I>>,
1065        id: Cow<'a, LazyIdOf<T, I>>,
1066    ),
1067
1068    Withdraw(
1069        balance: MutHandle<'a, LazyBalanceOf<T, I>>,
1070        variant: Cow<'a, LazyVariantOf<T, I>>,
1071        id: Cow<'a, LazyIdOf<T, I>>,
1072        receipt: Cow<'a, LazyReceiptOf<T, I>>,
1073    ),
1074
1075    CanDeposit(
1076        balance: Cow<'a, LazyBalanceOf<T, I>>,
1077        variant: Cow<'a, LazyVariantOf<T, I>>,
1078        id: Cow<'a, LazyIdOf<T, I>>,
1079        asset: Cow<'a, LazyAssetOf<T, I>>,
1080        subject: Cow<'a, DispatchPolicy>,
1081    ),
1082
1083    CanMint(
1084        balance: Cow<'a, LazyBalanceOf<T, I>>,
1085        variant: Cow<'a, LazyVariantOf<T, I>>,
1086        id: Cow<'a, LazyIdOf<T, I>>,
1087        asset: Cow<'a, LazyAssetOf<T, I>>,
1088        subject: Cow<'a, DispatchPolicy>,
1089    ),
1090
1091    CanReap(
1092        balance: Cow<'a, LazyBalanceOf<T, I>>,
1093        variant: Cow<'a, LazyVariantOf<T, I>>,
1094        id: Cow<'a, LazyIdOf<T, I>>,
1095        asset: Cow<'a, LazyAssetOf<T, I>>,
1096        subject: Cow<'a, DispatchPolicy>,
1097    ),
1098
1099    CanWithdraw(
1100        balance: Cow<'a, LazyBalanceOf<T, I>>,
1101        variant: Cow<'a, LazyVariantOf<T, I>>,
1102        id: Cow<'a, LazyIdOf<T, I>>,
1103        receipt: Cow<'a, LazyReceiptOf<T, I>>,
1104    ),
1105
1106    TotalValue(
1107        balance: Cow<'a, LazyBalanceOf<T, I>>,
1108        variant: Cow<'a, LazyVariantOf<T, I>>,
1109        id: Cow<'a, LazyIdOf<T, I>>,
1110    ),
1111
1112    ReceiptActiveValue(
1113        balance: Cow<'a, LazyBalanceOf<T, I>>,
1114        variant: Cow<'a, LazyVariantOf<T, I>>,
1115        id: Cow<'a, LazyIdOf<T, I>>,
1116        receipt: Cow<'a, LazyReceiptOf<T, I>>,
1117    ),
1118
1119    HasDeposits(
1120        balance: Cow<'a, LazyBalanceOf<T, I>>,
1121        variant: Cow<'a, LazyVariantOf<T, I>>,
1122        id: Cow<'a, LazyIdOf<T, I>>,
1123    ),
1124
1125    ReceiptDepositValue(
1126        receipt: Cow<'a, LazyReceiptOf<T, I>>,
1127    ),
1128
1129    DepositLimits(
1130        balance: Cow<'a, LazyBalanceOf<T, I>>,
1131        variant: Cow<'a, LazyVariantOf<T, I>>,
1132        id: Cow<'a, LazyIdOf<T, I>>,
1133        subject: Cow<'a, DispatchPolicy>,
1134    ),
1135
1136    MintLimits(
1137        balance: Cow<'a, LazyBalanceOf<T, I>>,
1138        variant: Cow<'a, LazyVariantOf<T, I>>,
1139        id: Cow<'a, LazyIdOf<T, I>>,
1140        subject: Cow<'a, DispatchPolicy>,
1141    ),
1142
1143    ReapLimits(
1144        balance: Cow<'a, LazyBalanceOf<T, I>>,
1145        variant: Cow<'a, LazyVariantOf<T, I>>,
1146        id: Cow<'a, LazyIdOf<T, I>>,
1147        subject: Cow<'a, DispatchPolicy>,
1148    ),
1149
1150}
1151
1152/// Defines the [`LazyOutput`] enum for [`LazyBalance`] operations.
1153///
1154/// Each variant represents an operation input, with typed fields encoded
1155/// as tuples and mapped via [`FromTag`] / [`TryIntoTag`].
1156///
1157/// Enables type-safe, tag-driven dispatch into [`LazyBalance::BalanceFamily`]
1158/// [`plugins`](frame_suite::plugins).
1159macro_rules! lazy_output {
1160    (
1161        $(
1162            $variant:ident ( $ty:ty )
1163        ),* $(,)?
1164    ) => {
1165
1166        pub enum LazyOutput<'a, T, I>
1167        where
1168            T: Config<I>,
1169            I: 'static,
1170        {
1171            $(
1172                $variant($ty),
1173            )*
1174        }
1175
1176        $(
1177        impl<'a, T, I> FromTag<$ty, $variant>
1178            for LazyOutput<'a, T, I>
1179        where
1180            T: Config<I>,
1181            I: 'static,
1182        {
1183            fn from_tag(t: $ty) -> Self {
1184                Self::$variant(t)
1185            }
1186        }
1187
1188        impl<'a, T, I> TryIntoTag<$ty, $variant>
1189            for LazyOutput<'a, T, I>
1190        where
1191            T: Config<I>,
1192            I: 'static,
1193        {
1194            type Error = ();
1195
1196            fn try_into_tag(self) -> Result<$ty, Self::Error> {
1197                match self {
1198                    Self::$variant(i) => Ok(i),
1199                    _ => Err(()),
1200                }
1201            }
1202        }
1203        )*
1204    };
1205}
1206
1207lazy_output! {
1208    Deposit(Result<(Cow<'a, AssetOf<T, I>>, Cow<'a, LazyReceiptOf<T, I>>), LazyErrorOf<T, I>>),
1209    Mint(Result<Cow<'a, AssetOf<T, I>>, LazyErrorOf<T, I>>),
1210    Reap(Result<Cow<'a, AssetOf<T, I>>, LazyErrorOf<T, I>>),
1211    Withdraw(Result<Cow<'a, LazyAssetOf<T, I>>, LazyErrorOf<T, I>>),
1212    Drain(Result<Cow<'a, AssetOf<T, I>>, LazyErrorOf<T, I>>),
1213    CanDeposit(Result<(), LazyErrorOf<T, I>>),
1214    CanMint(Result<(), LazyErrorOf<T, I>>),
1215    CanReap(Result<(), LazyErrorOf<T, I>>),
1216    CanWithdraw(Result<(), LazyErrorOf<T, I>>),
1217    TotalValue(Result<Cow<'a, LazyAssetOf<T, I>>, LazyErrorOf<T, I>>),
1218    ReceiptActiveValue(Result<Cow<'a, LazyAssetOf<T, I>>, LazyErrorOf<T, I>>),
1219    HasDeposits(Result<(), LazyErrorOf<T, I>>),
1220    ReceiptDepositValue(Result<Cow<'a, LazyAssetOf<T, I>>, LazyErrorOf<T, I>>),
1221    DepositLimits(Result<Cow<'a, LimitsProduct<T, I>>, LazyErrorOf<T, I>>),
1222    MintLimits(Result<Cow<'a, LimitsProduct<T, I>>, LazyErrorOf<T, I>>),
1223    ReapLimits(Result<Cow<'a, LimitsProduct<T, I>>, LazyErrorOf<T, I>>),
1224}
1225
1226// ===============================================================================
1227// `````````````````````````` LAZY BALANCE VIRTUAL MAP ```````````````````````````
1228// ===============================================================================
1229
1230/// [`VirtualNMap`] implementation for snapshot storage of [`VirtualBalance`].
1231///
1232/// Implemented for [`Pallet`] since it satisfies the super-bounds of
1233/// [`LazyBalance`] and provides the concrete virtual storage backend.
1234///
1235/// Maps `(digest, variant, time)` -> [`VirtualSnapShot`], enabling
1236/// time-indexed balance projections via [`BalanceSnapShots`].
1237impl<T, I> VirtualNMap<VirtualBalance<T, I>, SnapShotStorage> for Pallet<T, I>
1238where
1239    T: Config<I>,
1240    I: 'static,
1241{
1242    type Key = (Digest<T>, T::Position, T::Time);
1243
1244    type Value = VirtualSnapShot<T, I>;
1245
1246    type KeyGen = (
1247        NMapKey<Blake2_128Concat, Digest<T>>,
1248        NMapKey<Blake2_128Concat, T::Position>,
1249        NMapKey<Blake2_128Concat, T::Time>,
1250    );
1251
1252    type Map = BalanceSnapShots<T, I>;
1253
1254    type Query = Option<VirtualSnapShot<T, I>>;
1255}
1256
1257// ===============================================================================
1258// ```````````````````````` LAZY BALANCE OPERATION LIMITS ````````````````````````
1259// ===============================================================================
1260
1261/// Virtual limits container for [`LazyBalance`] operations.
1262///
1263/// Stores bounded asset constraints (`min`, `max`, `optimal`) using
1264/// [`SumDynType`] with fixed capacity (`ConstU32<3>`).
1265///
1266/// Interpreted via [`Extent`] and accessed through [`VirtualDynField`].
1267#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen)]
1268#[scale_info(skip_type_params(T, I))]
1269pub struct LimitsProduct<T, I>
1270where
1271    T: Config<I>,
1272    I: 'static,
1273{
1274    /// Asset bounds (`min`, `max`, `optimal`) encoded as [`SumDynType`].
1275    ///
1276    /// Capacity is fixed to 3 via [`ConstU32<3>`].
1277    asset: SumDynType<AssetOf<T, I>, ConstU32<3>>,
1278}
1279
1280impl<T, I> Clone for LimitsProduct<T, I>
1281where
1282    T: Config<I>,
1283    I: 'static,
1284    SumDynType<AssetOf<T, I>, ConstU32<3>>: Clone,
1285{
1286    fn clone(&self) -> Self {
1287        Self {
1288            asset: self.asset.clone(),
1289        }
1290    }
1291}
1292
1293impl<T, I> core::fmt::Debug for LimitsProduct<T, I>
1294where
1295    T: Config<I>,
1296    I: 'static,
1297    SumDynType<AssetOf<T, I>, ConstU32<3>>: core::fmt::Debug,
1298{
1299    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1300        f.debug_struct("LimitsProduct")
1301            .field("asset", &self.asset)
1302            .finish()
1303    }
1304}
1305
1306impl<T, I> Default for LimitsProduct<T, I>
1307where
1308    T: Config<I>,
1309    I: 'static,
1310    SumDynType<AssetOf<T, I>, ConstU32<3>>: Default,
1311{
1312    fn default() -> Self {
1313        Self {
1314            asset: Default::default(),
1315        }
1316    }
1317}
1318
1319impl<T, I> core::cmp::PartialEq for LimitsProduct<T, I>
1320where
1321    T: Config<I>,
1322    I: 'static,
1323    SumDynType<AssetOf<T, I>, ConstU32<3>>: PartialEq,
1324{
1325    fn eq(&self, other: &Self) -> bool {
1326        self.asset == other.asset
1327    }
1328}
1329
1330impl<T, I> core::cmp::Eq for LimitsProduct<T, I>
1331where
1332    T: Config<I>,
1333    I: 'static,
1334    SumDynType<AssetOf<T, I>, ConstU32<3>>: Eq,
1335{
1336}
1337
1338impl<T: Config<I>, I: 'static> VirtualDynField<LimitsAsset> for LimitsProduct<T, I> {
1339    type None = ();
1340
1341    type Some = AssetOf<T, I>;
1342
1343    type Many = Vec<AssetOf<T, I>>;
1344
1345    type Repr = SumDynType<AssetOf<T, I>, ConstU32<3>>;
1346
1347    fn access(&self) -> Self::Repr {
1348        self.asset.clone()
1349    }
1350
1351    fn mutate(&mut self, v: Self::Repr) {
1352        self.asset = v
1353    }
1354
1355    fn len(&self) -> usize {
1356        match &self.asset {
1357            SumDynType::None => 0,
1358            SumDynType::Some(_) => 1,
1359            SumDynType::Many(v) => v.len(),
1360        }
1361    }
1362
1363    fn min(&self) -> usize {
1364        match &self.asset {
1365            SumDynType::None => 0,
1366            SumDynType::Some(_) => 1,
1367            SumDynType::Many(_) => 0,
1368        }
1369    }
1370
1371    fn max(&self) -> usize {
1372        match &self.asset {
1373            SumDynType::None => 0,
1374            SumDynType::Some(_) => 1,
1375            SumDynType::Many(_) => 3,
1376        }
1377    }
1378}
1379
1380impl<T: Config<I>, I: 'static> VirtualDynBound<LimitsAsset> for LimitsProduct<T, I> {
1381    type Bound = ConstU32<3>;
1382}
1383
1384impl<T: Config<I>, I: 'static> Extent<LimitsAsset> for LimitsProduct<T, I> {
1385    type Scalar = AssetOf<T, I>;
1386
1387    fn minimum(&self) -> Option<Self::Scalar> {
1388        self.index_get(0)
1389    }
1390
1391    fn maximum(&self) -> Option<Self::Scalar> {
1392        self.index_get(1)
1393    }
1394
1395    fn optimal(&self) -> Option<Self::Scalar> {
1396        self.index_get(3)
1397    }
1398
1399    fn none() -> Self {
1400        Default::default()
1401    }
1402}
1403
1404impl<T: Config<I>, I: 'static> Extent for LimitsProduct<T, I> {
1405    type Scalar = AssetOf<T, I>;
1406
1407    fn minimum(&self) -> Option<Self::Scalar> {
1408        self.index_get(0)
1409    }
1410
1411    fn maximum(&self) -> Option<Self::Scalar> {
1412        self.index_get(1)
1413    }
1414
1415    fn optimal(&self) -> Option<Self::Scalar> {
1416        self.index_get(3)
1417    }
1418
1419    fn none() -> Self {
1420        Default::default()
1421    }
1422}
1423
1424// ===============================================================================
1425// ```````````````````````` LAZY BALANCE OPERATION POLICY ````````````````````````
1426// ===============================================================================
1427
1428/// Execution policy for [`LazyBalance`] [`plugin`](frame_suite::plugins) operations.
1429///
1430/// Encodes dispatch preferences:
1431/// - `precise`: `true` -> [`Precision::Exact`], `false` -> [`Precision::BestEffort`]
1432/// - `force`  : `true` -> [`Fortitude::Force`], `false` -> [`Fortitude::Polite`]
1433///
1434/// Used to guide behavior during execution.
1435#[derive(
1436    Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen, Debug,
1437)]
1438pub struct DispatchPolicy {
1439    /// `true` for exact execution, `false` for best-effort
1440    pub precise: bool,
1441
1442    /// `true` to force execution, `false` for polite (non-forcing)
1443    pub force: bool,
1444}
1445
1446impl Directive for DispatchPolicy {
1447    fn precision(&self) -> Precision {
1448        if self.precise {
1449            return Precision::Exact;
1450        };
1451        Precision::BestEffort
1452    }
1453
1454    fn fortitude(&self) -> Fortitude {
1455        if self.force {
1456            return Fortitude::Force;
1457        };
1458        Fortitude::Polite
1459    }
1460
1461    fn new(precision: Precision, fortitude: Fortitude) -> Self {
1462        Self {
1463            precise: matches!(precision, Precision::Exact),
1464            force: matches!(fortitude, Fortitude::Force),
1465        }
1466    }
1467}
1468
1469impl Default for DispatchPolicy {
1470    fn default() -> Self {
1471        Self {
1472            precise: false,
1473            force: false,
1474        }
1475    }
1476}