Skip to content
Merged
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
38 changes: 36 additions & 2 deletions contracts/sharpy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod test;
use soroban_sdk::{contract, contractimpl, symbol_short, token, Address, Env, Map, Symbol, Vec};
use types::{
AuditEntry, CreateInvoiceParams, DisputeState, Invoice, InvoiceOptions, InvoicePayment,
InvoiceStatus, Payment, SplitRule, SubscriptionParams,
InvoiceStats, InvoiceStatus, Payment, SplitRule, SubscriptionParams,
};

fn admin_key() -> Symbol { symbol_short!("admin") }
Expand Down Expand Up @@ -138,6 +138,16 @@ impl SharpyContract {
assert!(amt > 0, "amounts must be positive");
}

// Validate percentage split rules do not exceed 10000 bps total
if !options.split_rules.is_empty() {
let total_bps: u32 = options.split_rules.iter().map(|r| match r {
SplitRule::Percentage(bps) => bps,
SplitRule::Tiered(_, bps) => bps,
SplitRule::Fixed(_) => 0,
}).sum();
assert!(total_bps <= 10_000u32, "split rules exceed 100% (10000 bps)");
}

let id = bump_counter(&env);
let invoice = build_invoice(
&env, creator.clone(), recipients, amounts, tokens, deadline,
Expand Down Expand Up @@ -350,7 +360,7 @@ impl SharpyContract {
invoice.completion_time = Some(env.ledger().timestamp());
save_invoice(&env, invoice_id, &invoice);
append_audit(&env, invoice_id, symbol_short!("resolve"), &resolver);
events::invoice_refunded(&env, invoice_id);
events::invoice_refunded(&env, invoice_id, invoice.funded, invoice.recipients.len(), &invoice.creator);
}

events::dispute_resolved(&env, invoice_id, &resolver, release);
Expand Down Expand Up @@ -498,6 +508,30 @@ impl SharpyContract {
env.storage().persistent().get(&next_invoice_key(invoice_id))
}

pub fn get_invoice_stats(env: Env, invoice_id: u64) -> InvoiceStats {
let invoice = load_invoice(&env, invoice_id);
let total: i128 = invoice.amounts.iter().sum();
let payment_count = invoice.payments.len();
let mut unique: Vec<Address> = Vec::new(&env);
for p in invoice.payments.iter() {
if !unique.contains(&p.payer) {
unique.push_back(p.payer.clone());
}
}
let completion_bps = if total > 0 {
(invoice.funded as u128 * 10_000u128 / total as u128) as u32
} else {
0
};
InvoiceStats {
funded: invoice.funded,
total,
payment_count,
unique_payers: unique.len(),
completion_bps,
}
}

pub fn get_escrow_state(env: Env, invoice_id: u64) -> Option<DisputeState> {
env.storage().persistent().get(&escrow_state_key(invoice_id))
}
Expand Down
28 changes: 19 additions & 9 deletions contracts/sharpy/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,6 @@ pub struct InvoiceOptions {
pub arbitrator: Option<Address>,
}

#[contracttype]
#[derive(Clone, Debug)]
pub struct CreateInvoiceParams {
pub recipients: Vec<Address>,
pub amounts: Vec<i128>,
pub tokens: Vec<Address>,
pub deadline: u64,
}

#[contracttype]
#[derive(Clone, Debug)]
pub struct Invoice {
Expand All @@ -121,3 +112,22 @@ pub struct Invoice {
pub auto_resolve_rules: Vec<ResolveRule>,
pub arbitrator: Option<Address>,
}

#[contracttype]
#[derive(Clone, Debug)]
pub struct CreateInvoiceParams {
pub recipients: Vec<Address>,
pub amounts: Vec<i128>,
pub tokens: Vec<Address>,
pub deadline: u64,
}

#[contracttype]
#[derive(Clone, Debug)]
pub struct InvoiceStats {
pub funded: i128,
pub total: i128,
pub payment_count: u32,
pub unique_payers: u32,
pub completion_bps: u32,
}
Loading