Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contracts/split/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const SHARD_COUNT: u64 = 8;

mod error;
mod events;
mod stats;
mod types;
mod validation;

#[cfg(test)]
mod test;
Expand Down
117 changes: 117 additions & 0 deletions contracts/split/src/stats.rs
Original file line number Diff line number Diff line change
@@ -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");
}
}
62 changes: 62 additions & 0 deletions contracts/split/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
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]
Expand Down Expand Up @@ -870,3 +906,29 @@ pub struct InvoiceParams {
pub recipients: Vec<Address>,
// ... 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);
}
}
}
31 changes: 31 additions & 0 deletions contracts/split/src/validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use soroban_sdk::{Address, Env};

pub fn assert_unique_recipients(env: &Env, recipients: &soroban_sdk::Vec<Address>) -> 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::<Address>::new(&env);

let result = assert_unique_recipients(&env, &recipients);
assert!(result.is_ok(), "empty recipient list should pass uniqueness check");
}
}