Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
const MAX_SAFE: i128 = 9_007_199_254_740_991;
/// Retention on a valuation, the way JCT-style contracts release it.
///
/// Retention is always worked out afresh on the cumulative value, never
/// accumulated valuation by valuation, so a later valuation corrects an earlier
/// one. At practical completion half the percentage applies (not half of a
/// figure rounded earlier), and after the defects are made good none does.
///
/// # Panics
/// Panics on a percentage outside 0..=10000, a negative value, an unknown
/// stage, or a product beyond 2^53 - 1.
pub fn retention(cumulative_value: &Money, basis_points: i64, stage: &str) -> Retention {
if !(0..=10000).contains(&basis_points) {
panic!("basisPoints must be a whole number from 0 to 10000, received {}", basis_points);
}
if cumulative_value.minor < 0 {
panic!("cumulativeValue must not be negative, received {}", cumulative_value.minor);
}
let product = cumulative_value.minor as i128 * basis_points as i128;
// i64 could go further, but TypeScript cannot, and all three must agree.
if product > MAX_SAFE {
panic!("the retention calculation exceeds 2^53 - 1");
}
let product = product as i64;
let full = round_div(product, 10000, "half-up");
let held = match stage {
"interim" => full,
"practical-completion" => round_div(product, 20000, "half-up"),
"final" => 0,
other => panic!("unknown retention stage \"{}\"", other),
};
let currency = &cumulative_value.currency;
Retention {
full: money(full, currency),
held: money(held, currency),
released: money(full - held, currency),
}
}
pub fn retention_to_value(r: &Retention) -> Value {
Value::obj(vec![
("full", money_to_value(&r.full)),
("held", money_to_value(&r.held)),
("released", money_to_value(&r.released)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
if let Value::Float(f) = &args[1] {
panic!("basisPoints must be a whole number from 0 to 10000, received {}", f);
}
retention_to_value(&retention(
&money_from_value(&args[0]),
args[1].as_i64(),
args[2].as_str(),
))
}