use serde::{Deserialize, Serialize};
use crate::types::VaultId;
use crate::wallet::Address;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct VaultState {
pub vault_id: VaultId,
pub leader: Address,
pub total_shares: u128,
pub nav_usd_cents: i64,
pub paused: bool,
pub management_fee_bps: u16,
pub withdrawal_lock_ms: u64,
pub created_at_ms: u64,
pub follower_count: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum VaultKind {
#[default]
User,
Metaliquidity,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CreateVault {
pub name: String,
pub lock_period_secs: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent: Option<VaultId>,
#[serde(default)]
pub kind: VaultKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct VaultTransfer {
pub vault_id: VaultId,
pub deposit: bool,
pub amount: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct VaultModify {
pub vault_id: VaultId,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_lock_period_secs: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_management_fee_bps: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_paused: Option<bool>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct VaultWithdraw {
pub vault_id: VaultId,
pub shares: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vault_state_round_trips() {
let v = VaultState {
vault_id: VaultId(42),
leader: Address::ZERO,
total_shares: 1_000_000,
nav_usd_cents: 5_000_000,
paused: false,
management_fee_bps: 1000,
withdrawal_lock_ms: 345_600_000,
created_at_ms: 1_700_000_000_000,
follower_count: 5,
};
let j = serde_json::to_string(&v).unwrap();
let dec: VaultState = serde_json::from_str(&j).unwrap();
assert_eq!(v, dec);
}
#[test]
fn vault_state_uses_snake_case_on_wire() {
let v = VaultState {
vault_id: VaultId(42),
leader: Address::ZERO,
total_shares: 0,
nav_usd_cents: 0,
paused: false,
management_fee_bps: 1000,
withdrawal_lock_ms: 345_600_000,
created_at_ms: 0,
follower_count: 0,
};
let j = serde_json::to_value(&v).unwrap();
for forbidden in [
"vaultId",
"navUsdCents",
"managementFeeBps",
"withdrawalLockMs",
"createdAtMs",
"followerCount",
"totalShares",
] {
assert!(j.get(forbidden).is_none(), "wire leak: {forbidden}");
}
}
#[test]
fn create_vault_defaults_kind_and_omits_parent() {
let c = CreateVault {
name: "mlp".into(),
lock_period_secs: 604_800,
parent: None,
kind: VaultKind::default(),
};
let j = serde_json::to_value(&c).unwrap();
assert!(j.get("parent").is_none());
assert_eq!(j["kind"], serde_json::json!("User"));
let c2 = CreateVault {
kind: VaultKind::Metaliquidity,
..c
};
assert_eq!(
serde_json::to_value(&c2).unwrap()["kind"],
serde_json::json!("Metaliquidity")
);
}
#[test]
fn vault_withdraw_shares_is_string() {
let w = VaultWithdraw {
vault_id: VaultId(4),
shares: "250".into(),
};
let j = serde_json::to_value(&w).unwrap();
assert!(
j["shares"].is_string(),
"shares must be a decimal JSON string"
);
assert_eq!(w, serde_json::from_value(j).unwrap());
}
}