use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use crate::error::CorpFinanceError;
use crate::CorpFinanceResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WealthTransferInput {
pub estate_value: Decimal,
pub annual_exclusion: Decimal,
pub lifetime_exemption: Decimal,
pub estate_tax_rate: Decimal,
pub gst_tax_rate: Decimal,
pub num_beneficiaries: u32,
pub transfer_years: u32,
pub asset_growth_rate: Decimal,
pub grantor_trust_assets: Decimal,
pub grat_annuity_rate: Decimal,
pub section_7520_rate: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferStrategy {
pub name: String,
pub amount_transferred: Decimal,
pub tax_savings: Decimal,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WealthTransferOutput {
pub annual_gifts_total: Decimal,
pub total_annual_gifts: Decimal,
pub remaining_estate: Decimal,
pub grat_remainder: Decimal,
pub grat_tax_savings: Decimal,
pub taxable_estate: Decimal,
pub estate_tax: Decimal,
pub gst_exposure: Decimal,
pub effective_transfer_rate: Decimal,
pub strategies: Vec<TransferStrategy>,
}
fn validate(input: &WealthTransferInput) -> CorpFinanceResult<()> {
if input.estate_value <= Decimal::ZERO {
return Err(CorpFinanceError::InvalidInput {
field: "estate_value".into(),
reason: "must be positive".into(),
});
}
if input.annual_exclusion < Decimal::ZERO {
return Err(CorpFinanceError::InvalidInput {
field: "annual_exclusion".into(),
reason: "cannot be negative".into(),
});
}
if input.lifetime_exemption < Decimal::ZERO {
return Err(CorpFinanceError::InvalidInput {
field: "lifetime_exemption".into(),
reason: "cannot be negative".into(),
});
}
if input.estate_tax_rate < Decimal::ZERO || input.estate_tax_rate > Decimal::ONE {
return Err(CorpFinanceError::InvalidInput {
field: "estate_tax_rate".into(),
reason: "must be between 0 and 1".into(),
});
}
if input.gst_tax_rate < Decimal::ZERO || input.gst_tax_rate > Decimal::ONE {
return Err(CorpFinanceError::InvalidInput {
field: "gst_tax_rate".into(),
reason: "must be between 0 and 1".into(),
});
}
if input.num_beneficiaries == 0 {
return Err(CorpFinanceError::InvalidInput {
field: "num_beneficiaries".into(),
reason: "must be at least 1".into(),
});
}
if input.transfer_years == 0 {
return Err(CorpFinanceError::InvalidInput {
field: "transfer_years".into(),
reason: "must be at least 1 year".into(),
});
}
if input.grantor_trust_assets < Decimal::ZERO {
return Err(CorpFinanceError::InvalidInput {
field: "grantor_trust_assets".into(),
reason: "cannot be negative".into(),
});
}
if input.grat_annuity_rate < Decimal::ZERO {
return Err(CorpFinanceError::InvalidInput {
field: "grat_annuity_rate".into(),
reason: "cannot be negative".into(),
});
}
if input.section_7520_rate < Decimal::ZERO {
return Err(CorpFinanceError::InvalidInput {
field: "section_7520_rate".into(),
reason: "cannot be negative".into(),
});
}
Ok(())
}
pub fn analyze_wealth_transfer(
input: &WealthTransferInput,
) -> CorpFinanceResult<WealthTransferOutput> {
validate(input)?;
let n_benef = Decimal::from(input.num_beneficiaries);
let n_years = Decimal::from(input.transfer_years);
let annual_gifts_total = input.annual_exclusion * n_benef;
let total_annual_gifts = annual_gifts_total * n_years;
let gifts_capped = total_annual_gifts.min(input.estate_value);
let mut remaining = input.estate_value - gifts_capped;
let growth = Decimal::ONE + input.asset_growth_rate;
for _ in 0..input.transfer_years {
remaining *= growth;
}
let grat_contribution = input.grantor_trust_assets;
let grat_annuity = input.grat_annuity_rate * grat_contribution;
let mut annuity_pv = Decimal::ZERO;
let hurdle_div = Decimal::ONE + input.section_7520_rate;
let mut df = Decimal::ONE;
for _ in 0..input.transfer_years {
df /= hurdle_div;
annuity_pv += grat_annuity * df;
}
let mut grat_fv = grat_contribution;
for _ in 0..input.transfer_years {
grat_fv *= growth;
}
let total_annuity_paid = grat_annuity * n_years;
let grat_remainder = if grat_fv > total_annuity_paid {
grat_fv - total_annuity_paid
} else {
Decimal::ZERO
};
let grat_tax_savings = grat_remainder * input.estate_tax_rate;
let estate_after_grat = if remaining > grat_contribution {
remaining - grat_contribution
} else {
remaining
};
let taxable_estate = if estate_after_grat > input.lifetime_exemption {
estate_after_grat - input.lifetime_exemption
} else {
Decimal::ZERO
};
let estate_tax = taxable_estate * input.estate_tax_rate;
let gst_exposure = if taxable_estate > Decimal::ZERO {
taxable_estate
} else {
Decimal::ZERO
};
let total_to_heirs = gifts_capped + grat_remainder + estate_after_grat - estate_tax;
let effective_transfer_rate = if input.estate_value > Decimal::ZERO {
total_to_heirs / input.estate_value
} else {
Decimal::ZERO
};
let mut strategies = Vec::new();
strategies.push(TransferStrategy {
name: "Annual Exclusion Gifting".into(),
amount_transferred: gifts_capped,
tax_savings: gifts_capped * input.estate_tax_rate,
description: format!(
"Gift ${} per year to {} beneficiaries for {} years",
input.annual_exclusion, input.num_beneficiaries, input.transfer_years
),
});
strategies.push(TransferStrategy {
name: "Zeroed-Out GRAT".into(),
amount_transferred: grat_remainder,
tax_savings: grat_tax_savings,
description: format!(
"GRAT with {} annuity rate, transferring excess growth over {:.2}% hurdle",
input.grat_annuity_rate,
input.section_7520_rate * dec!(100)
),
});
let idgt_benefit = grat_contribution * input.estate_tax_rate * dec!(0.30);
strategies.push(TransferStrategy {
name: "IDGT (Intentional Defective Grantor Trust)".into(),
amount_transferred: grat_contribution,
tax_savings: idgt_benefit,
description: "Grantor pays income tax on trust assets, allowing tax-free growth".into(),
});
strategies.push(TransferStrategy {
name: "Lifetime Exemption".into(),
amount_transferred: input.lifetime_exemption.min(input.estate_value),
tax_savings: input.lifetime_exemption.min(input.estate_value) * input.estate_tax_rate,
description: format!("Use ${} lifetime exemption", input.lifetime_exemption),
});
strategies.sort_by(|a, b| b.tax_savings.cmp(&a.tax_savings));
Ok(WealthTransferOutput {
annual_gifts_total,
total_annual_gifts: gifts_capped,
remaining_estate: remaining,
grat_remainder,
grat_tax_savings,
taxable_estate,
estate_tax,
gst_exposure,
effective_transfer_rate,
strategies,
})
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
fn base_input() -> WealthTransferInput {
WealthTransferInput {
estate_value: dec!(50_000_000),
annual_exclusion: dec!(18_000),
lifetime_exemption: dec!(13_610_000),
estate_tax_rate: dec!(0.40),
gst_tax_rate: dec!(0.40),
num_beneficiaries: 4,
transfer_years: 10,
asset_growth_rate: dec!(0.07),
grantor_trust_assets: dec!(10_000_000),
grat_annuity_rate: dec!(0.20),
section_7520_rate: dec!(0.052),
}
}
#[test]
fn test_annual_gifts_total() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert_eq!(out.annual_gifts_total, dec!(72_000));
}
#[test]
fn test_total_annual_gifts() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert_eq!(out.total_annual_gifts, dec!(720_000));
}
#[test]
fn test_remaining_estate_grows() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert!(out.remaining_estate > dec!(50_000_000));
}
#[test]
fn test_grat_remainder_positive() {
let mut inp = base_input();
inp.grat_annuity_rate = dec!(0.10);
let out = analyze_wealth_transfer(&inp).unwrap();
assert!(out.grat_remainder > Decimal::ZERO);
}
#[test]
fn test_grat_tax_savings() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert_eq!(out.grat_tax_savings, out.grat_remainder * dec!(0.40));
}
#[test]
fn test_taxable_estate_positive_large_estate() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert!(out.taxable_estate > Decimal::ZERO);
}
#[test]
fn test_estate_tax_calculation() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert_eq!(out.estate_tax, out.taxable_estate * dec!(0.40));
}
#[test]
fn test_small_estate_no_tax() {
let mut inp = base_input();
inp.estate_value = dec!(5_000_000);
inp.grantor_trust_assets = Decimal::ZERO;
let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.estate_tax, Decimal::ZERO);
}
#[test]
fn test_exemption_covers_all() {
let mut inp = base_input();
inp.estate_value = dec!(10_000_000);
inp.grantor_trust_assets = Decimal::ZERO;
inp.asset_growth_rate = Decimal::ZERO;
let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.estate_tax, Decimal::ZERO);
}
#[test]
fn test_gst_exposure_mirrors_taxable() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert_eq!(out.gst_exposure, out.taxable_estate);
}
#[test]
fn test_effective_transfer_rate_range() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert!(out.effective_transfer_rate > Decimal::ZERO);
}
#[test]
fn test_strategies_count() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
assert_eq!(out.strategies.len(), 4);
}
#[test]
fn test_strategies_sorted_by_tax_savings() {
let out = analyze_wealth_transfer(&base_input()).unwrap();
for i in 1..out.strategies.len() {
assert!(out.strategies[i - 1].tax_savings >= out.strategies[i].tax_savings);
}
}
#[test]
fn test_grat_zero_growth() {
let mut inp = base_input();
inp.asset_growth_rate = Decimal::ZERO;
let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.grat_remainder, Decimal::ZERO);
}
#[test]
fn test_grat_growth_below_hurdle() {
let mut inp = base_input();
inp.asset_growth_rate = dec!(0.03); let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.grat_remainder, Decimal::ZERO);
}
#[test]
fn test_many_beneficiaries() {
let mut inp = base_input();
inp.num_beneficiaries = 20;
let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.annual_gifts_total, dec!(360_000));
}
#[test]
fn test_invalid_estate_value() {
let mut inp = base_input();
inp.estate_value = Decimal::ZERO;
assert!(analyze_wealth_transfer(&inp).is_err());
}
#[test]
fn test_invalid_num_beneficiaries() {
let mut inp = base_input();
inp.num_beneficiaries = 0;
assert!(analyze_wealth_transfer(&inp).is_err());
}
#[test]
fn test_invalid_transfer_years() {
let mut inp = base_input();
inp.transfer_years = 0;
assert!(analyze_wealth_transfer(&inp).is_err());
}
#[test]
fn test_invalid_tax_rate() {
let mut inp = base_input();
inp.estate_tax_rate = dec!(1.5);
assert!(analyze_wealth_transfer(&inp).is_err());
}
#[test]
fn test_one_year_horizon() {
let mut inp = base_input();
inp.transfer_years = 1;
let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.annual_gifts_total, dec!(72_000));
assert_eq!(out.total_annual_gifts, dec!(72_000));
}
#[test]
fn test_no_grantor_trust() {
let mut inp = base_input();
inp.grantor_trust_assets = Decimal::ZERO;
let out = analyze_wealth_transfer(&inp).unwrap();
assert_eq!(out.grat_remainder, Decimal::ZERO);
assert_eq!(out.grat_tax_savings, Decimal::ZERO);
}
}