use crate::{
base::{Asset, Delimited, Fractional, RuntimeEnum, Time},
declare_family, discriminants, impl_discriminants,
misc::{Directive, Extent},
mutation::MutHandle,
plugin_output, plugin_types,
plugins::ModelContext,
virtuals::*,
};
use sp_runtime::Cow;
pub type Context<T> = <<T as LazyBalance>::BalanceContext as ModelContext>::Context;
pub type Error<T> = <Context<T> as VirtualError<LazyBalanceError>>::Error;
pub trait LazyBalance:
VirtualNMap<
Self::Balance,
SnapShotStorage,
Key = (Self::Id, Self::Variant, Self::Time),
Value = Self::SnapShot,
Query = Option<Self::SnapShot>,
>
where
Self: Sized,
{
type Asset: Asset;
type Rational: Fractional;
type Time: Time;
type Variant: Delimited + RuntimeEnum + Default;
type Id: Delimited;
type Subject: Delimited + Directive + Default;
type Balance: LazyBalanceComponent<
Self,
BalanceAsset,
BalanceRational,
BalanceTime,
Context<Self>,
BalanceAddon,
>;
type SnapShot: LazyBalanceComponent<
Self,
SnapShotAsset,
SnapShotRational,
SnapShotTime,
Context<Self>,
SnapShotAddon,
>;
type Receipt: LazyBalanceComponent<
Self,
ReceiptAsset,
ReceiptRational,
ReceiptTime,
Context<Self>,
ReceiptAddon,
>;
type Limits: LazyBalanceLimits<Self>;
type Input<'a>: LazyBalanceInput<
'a,
Self::Balance,
Self::Variant,
Self::Id,
Self::Asset,
Self::Receipt,
Self,
>;
type Output<'a>: LazyBalanceOutput<
'a,
Self::Asset,
Self::Receipt,
Self::SnapShot,
Self::Time,
Self::Limits,
Self,
>;
plugin_types! {
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: BalanceFamily,
context: BalanceContext,
provides: [LazyBalanceContext],
}
plugin_output! {
fn can_deposit,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: CanDeposit,
context: Self::BalanceContext
}
plugin_output! {
fn can_withdraw,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: CanWithdraw,
context: Self::BalanceContext
}
plugin_output! {
fn can_mint,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: CanMint,
context: Self::BalanceContext
}
plugin_output! {
fn can_reap,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: CanReap,
context: Self::BalanceContext
}
plugin_output! {
fn deposit,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: Deposit,
context: Self::BalanceContext
}
plugin_output! {
fn withdraw,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: Withdraw,
context: Self::BalanceContext
}
plugin_output! {
fn mint,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: Mint,
context: Self::BalanceContext
}
plugin_output! {
fn reap,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: Reap,
context: Self::BalanceContext
}
plugin_output! {
fn drain,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: Drain,
context: Self::BalanceContext
}
plugin_output! {
fn total_value,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: TotalValue,
context: Self::BalanceContext
}
plugin_output! {
fn receipt_active_value,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: ReceiptActiveValue,
context: Self::BalanceContext
}
plugin_output! {
fn has_deposits,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: HasDeposits,
context: Self::BalanceContext
}
plugin_output! {
fn receipt_deposit_value,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: ReceiptDepositValue,
context: Self::BalanceContext
}
plugin_output! {
fn deposit_limits,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: DepositLimits,
context: Self::BalanceContext
}
plugin_output! {
fn mint_limits,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: MintLimits,
context: Self::BalanceContext
}
plugin_output! {
fn reap_limits,
input: Self::Input<'a>,
output: Self::Output<'a>,
borrow: ['a],
root: LazyBalanceRoot,
family: Self::BalanceFamily<'a>,
child: ReapLimits,
context: Self::BalanceContext
}
}
declare_family!(
root: pub LazyBalanceRoot,
child: [
CanDeposit,
CanWithdraw,
CanReap,
CanMint,
Deposit,
Withdraw,
Reap,
Drain,
Mint,
TotalValue,
ReceiptActiveValue,
ReceiptDepositValue,
HasDeposits,
DepositLimits,
MintLimits,
ReapLimits,
]
);
impl_discriminants! {
CanDeposit,
CanWithdraw,
CanReap,
CanMint,
Deposit,
Withdraw,
Reap,
Drain,
Mint,
TotalValue,
ReceiptActiveValue,
ReceiptDepositValue,
HasDeposits,
DepositLimits,
ReapLimits,
MintLimits,
}
pub trait LazyBalanceContext:
VirtualDynBound<BalanceAsset>
+ VirtualDynBound<BalanceRational>
+ VirtualDynBound<BalanceTime>
+ VirtualDynBound<SnapShotAsset>
+ VirtualDynBound<SnapShotRational>
+ VirtualDynBound<SnapShotTime>
+ VirtualDynBound<ReceiptAsset>
+ VirtualDynBound<ReceiptRational>
+ VirtualDynBound<ReceiptTime>
+ VirtualError<LazyBalanceError>
+ VirtualDynExtensionSchema<BalanceAddon>
+ VirtualDynExtensionSchema<SnapShotAddon>
+ VirtualDynExtensionSchema<ReceiptAddon>
{
}
impl<T> LazyBalanceContext for T where
T: VirtualDynBound<BalanceAsset>
+ VirtualDynBound<BalanceRational>
+ VirtualDynBound<BalanceTime>
+ VirtualDynBound<SnapShotAsset>
+ VirtualDynBound<SnapShotRational>
+ VirtualDynBound<SnapShotTime>
+ VirtualDynBound<ReceiptAsset>
+ VirtualDynBound<ReceiptRational>
+ VirtualDynBound<ReceiptTime>
+ VirtualError<LazyBalanceError>
+ VirtualDynExtensionSchema<BalanceAddon>
+ VirtualDynExtensionSchema<SnapShotAddon>
+ VirtualDynExtensionSchema<ReceiptAddon>
{
}
pub trait LazyBalanceInput<'a, Balance, Variant, Id, Asset, Receipt, T: LazyBalance>:
VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, T::Subject>,
),
Deposit,
> + VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, T::Subject>,
),
Mint,
> + VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, T::Subject>,
),
Reap,
> + VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Receipt>,
),
Withdraw,
> + VirtualCollector<(MutHandle<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), Drain>
+ VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, T::Subject>,
),
CanDeposit,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, T::Subject>,
),
CanMint,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, T::Subject>,
),
CanReap,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Receipt>,
),
CanWithdraw,
> + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), TotalValue>
+ VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Receipt>,
),
ReceiptActiveValue,
> + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), HasDeposits>
+ VirtualCollector<Cow<'a, Receipt>, ReceiptDepositValue>
+ VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, T::Subject>,
),
DepositLimits,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, T::Subject>,
),
MintLimits,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, T::Subject>,
),
ReapLimits,
>
where
Balance: 'a + Clone,
Variant: 'a + Clone,
Id: 'a + Clone,
Asset: 'a + Clone,
Receipt: 'a + Clone,
{
}
impl<'a, T, Balance, Variant, Id, Asset, Receipt, B>
LazyBalanceInput<'a, Balance, Variant, Id, Asset, Receipt, B> for T
where
Balance: 'a + Clone,
Variant: 'a + Clone,
Id: 'a + Clone,
Asset: 'a + Clone,
Receipt: 'a + Clone,
B: LazyBalance,
T: VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, B::Subject>,
),
Deposit,
> + VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, B::Subject>,
),
Mint,
> + VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, B::Subject>,
),
Reap,
> + VirtualCollector<
(
MutHandle<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Receipt>,
),
Withdraw,
> + VirtualCollector<(MutHandle<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), Drain>
+ VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, B::Subject>,
),
CanDeposit,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, B::Subject>,
),
CanMint,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Asset>,
Cow<'a, B::Subject>,
),
CanReap,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Receipt>,
),
CanWithdraw,
> + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), TotalValue>
+ VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, Receipt>,
),
ReceiptActiveValue,
> + VirtualCollector<(Cow<'a, Balance>, Cow<'a, Variant>, Cow<'a, Id>), HasDeposits>
+ VirtualCollector<Cow<'a, Receipt>, ReceiptDepositValue>
+ VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, B::Subject>,
),
DepositLimits,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, B::Subject>,
),
MintLimits,
> + VirtualCollector<
(
Cow<'a, Balance>,
Cow<'a, Variant>,
Cow<'a, Id>,
Cow<'a, B::Subject>,
),
ReapLimits,
>,
{
}
pub trait LazyBalanceOutput<'a, Asset, Receipt, SnapShot, Time, Limits, T>:
VirtualCollector<Result<(Cow<'a, Asset>, Cow<'a, Receipt>), Error<T>>, Deposit>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Mint>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Reap>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Drain>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, Withdraw>
+ VirtualCollector<Result<(), Error<T>>, CanDeposit>
+ VirtualCollector<Result<(), Error<T>>, CanMint>
+ VirtualCollector<Result<(), Error<T>>, CanReap>
+ VirtualCollector<Result<(), Error<T>>, CanWithdraw>
+ VirtualCollector<Result<(), Error<T>>, HasDeposits>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, TotalValue>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, ReceiptActiveValue>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, ReceiptDepositValue>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<T>>, ReceiptDepositValue>
+ VirtualCollector<Result<Cow<'a, Limits>, Error<T>>, DepositLimits>
+ VirtualCollector<Result<Cow<'a, Limits>, Error<T>>, MintLimits>
+ VirtualCollector<Result<Cow<'a, Limits>, Error<T>>, ReapLimits>
where
T: LazyBalance,
Asset: 'a + Clone,
Receipt: 'a + Clone,
SnapShot: 'a + Clone,
Time: 'a + Clone,
Limits: 'a + Clone,
{
}
impl<'a, T, Asset, Receipt, SnapShot, Time, Limits, B>
LazyBalanceOutput<'a, Asset, Receipt, SnapShot, Time, Limits, B> for T
where
B: LazyBalance,
Asset: 'a + Clone,
Receipt: 'a + Clone,
SnapShot: 'a + Clone,
Time: 'a + Clone,
Limits: 'a + Clone,
T: VirtualCollector<Result<(Cow<'a, Asset>, Cow<'a, Receipt>), Error<B>>, Deposit>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Mint>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Reap>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Drain>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, Withdraw>
+ VirtualCollector<Result<(), Error<B>>, CanDeposit>
+ VirtualCollector<Result<(), Error<B>>, CanMint>
+ VirtualCollector<Result<(), Error<B>>, CanReap>
+ VirtualCollector<Result<(), Error<B>>, CanWithdraw>
+ VirtualCollector<Result<(), Error<B>>, HasDeposits>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, TotalValue>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, ReceiptActiveValue>
+ VirtualCollector<Result<Cow<'a, Asset>, Error<B>>, ReceiptDepositValue>
+ VirtualCollector<Result<Cow<'a, Limits>, Error<B>>, DepositLimits>
+ VirtualCollector<Result<Cow<'a, Limits>, Error<B>>, MintLimits>
+ VirtualCollector<Result<Cow<'a, Limits>, Error<B>>, ReapLimits>,
{
}
pub trait LazyBalanceComponent<T, Asset, Rational, Time, Context, Addon>:
Delimited
+ VirtualDynFieldWithDelegatedBounds<T::Asset, Context, Asset>
+ VirtualDynFieldWithDelegatedBounds<T::Rational, Context, Rational>
+ VirtualDynFieldWithDelegatedBounds<T::Time, Context, Time>
+ DelegateVirtualDynExtension<Context, Addon>
where
T: LazyBalance,
Context: VirtualDynExtensionSchema<Addon>
+ VirtualDynBound<Asset>
+ VirtualDynBound<Rational>
+ VirtualDynBound<Time>,
Addon: DiscriminantTag,
Rational: DiscriminantTag,
Time: DiscriminantTag,
Asset: DiscriminantTag,
{
}
impl<B, T, Asset, Rational, Time, Context, Addon>
LazyBalanceComponent<B, Asset, Rational, Time, Context, Addon> for T
where
T: Delimited
+ VirtualDynFieldWithDelegatedBounds<B::Asset, Context, Asset>
+ VirtualDynFieldWithDelegatedBounds<B::Rational, Context, Rational>
+ VirtualDynFieldWithDelegatedBounds<B::Time, Context, Time>
+ DelegateVirtualDynExtension<Context, Addon>,
B: LazyBalance,
Context: VirtualDynExtensionSchema<Addon>
+ VirtualDynBound<Asset>
+ VirtualDynBound<Rational>
+ VirtualDynBound<Time>,
Addon: DiscriminantTag,
Rational: DiscriminantTag,
Time: DiscriminantTag,
Asset: DiscriminantTag,
{
}
pub trait LazyBalanceLimits<T: LazyBalance>:
Delimited
+ VirtualDynField<LimitsAsset, Some = T::Asset>
+ VirtualDynBound<LimitsAsset>
+ Extent<LimitsAsset, Scalar = T::Asset>
{
}
impl<L, T> LazyBalanceLimits<L> for T
where
T: Delimited
+ VirtualDynField<LimitsAsset, Some = L::Asset>
+ VirtualDynBound<LimitsAsset>
+ Extent<LimitsAsset, Scalar = L::Asset>,
L: LazyBalance,
{
}
discriminants! {
BalanceAsset,
BalanceRational,
BalanceTime,
SnapShotAsset,
SnapShotRational,
SnapShotTime,
ReceiptAsset,
ReceiptRational,
ReceiptTime,
SnapShotStorage,
LazyBalanceError,
BalanceAddon,
SnapShotAddon,
ReceiptAddon,
LimitsAsset,
}
#[cfg(feature = "std")]
pub use lazy_balance_model_checker::*;
#[cfg(feature = "std")]
mod lazy_balance_model_checker {
use crate::{assets::*, base::Sortable};
use core::{fmt::Debug, hash::Hash};
use sp_runtime::{traits::One, Cow};
use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet, vec_deque::VecDeque};
use std::{
fs::{create_dir_all, File},
io::Write,
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};
use rand::{seq::SliceRandom, thread_rng};
pub trait LazyBalanceModelChecker: Sized {
type LazyBalance: LazyBalanceMarker;
type ManualBalance: ManualBalanceModel<Self::LazyBalance>
+ BalanceStateHasher<Self::LazyBalance, Self::ManualBalance>
+ BalanceGuards<Self::LazyBalance, Self::ManualBalance>
+ Clone;
type TrapFn: Fn(
&BalanceState<Self::LazyBalance, Self::ManualBalance>,
&BalanceOp<Self::LazyBalance, Self::ManualBalance>,
) -> bool;
type FlowFn: Fn(
&BalanceState<Self::LazyBalance, Self::ManualBalance>,
&BalanceOp<Self::LazyBalance, Self::ManualBalance>,
) -> bool;
type Hasher: Fn(&BalanceState<Self::LazyBalance, Self::ManualBalance>) -> u64;
fn initiate_results() -> BalanceModelResults<Self::LazyBalance, Self::ManualBalance> {
BalanceModelResults::new()
}
fn explore(
users: &[<Self::ManualBalance as ManualBalanceModel<Self::LazyBalance>>::User],
deposits: &[<Self::LazyBalance as LazyBalance>::Asset],
adjustments: &[<Self::LazyBalance as LazyBalance>::Asset],
subjects: &[<Self::LazyBalance as LazyBalance>::Subject],
max_depth: u32,
allowed_bps: u32,
allowed_diff: u32,
results: &mut BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
) where
<Self::LazyBalance as LazyBalance>::Id: Default,
<Self::LazyBalance as LazyBalance>::Asset: From<<Self::LazyBalance as LazyBalance>::Asset>
+ Into<<Self::LazyBalance as LazyBalance>::Asset>,
{
explore_explore_balance_states_default::<Self::LazyBalance, Self::ManualBalance>(
users,
deposits,
adjustments,
subjects,
max_depth,
allowed_bps,
allowed_diff,
results,
)
}
fn explore_traps(
users: &[<Self::ManualBalance as ManualBalanceModel<Self::LazyBalance>>::User],
deposits: &[<Self::LazyBalance as LazyBalance>::Asset],
adjustments: &[<Self::LazyBalance as LazyBalance>::Asset],
subjects: &[<Self::LazyBalance as LazyBalance>::Subject],
max_depth: u32,
allowed_bps: u32,
allowed_diff: u32,
traps: Option<BalanceTraps<Self::TrapFn, Self::FlowFn>>,
results: &mut BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
) where
<Self::LazyBalance as LazyBalance>::Id: Default,
<Self::LazyBalance as LazyBalance>::Asset: From<<Self::LazyBalance as LazyBalance>::Asset>
+ Into<<Self::LazyBalance as LazyBalance>::Asset>,
{
explore_balance_trap_states_default::<
Self::LazyBalance,
Self::ManualBalance,
Self::TrapFn,
Self::FlowFn,
>(
users,
deposits,
adjustments,
subjects,
max_depth,
allowed_bps,
allowed_diff,
traps,
results,
)
}
fn explore_custom(
users: &[<Self::ManualBalance as ManualBalanceModel<Self::LazyBalance>>::User],
deposits: &[<Self::LazyBalance as LazyBalance>::Asset],
adjustments: &[<Self::LazyBalance as LazyBalance>::Asset],
subjects: &[<Self::LazyBalance as LazyBalance>::Subject],
max_depth: u32,
allowed_bps: u32,
allowed_diff: u32,
traps: Option<BalanceTraps<Self::TrapFn, Self::FlowFn>>,
hasher: Option<Self::Hasher>,
results: &mut BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
) where
<Self::LazyBalance as LazyBalance>::Id: Default,
<Self::LazyBalance as LazyBalance>::Asset: From<<Self::LazyBalance as LazyBalance>::Asset>
+ Into<<Self::LazyBalance as LazyBalance>::Asset>,
{
explore_balance_states::<
Self::LazyBalance,
Self::ManualBalance,
Self::TrapFn,
Self::FlowFn,
Self::Hasher,
>(
users,
deposits,
adjustments,
subjects,
max_depth,
allowed_bps,
allowed_diff,
traps,
hasher,
results,
)
}
fn write_reports(
dir: std::path::PathBuf,
results: &BalanceModelResults<Self::LazyBalance, Self::ManualBalance>,
write_pass: bool,
write_exit: bool,
write_trap: bool,
) {
balance_states_csv_reports(dir, results, write_pass, write_exit, write_trap)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum BalanceOp<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
Deposit(M::User, T::Asset, T::Subject),
Withdraw(M::User),
Mint(T::Asset, T::Subject),
Reap(T::Asset, T::Subject),
Drain,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BalanceFailure<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
pub reason: String,
pub sequence: Vec<BalanceOp<T, M>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BalanceExit<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
pub lazy: T::Asset,
pub manual: T::Asset,
pub diff: T::Asset,
pub bps: T::Asset,
pub sequence: Vec<BalanceOp<T, M>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LazyContainer<T: LazyBalanceMarker> {
pub balance: T::Balance,
pub variant: T::Variant,
pub id: T::Id,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BalanceState<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
pub lazy: LazyContainer<T>,
pub manual: M,
pub receipts: BTreeMap<M::User, T::Receipt>,
pub trace: Vec<BalanceOp<T, M>>,
}
pub struct BalanceTraps<G, F> {
pub trap: G,
pub flow: F,
pub reason: String,
}
pub trait LazyBalanceMarker: LazyBalance + Clone + Debug {}
impl<T> LazyBalanceMarker for T where T: LazyBalance + Clone + Debug {}
pub trait ManualBalanceModel<T>: Clone + Debug
where
T: LazyBalanceMarker,
{
type User: Sortable + Hash + Copy;
type Error: Debug + Clone + 'static;
fn new() -> Self;
fn deposit(
&mut self,
user: Self::User,
amount: T::Asset,
lazy_result: &(T::Asset, T::Receipt),
) -> Result<(), Self::Error>;
fn withdraw(
&mut self,
user: Self::User,
lazy_result: &T::Asset,
) -> Result<T::Asset, Self::Error>;
fn mint(&mut self, amount: T::Asset, lazy_result: &T::Asset) -> Result<(), Self::Error>;
fn reap(&mut self, amount: T::Asset, lazy_result: &T::Asset) -> Result<(), Self::Error>;
fn drain(&mut self) -> Result<(), Self::Error>;
fn total(&self) -> T::Asset;
}
pub trait BalanceStateHasher<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
fn hash(state: &BalanceState<T, M>) -> u64;
}
pub trait BalanceGuards<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
fn deposit(
_state: &BalanceState<T, M>,
_user: &M::User,
_amount: &T::Asset,
_subject: &T::Subject,
) -> bool {
true
}
fn withdraw(_state: &BalanceState<T, M>, _user: &M::User) -> bool {
true
}
fn mint(_state: &BalanceState<T, M>, _value: &T::Asset, _subject: &T::Subject) -> bool {
true
}
fn reap(_state: &BalanceState<T, M>, _value: &T::Asset, _subject: &T::Subject) -> bool {
true
}
fn drain(_state: &BalanceState<T, M>) -> bool {
true
}
fn flow(_state: &BalanceState<T, M>, _next: &BalanceOp<T, M>) -> bool {
true
}
fn invariant(_state: &BalanceState<T, M>) -> Result<(), String> {
Ok(())
}
}
fn explore_explore_balance_states_default<T, M>(
users: &[M::User],
deposits: &[T::Asset],
adjustments: &[T::Asset],
subjects: &[T::Subject],
max_depth: u32,
allowed_bps: u32,
allowed_diff: u32,
results: &mut BalanceModelResults<T, M>,
) where
T: LazyBalanceMarker + Clone,
M: ManualBalanceModel<T> + BalanceStateHasher<T, M> + BalanceGuards<T, M> + Clone,
T::Id: Default,
T::Asset: From<T::Asset> + Into<T::Asset>,
{
explore_balance_states::<
T,
M,
fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
fn(&BalanceState<T, M>) -> u64,
>(
users,
deposits,
adjustments,
subjects,
max_depth,
allowed_bps,
allowed_diff,
None,
None,
results,
);
}
fn explore_balance_trap_states_default<T, M, G, F>(
users: &[M::User],
deposits: &[T::Asset],
adjustments: &[T::Asset],
subjects: &[T::Subject],
max_depth: u32,
allowed_bps: u32,
allowed_diff: u32,
overrides: Option<BalanceTraps<G, F>>,
results: &mut BalanceModelResults<T, M>,
) where
T: LazyBalanceMarker + Clone,
M: ManualBalanceModel<T> + BalanceStateHasher<T, M> + BalanceGuards<T, M> + Clone,
T::Id: Default,
T::Asset: From<T::Asset> + Into<T::Asset>,
G: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
F: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
{
explore_balance_states::<T, M, G, F, fn(&BalanceState<T, M>) -> u64>(
users,
deposits,
adjustments,
subjects,
max_depth,
allowed_bps,
allowed_diff,
overrides,
None,
results,
);
}
fn explore_balance_states<T, M, G, F, H>(
users: &[M::User],
deposits: &[T::Asset],
adjustments: &[T::Asset],
subjects: &[T::Subject],
max_depth: u32,
allowed_bps: u32,
allowed_diff: u32,
overrides: Option<BalanceTraps<G, F>>,
hasher: Option<H>,
results: &mut BalanceModelResults<T, M>,
) where
T: LazyBalanceMarker + Clone,
M: ManualBalanceModel<T> + BalanceStateHasher<T, M> + BalanceGuards<T, M> + Clone,
T::Id: Default,
T::Asset: From<T::Asset> + Into<T::Asset>,
G: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
F: Fn(&BalanceState<T, M>, &BalanceOp<T, M>) -> bool,
H: Fn(&BalanceState<T, M>) -> u64,
{
let mut visited = BTreeSet::new();
let mut queue = VecDeque::new();
let mut rng = thread_rng();
let traps = overrides.as_ref().map(|v| &v.reason);
let flow_ok = |state: &BalanceState<T, M>, op: &BalanceOp<T, M>| {
let base = <M as BalanceGuards<T, M>>::flow(state, op);
if let Some(o) = &overrides {
base && (o.flow)(state, op)
} else {
base
}
};
let guard_ok = |state: &BalanceState<T, M>, op: &BalanceOp<T, M>| {
let base = match op {
BalanceOp::Deposit(u, v, l) => <M as BalanceGuards<T, M>>::deposit(state, u, v, l),
BalanceOp::Withdraw(u) => <M as BalanceGuards<T, M>>::withdraw(state, u),
BalanceOp::Mint(v, l) => <M as BalanceGuards<T, M>>::mint(state, v, l),
BalanceOp::Reap(v, l) => <M as BalanceGuards<T, M>>::reap(state, v, l),
BalanceOp::Drain => <M as BalanceGuards<T, M>>::drain(state),
};
let trap_override = overrides
.as_ref()
.map(|o| (o.trap)(state, op))
.unwrap_or(false);
base || trap_override
};
let push_trace = |trace: &[BalanceOp<T, M>], op| {
let mut t = trace.to_vec();
t.push(op);
t
};
queue.push_back(BalanceState {
lazy: LazyContainer::<T> {
balance: Default::default(),
variant: Default::default(),
id: Default::default(),
},
manual: M::new(),
receipts: BTreeMap::new(),
trace: Vec::new(),
});
while let Some(state) = queue.pop_front() {
if let Err(reason) = <M as BalanceGuards<T, M>>::invariant(&state) {
let reason = reason.to_string();
record_err(results, &state.trace, reason, traps);
continue;
}
if state.trace.len() == max_depth as usize {
record_pass(results, &state.trace, traps);
continue;
}
if !visited.insert(<M as BalanceStateHasher<T, M>>::hash(&state)) {
continue;
}
if let Some(ref h) = hasher {
if !visited.insert(h(&state)) {
continue;
}
}
for &u in users {
for _ in 0..deposits.len() {
if let Some(&amount) = deposits.choose(&mut rng) {
let sub = subjects.choose(&mut rng).cloned().unwrap_or_default();
if !guard_ok(&state, &BalanceOp::Deposit(u, amount, sub.clone())) {
continue;
}
if !flow_ok(&state, &BalanceOp::Deposit(u, amount, sub.clone())) {
continue;
}
let mut next = state.clone();
match deposit::<T>(&mut next.lazy, amount, &sub) {
Ok(pass) => {
if let Err(e) = next.manual.deposit(u, amount, &pass) {
let trace = push_trace(
&state.trace,
BalanceOp::Deposit(u, amount, sub),
);
record_err(results, &trace, format!("{:?}", e), traps);
continue;
}
let (actual, receipt) = pass;
next.receipts.insert(u, receipt);
next.trace.push(BalanceOp::Deposit(u, actual, sub));
queue.push_back(next);
}
Err(e) => {
let trace =
push_trace(&state.trace, BalanceOp::Deposit(u, amount, sub));
record_err(results, &trace, format!("{:?}", e), traps);
}
}
}
}
}
for &u in users {
if !guard_ok(&state, &BalanceOp::Withdraw(u)) {
continue;
}
if !flow_ok(&state, &BalanceOp::Withdraw(u)) {
continue;
}
let mut next = state.clone();
let Some(receipt) = next.receipts.remove(&u) else {
let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
record_err(
results,
&trace,
"ModelChecker::WithdrawReceiptMissing".into(),
traps,
);
continue;
};
match withdraw::<T>(&mut next.lazy, receipt) {
Ok(lazy_value) => {
let manual_value = match next.manual.withdraw(u, &lazy_value) {
Ok(v) => v,
Err(e) => {
let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
record_err(results, &trace, format!("{:?}", e), traps);
continue;
}
};
let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
if let Err(exit) = compare_balances_values::<T, M>(
lazy_value,
manual_value,
&trace,
allowed_bps,
allowed_diff,
) {
record_exit(results, exit);
continue;
}
next.trace = trace;
queue.push_back(next);
}
Err(e) => {
let trace = push_trace(&next.trace, BalanceOp::Withdraw(u));
record_err(results, &trace, format!("{:?}", e), traps);
}
}
}
for _ in 0..adjustments.len() {
if let Some(&v) = adjustments.choose(&mut rng) {
let sub = subjects.choose(&mut rng).cloned().unwrap_or_default();
if !guard_ok(&state, &BalanceOp::Mint(v, sub.clone())) {
continue;
}
if !flow_ok(&state, &BalanceOp::Mint(v, sub.clone())) {
continue;
}
let mut next = state.clone();
match mint::<T>(&mut next.lazy, v, &sub) {
Ok(pass) => {
if let Err(e) = next.manual.mint(v, &pass) {
let trace = push_trace(&next.trace, BalanceOp::Mint(v, sub));
record_err(results, &trace, format!("{:?}", e), traps);
continue;
}
next.trace.push(BalanceOp::Mint(pass, sub));
queue.push_back(next);
}
Err(e) => {
let trace = push_trace(&next.trace, BalanceOp::Mint(v, sub));
record_err(results, &trace, format!("{:?}", e), traps);
}
}
}
}
for _ in 0..adjustments.len() {
if let Some(&v) = adjustments.choose(&mut rng) {
let sub = subjects.choose(&mut rng).cloned().unwrap_or_default();
if !guard_ok(&state, &BalanceOp::Reap(v, sub.clone())) {
continue;
}
if !flow_ok(&state, &BalanceOp::Reap(v, sub.clone())) {
continue;
}
let mut next = state.clone();
match reap::<T>(&mut next.lazy, v, &sub) {
Ok(pass) => {
if let Err(e) = next.manual.reap(v, &pass) {
let trace = push_trace(&next.trace, BalanceOp::Reap(v, sub));
record_err(results, &trace, format!("{:?}", e), traps);
continue;
}
next.trace.push(BalanceOp::Reap(pass, sub));
queue.push_back(next);
}
Err(e) => {
let trace = push_trace(&next.trace, BalanceOp::Reap(v, sub));
record_err(results, &trace, format!("{:?}", e), traps);
}
}
}
}
if flow_ok(&state, &BalanceOp::Drain) && guard_ok(&state, &BalanceOp::Drain) {
let mut next = state.clone();
match drain::<T>(&mut next.lazy) {
Ok(_) => {
if let Err(e) = next.manual.drain() {
let trace = push_trace(&next.trace, BalanceOp::Drain);
record_err(results, &trace, format!("{:?}", e), traps);
continue;
}
next.trace.push(BalanceOp::Drain);
queue.push_back(next);
}
Err(e) => {
let trace = push_trace(&next.trace, BalanceOp::Drain);
record_err(results, &trace, format!("{:?}", e), traps);
}
}
}
}
}
fn balance_states_csv_reports<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
dir: PathBuf,
results: &BalanceModelResults<T, M>,
write_pass: bool,
write_exit: bool,
write_trap: bool,
) {
if results.fail.is_empty()
&& results.pass.is_empty()
&& results.trap.is_empty()
&& results.exit.is_empty()
{
return;
}
let fuzz_dir = dir;
create_dir_all(&fuzz_dir).unwrap();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
if !results.fail.is_empty() {
let fail_path = fuzz_dir.join(format!("fail_{}.csv", now));
let mut fail_file = File::create(&fail_path).unwrap();
writeln!(fail_file, "sl_no,reason,step,sequence").unwrap();
for (id, f) in &results.fail {
writeln!(fail_file, "{},\"{}\",\"{:?}\"", id, f.reason, f.sequence).unwrap();
}
}
if write_pass && !results.pass.is_empty() {
let pass_path = fuzz_dir.join(format!("pass_{}.csv", now));
let mut pass_file = File::create(&pass_path).unwrap();
writeln!(pass_file, "sl_no,sequence").unwrap();
for (id, seq) in &results.pass {
writeln!(pass_file, "{},\"{:?}\"", id, seq).unwrap();
}
}
if write_trap && !results.trap.is_empty() {
let trap_path = fuzz_dir.join(format!("trap_{}.csv", now));
let mut trap_file = File::create(&trap_path).unwrap();
writeln!(trap_file, "sl_no,reason,sequence").unwrap();
for (id, reason, seq) in &results.trap {
writeln!(trap_file, "{},\"{}\",\"{:?}\"", id, reason, seq).unwrap();
}
}
if write_exit && !results.exit.is_empty() {
let exit_path = fuzz_dir.join(format!("exit_{}.csv", now));
let mut exit_file = File::create(&exit_path).unwrap();
writeln!(exit_file, "sl_no,lazy,manual,diff,bps,sequence").unwrap();
for (id, exit) in &results.exit {
writeln!(
exit_file,
"{},{:?},{:?},{:?},{:?},\"{:?}\"",
id, exit.lazy, exit.manual, exit.diff, exit.bps, exit.sequence
)
.unwrap();
}
}
if !results.trap.is_empty() {
println!(
"Trap check completed: {} failed, {} passed, {} exited, {} trapped",
results.fail.len(),
results.pass.len(),
results.exit.len(),
results.trap.len(),
);
} else {
println!(
"Model check completed: {} failed, {} passed, {} exited",
results.fail.len(),
results.pass.len(),
results.exit.len(),
);
}
}
fn compare_balances_values<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
lazy: T::Asset,
manual: T::Asset,
seq: &[BalanceOp<T, M>],
allowed_bps: u32,
allowed_diff: u32,
) -> Result<(), BalanceExit<T, M>> {
let lazy = lazy.into();
let diff = if lazy > manual {
lazy - manual
} else {
manual - lazy
};
if diff <= One::one() {
return Ok(());
}
let base = lazy.max(manual).max(1u8.into());
if diff * 10_000u32.into() > base * allowed_bps.into() {
if diff <= allowed_diff.into() {
return Ok(());
}
return Err(BalanceExit::<T, M> {
lazy,
manual,
diff,
bps: diff * 10_000u32.into() / base,
sequence: seq.to_vec(),
});
}
Ok(())
}
fn record_err<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
results: &mut BalanceModelResults<T, M>,
trace: &[BalanceOp<T, M>],
reason: String,
traps: Option<&String>,
) {
let failure = BalanceFailure {
reason,
sequence: trace.to_vec(),
};
match traps {
Some(e) => results.record_trapped(trace, Err(failure), Some(e)),
None => results.record(trace, Err(failure)),
}
}
fn record_pass<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
results: &mut BalanceModelResults<T, M>,
trace: &[BalanceOp<T, M>],
traps: Option<&String>,
) {
match traps {
Some(_) => results.record(trace, Ok(())),
None => results.record(trace, Ok(())),
}
}
fn record_exit<T: LazyBalanceMarker, M: ManualBalanceModel<T>>(
results: &mut BalanceModelResults<T, M>,
exit: BalanceExit<T, M>,
) {
results.exit_record(exit)
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct BalanceModelResults<T: LazyBalanceMarker, M: ManualBalanceModel<T>> {
pub pass: Vec<(usize, Vec<BalanceOp<T, M>>)>,
pub fail: Vec<(usize, BalanceFailure<T, M>)>,
pub exit: Vec<(usize, BalanceExit<T, M>)>,
pub trap: Vec<(usize, String, Vec<BalanceOp<T, M>>)>,
pub next_pass_id: usize,
pub next_exit_id: usize,
pub next_fail_id: usize,
pub next_trap_id: usize,
}
impl<T: LazyBalanceMarker, M: ManualBalanceModel<T>> BalanceModelResults<T, M> {
fn new() -> Self {
Self {
pass: Vec::new(),
fail: Vec::new(),
exit: Vec::new(),
trap: Vec::new(),
next_pass_id: 1,
next_exit_id: 1,
next_fail_id: 1,
next_trap_id: 1,
}
}
fn exit_record(&mut self, exit: BalanceExit<T, M>) {
let id = self.next_exit_id;
self.next_exit_id += 1;
self.exit.push((id, exit));
}
fn record(&mut self, seq: &[BalanceOp<T, M>], result: Result<(), BalanceFailure<T, M>>) {
self.record_trapped(seq, result, None);
}
fn record_trapped(
&mut self,
seq: &[BalanceOp<T, M>],
result: Result<(), BalanceFailure<T, M>>,
traps: Option<&String>,
) {
match result {
Ok(_) => {
let id = self.next_pass_id;
self.next_pass_id += 1;
self.pass.push((id, seq.to_vec()));
}
Err(f) => {
if let Some(trap) = traps {
if *trap == f.reason {
let id = self.next_trap_id;
self.next_trap_id += 1;
self.trap.push((id, f.reason.clone(), seq.to_vec()));
return;
}
}
let id = self.next_fail_id;
self.next_fail_id += 1;
self.fail.push((id, f.clone()));
}
}
}
}
fn deposit<'a, T: LazyBalanceMarker>(
model: &'a mut LazyContainer<T>,
value: T::Asset,
subject: &'a T::Subject,
) -> Result<(T::Asset, T::Receipt), Error<T>> {
let input = <T::Input<'_> as FromTag<_, Deposit>>::from_tag((
MutHandle::Borrowed(&mut model.balance),
Cow::Borrowed(&model.variant),
Cow::Borrowed(&model.id),
Cow::Owned(value),
Cow::Borrowed(subject),
));
let raw = T::deposit(input);
let Ok(result) = TryIntoTag::<_, Deposit>::try_into_tag(raw) else {
unreachable!()
};
match result {
Ok((asset, receipt)) => Ok((asset.into_owned(), receipt.into_owned())),
Err(e) => Err(e),
}
}
fn withdraw<'a, T: LazyBalanceMarker>(
model: &'a mut LazyContainer<T>,
receipt: T::Receipt,
) -> Result<T::Asset, Error<T>> {
let input = <T::Input<'_> as FromTag<_, Withdraw>>::from_tag((
MutHandle::Borrowed(&mut model.balance),
Cow::Borrowed(&model.variant),
Cow::Borrowed(&model.id),
Cow::Owned(receipt),
));
let raw = T::withdraw(input);
let Ok(result) = TryIntoTag::<_, Withdraw>::try_into_tag(raw) else {
unreachable!()
};
match result {
Ok(v) => Ok(v.into_owned()),
Err(e) => Err(e),
}
}
fn mint<'a, T: LazyBalanceMarker>(
model: &'a mut LazyContainer<T>,
value: T::Asset,
subject: &'a T::Subject,
) -> Result<T::Asset, Error<T>> {
let input = <T::Input<'_> as FromTag<_, Mint>>::from_tag((
MutHandle::Borrowed(&mut model.balance),
Cow::Borrowed(&model.variant),
Cow::Borrowed(&model.id),
Cow::Owned(value),
Cow::Borrowed(subject),
));
let raw = T::mint(input);
let Ok(result) = TryIntoTag::<_, Mint>::try_into_tag(raw) else {
unreachable!()
};
match result {
Ok(v) => Ok(v.into_owned()),
Err(e) => Err(e),
}
}
fn reap<'a, T: LazyBalanceMarker>(
model: &'a mut LazyContainer<T>,
value: T::Asset,
subject: &'a T::Subject,
) -> Result<T::Asset, Error<T>> {
let input = <T::Input<'_> as FromTag<_, Reap>>::from_tag((
MutHandle::Borrowed(&mut model.balance),
Cow::Borrowed(&model.variant),
Cow::Borrowed(&model.id),
Cow::Owned(value),
Cow::Borrowed(subject),
));
let raw = T::reap(input);
let Ok(result) = TryIntoTag::<_, Reap>::try_into_tag(raw) else {
unreachable!()
};
match result {
Ok(v) => Ok(v.into_owned()),
Err(e) => Err(e),
}
}
fn drain<'a, T: LazyBalanceMarker>(
model: &'a mut LazyContainer<T>,
) -> Result<T::Asset, Error<T>> {
let input = <T::Input<'_> as FromTag<_, Drain>>::from_tag((
MutHandle::Borrowed(&mut model.balance),
Cow::Borrowed(&model.variant),
Cow::Borrowed(&model.id),
));
let raw = <T as LazyBalance>::drain(input);
let Ok(result) = TryIntoTag::<_, Drain>::try_into_tag(raw) else {
unreachable!()
};
match result {
Ok(v) => Ok(v.into_owned()),
Err(e) => Err(e),
}
}
}