Transaction Science open standards monorepo. Each subdirectory is a self-contained reference protocol with its own README and licence. - openpay/ Apache-2.0 payment-acceptance stack (Rust, 26 crates) - smart-byte/ CC-BY-4.0 content-addressed value carrier (spec) - eoc/ CC-BY-4.0 energy-optimised compute (spec) The wire format and the right to fork are public. The optional hosted services are the offer; the protocols are not.
91 lines
2.8 KiB
Rust
91 lines
2.8 KiB
Rust
//! Balance — what an account holds at a point in time.
|
|
//!
|
|
//! Three balance views per Modern Treasury convention:
|
|
//!
|
|
//! - **Posted** — sum of posted transactions only. The settled state.
|
|
//! - **Pending** — sum of posted AND pending transactions. The
|
|
//! "balance after everything in flight settles" projection.
|
|
//! - **Available** — context-dependent; for OpenPay's reference
|
|
//! model it equals `posted` (we don't model holds beyond pending).
|
|
//!
|
|
//! Balances are derived (never stored). They're computed by walking
|
|
//! the ledger's entries; the [`LedgerStore`](crate::LedgerStore)
|
|
//! does the iteration.
|
|
//!
|
|
//! ## Why a struct instead of `(i64, i64)`?
|
|
//!
|
|
//! Type-safety. `Balance::pending` and `Balance::posted` are clearly
|
|
//! labeled; you can't accidentally pass posted where pending is
|
|
//! expected. The struct also carries the currency so the caller
|
|
//! doesn't have to refetch the account to format the amount.
|
|
|
|
use op_core::{Currency, Money};
|
|
|
|
/// Balance for a single account at a point in time.
|
|
///
|
|
/// All three views carry the account's currency. They're always in
|
|
/// the SAME currency (one account → one currency, fixed at
|
|
/// creation).
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub struct Balance {
|
|
/// Currency. Always matches the account's currency.
|
|
pub currency: Currency,
|
|
|
|
/// Sum of posted-transaction entries, signed by the account's
|
|
/// normal balance so it's natural-positive for a healthy account.
|
|
pub posted: Money,
|
|
|
|
/// Sum of posted-and-pending entries, signed the same way.
|
|
pub pending: Money,
|
|
}
|
|
|
|
impl Balance {
|
|
/// Construct a zero balance in the given currency.
|
|
#[must_use]
|
|
pub fn zero(currency: Currency) -> Self {
|
|
Self {
|
|
currency,
|
|
posted: Money::from_minor(0, currency),
|
|
pending: Money::from_minor(0, currency),
|
|
}
|
|
}
|
|
|
|
/// True if both views are zero.
|
|
#[must_use]
|
|
pub fn is_zero(&self) -> bool {
|
|
self.posted.is_zero() && self.pending.is_zero()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn zero_constructs_zero_money_for_each_view() {
|
|
let b = Balance::zero(Currency::USD);
|
|
assert!(b.is_zero());
|
|
assert_eq!(b.posted.minor_units, 0);
|
|
assert_eq!(b.pending.minor_units, 0);
|
|
assert_eq!(b.currency, Currency::USD);
|
|
}
|
|
|
|
#[test]
|
|
fn non_zero_pending_is_not_zero() {
|
|
let b = Balance {
|
|
currency: Currency::USD,
|
|
posted: Money::from_minor(0, Currency::USD),
|
|
pending: Money::from_minor(100, Currency::USD),
|
|
};
|
|
assert!(!b.is_zero());
|
|
}
|
|
|
|
#[test]
|
|
fn equality_requires_all_three_fields() {
|
|
let a = Balance::zero(Currency::USD);
|
|
let b = Balance::zero(Currency::USD);
|
|
assert_eq!(a, b);
|
|
let c = Balance::zero(Currency::EUR);
|
|
assert_ne!(a, c);
|
|
}
|
|
}
|