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_RENT: i64 = 1_000_000_000;
const MAX_INDEX: i64 = 1_000_000;
fn check_basis_points(name: &str, value: Option<i64>) {
if let Some(v) = value {
if v <= -10000 || v > 100000 {
panic!("{} must be an integer from -9999 to 100000, received {}", name, v);
}
}
}
/// The rent after an index-linked review, held between an optional collar and
/// cap, and not below the passing rent when upward-only. Every comparison is
/// on exact fractions and the rent is rounded half-up once.
///
/// # Panics
/// Panics on a rent or index out of range, a collar or cap out of range, or a
/// collar above the cap.
pub fn rent_review(
passing_rent: &Money,
base_index: i64,
review_index: i64,
collar_basis_points: Option<i64>,
cap_basis_points: Option<i64>,
upward_only: bool,
) -> RentReviewResult {
if passing_rent.minor < 0 || passing_rent.minor > MAX_RENT {
panic!("passingRent must be between 0 and {} minor units, received {}", MAX_RENT, passing_rent.minor);
}
for (name, value) in [("baseIndex", base_index), ("reviewIndex", review_index)] {
if value <= 0 || value > MAX_INDEX {
panic!("{} must be an integer from 1 to {}, received {}", name, MAX_INDEX, value);
}
}
check_basis_points("collarBasisPoints", collar_basis_points);
check_basis_points("capBasisPoints", cap_basis_points);
if let (Some(collar), Some(cap)) = (collar_basis_points, cap_basis_points) {
if collar > cap {
panic!("collar {} is above cap {}", collar, cap);
}
}
let p = passing_rent.minor;
// The multiplier as a fraction num/den: the index ratio, or 1 + collar/cap.
let (mut num, mut den, mut rule) = (review_index, base_index, "index");
match (cap_basis_points, collar_basis_points) {
(Some(cap), _) if review_index * 10000 > base_index * (10000 + cap) => {
num = 10000 + cap;
den = 10000;
rule = "cap";
}
(_, Some(collar)) if review_index * 10000 < base_index * (10000 + collar) => {
num = 10000 + collar;
den = 10000;
rule = "collar";
}
_ => {}
}
if upward_only && num < den {
num = 1;
den = 1;
rule = "upward-only";
}
RentReviewResult {
new_rent: money(round_div(p * num, den, "half-up"), &passing_rent.currency),
index_rent: money(round_div(p * review_index, base_index, "half-up"), &passing_rent.currency),
index_change_basis_points: round_div((review_index - base_index) * 10000, base_index, "half-up"),
applied_by: rule.to_string(),
}
}
pub fn rent_review_result_to_value(result: &RentReviewResult) -> Value {
Value::obj(vec![
("newRent", money_to_value(&result.new_rent)),
("indexRent", money_to_value(&result.index_rent)),
("indexChangeBasisPoints", Value::Int(result.index_change_basis_points)),
("appliedBy", Value::str(&result.applied_by)),
])
}
fn optional_int(value: &Value, name: &str) -> Option<i64> {
match value {
Value::Null => None,
Value::Int(v) => Some(*v),
_ => panic!("{} must be an integer from -9999 to 100000, received {:?}", name, value),
}
}
fn int_arg(value: &Value, name: &str) -> i64 {
match value {
Value::Int(v) => *v,
_ => panic!("{} must be an integer from 1 to {}, received {:?}", name, MAX_INDEX, value),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
rent_review_result_to_value(&rent_review(
&money_from_value(&args[0]),
int_arg(&args[1], "baseIndex"),
int_arg(&args[2], "reviewIndex"),
optional_int(&args[3], "collarBasisPoints"),
optional_int(&args[4], "capBasisPoints"),
args[5].as_bool(),
))
}