Functional Weave
Code in Rust

construction.retention@1.0.0

impl/rust.rs

2,358 bytes · the Rust implementation · view raw

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(),
    ))
}