diff --git a/contracts/split/src/lib.rs b/contracts/split/src/lib.rs index bc917d7..2613cfd 100644 --- a/contracts/split/src/lib.rs +++ b/contracts/split/src/lib.rs @@ -7,7 +7,9 @@ const SHARD_COUNT: u64 = 8; mod error; mod events; +mod stats; mod types; +mod validation; #[cfg(test)] mod test; diff --git a/contracts/split/src/stats.rs b/contracts/split/src/stats.rs new file mode 100644 index 0000000..24b1f56 --- /dev/null +++ b/contracts/split/src/stats.rs @@ -0,0 +1,117 @@ +use soroban_sdk::{Address, Env, Symbol, symbol_short, Vec}; + +pub fn get_stats(env: &Env) -> (u64, i128, i128, i128) { + let total_invoices = env + .storage() + .persistent() + .get(&total_invoices_key()) + .unwrap_or(0u64); + let total_volume = env + .storage() + .persistent() + .get(&total_volume_key()) + .unwrap_or(0i128); + let total_released = env + .storage() + .persistent() + .get(&total_released_key()) + .unwrap_or(0i128); + let total_refunded = env + .storage() + .persistent() + .get(&total_refunded_key()) + .unwrap_or(0i128); + (total_invoices, total_volume, total_released, total_refunded) +} + +pub fn increment_invoice_count(env: &Env) { + let count: u64 = env + .storage() + .persistent() + .get(&total_invoices_key()) + .unwrap_or(0u64); + env.storage() + .persistent() + .set(&total_invoices_key(), &count.checked_add(1).expect("overflow")); +} + +pub fn increment_volume(env: &Env, amount: i128) { + let volume: i128 = env + .storage() + .persistent() + .get(&total_volume_key()) + .unwrap_or(0i128); + env.storage() + .persistent() + .set(&total_volume_key(), &volume.checked_add(amount).expect("overflow")); +} + +pub fn increment_released(env: &Env, amount: i128) { + let released: i128 = env + .storage() + .persistent() + .get(&total_released_key()) + .unwrap_or(0i128); + env.storage() + .persistent() + .set(&total_released_key(), &released.checked_add(amount).expect("overflow")); +} + +pub fn increment_refunded(env: &Env, amount: i128) { + let refunded: i128 = env + .storage() + .persistent() + .get(&total_refunded_key()) + .unwrap_or(0i128); + env.storage() + .persistent() + .set(&total_refunded_key(), &refunded.checked_add(amount).expect("overflow")); +} + +fn total_invoices_key() -> Symbol { + symbol_short!("tot_inv") +} + +fn total_volume_key() -> Symbol { + symbol_short!("tot_vol") +} + +fn total_released_key() -> Symbol { + symbol_short!("tot_rel") +} + +fn total_refunded_key() -> Symbol { + symbol_short!("tot_ref") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_stats_defaults_to_zero() { + let env = Env::default(); + + let stats = get_stats(&env); + assert_eq!(stats.0, 0, "total_invoices should default to 0"); + assert_eq!(stats.1, 0, "total_volume should default to 0"); + assert_eq!(stats.2, 0, "total_released should default to 0"); + assert_eq!(stats.3, 0, "total_refunded should default to 0"); + } + + #[test] + fn record_invoice_created_increments_counter() { + let env = Env::default(); + + let stats_before = get_stats(&env); + assert_eq!(stats_before.0, 0, "invoice count should start at 0"); + + increment_invoice_count(&env); + + let stats_after = get_stats(&env); + assert_eq!(stats_after.0, 1, "invoice count should increment to 1"); + assert_eq!(stats_before.1, stats_after.1, "volume should remain unchanged"); + assert_eq!(stats_before.2, stats_after.2, "released should remain unchanged"); + assert_eq!(stats_before.3, stats_after.3, "refunded should remain unchanged"); + } +} diff --git a/contracts/split/src/types.rs b/contracts/split/src/types.rs index b3f87b3..e45011c 100644 --- a/contracts/split/src/types.rs +++ b/contracts/split/src/types.rs @@ -69,6 +69,42 @@ pub enum InvoiceStatus { Released, Refunded, Cancelled, + Expired, + Disputed, + PartiallyReleased, + Finalised, + Deleted, +} + +impl InvoiceStatus { + pub fn to_u8(&self) -> u8 { + match self { + InvoiceStatus::Pending => 0, + InvoiceStatus::Released => 1, + InvoiceStatus::Refunded => 2, + InvoiceStatus::Cancelled => 3, + InvoiceStatus::Expired => 4, + InvoiceStatus::Disputed => 5, + InvoiceStatus::PartiallyReleased => 6, + InvoiceStatus::Finalised => 7, + InvoiceStatus::Deleted => 8, + } + } + + pub fn from_u8(value: u8) -> Option { + match value { + 0 => Some(InvoiceStatus::Pending), + 1 => Some(InvoiceStatus::Released), + 2 => Some(InvoiceStatus::Refunded), + 3 => Some(InvoiceStatus::Cancelled), + 4 => Some(InvoiceStatus::Expired), + 5 => Some(InvoiceStatus::Disputed), + 6 => Some(InvoiceStatus::PartiallyReleased), + 7 => Some(InvoiceStatus::Finalised), + 8 => Some(InvoiceStatus::Deleted), + _ => None, + } + } } #[contracttype] @@ -870,3 +906,29 @@ pub struct InvoiceParams { pub recipients: Vec
, // ... add all other fields here ... } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn invoice_status_u8_round_trip() { + let variants = vec![ + InvoiceStatus::Pending, + InvoiceStatus::Released, + InvoiceStatus::Refunded, + InvoiceStatus::Cancelled, + InvoiceStatus::Expired, + InvoiceStatus::Disputed, + InvoiceStatus::PartiallyReleased, + InvoiceStatus::Finalised, + InvoiceStatus::Deleted, + ]; + + for status in variants { + let u8_val = status.to_u8(); + let recovered = InvoiceStatus::from_u8(u8_val); + assert_eq!(recovered, Some(status.clone()), "Round-trip failed for {:?}", status); + } + } +} diff --git a/contracts/split/src/validation.rs b/contracts/split/src/validation.rs new file mode 100644 index 0000000..81bc1a7 --- /dev/null +++ b/contracts/split/src/validation.rs @@ -0,0 +1,31 @@ +use soroban_sdk::{Address, Env}; + +pub fn assert_unique_recipients(env: &Env, recipients: &soroban_sdk::Vec
) -> Result<(), String> { + if recipients.is_empty() { + return Ok(()); + } + + for i in 0..recipients.len() { + for j in (i + 1)..recipients.len() { + if recipients.get(i as u32) == recipients.get(j as u32) { + return Err("duplicate recipient found".to_string()); + } + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_recipient_list_passes_uniqueness_check() { + let env = Env::default(); + let recipients = soroban_sdk::Vec::
::new(&env); + + let result = assert_unique_recipients(&env, &recipients); + assert!(result.is_ok(), "empty recipient list should pass uniqueness check"); + } +}