diff --git a/opsr/contracts/escrow/lib.rs b/opsr/contracts/escrow/lib.rs new file mode 100644 index 00000000..88f6f4bf --- /dev/null +++ b/opsr/contracts/escrow/lib.rs @@ -0,0 +1,87 @@ +#![no_std] +use soroban_sdk::{contract, contractimpl, contracttype, token, Address, Env}; + +#[contracttype] +pub struct TreasuryStats { + pub total_funded: i128, + pub total_released: i128, + pub total_refunded: i128, + pub total_fees_collected: i128, +} + +const FUNDED: &str = "FUNDED"; +const RELEASED: &str = "RELEASED"; +const REFUNDED: &str = "REFUNDED"; +const FEES: &str = "FEES"; + +#[contract] +pub struct EscrowContract; + +#[contractimpl] +impl EscrowContract { + pub fn fund(env: Env, token: Address, from: Address, amount: i128) { + from.require_auth(); + token::Client::new(&env, &token).transfer(&from, &env.current_contract_address(), &amount); + let v: i128 = env.storage().instance().get(&FUNDED).unwrap_or(0); + env.storage().instance().set(&FUNDED, &(v + amount)); + } + + pub fn release(env: Env, token: Address, to: Address, amount: i128, fee: i128) { + let net = amount - fee; + token::Client::new(&env, &token).transfer(&env.current_contract_address(), &to, &net); + let r: i128 = env.storage().instance().get(&RELEASED).unwrap_or(0); + env.storage().instance().set(&RELEASED, &(r + net)); + let f: i128 = env.storage().instance().get(&FEES).unwrap_or(0); + env.storage().instance().set(&FEES, &(f + fee)); + } + + pub fn refund(env: Env, token: Address, to: Address, amount: i128) { + token::Client::new(&env, &token).transfer(&env.current_contract_address(), &to, &amount); + let v: i128 = env.storage().instance().get(&REFUNDED).unwrap_or(0); + env.storage().instance().set(&REFUNDED, &(v + amount)); + } + + pub fn get_treasury_stats(env: Env) -> TreasuryStats { + TreasuryStats { + total_funded: env.storage().instance().get(&FUNDED).unwrap_or(0), + total_released: env.storage().instance().get(&RELEASED).unwrap_or(0), + total_refunded: env.storage().instance().get(&REFUNDED).unwrap_or(0), + total_fees_collected: env.storage().instance().get(&FEES).unwrap_or(0), + } + } + + pub fn get_current_balance(env: Env, token: Address) -> i128 { + token::Client::new(&env, &token).balance(&env.current_contract_address()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::testutils::Address as _; + use soroban_sdk::{token::StellarAssetClient, Env}; + + #[test] + fn test_lifecycle_counters() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, EscrowContract); + let client = EscrowContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let token_id = env.register_stellar_asset_contract(admin.clone()); + let asset = StellarAssetClient::new(&env, &token_id); + asset.mint(&user, &1000); + + client.fund(&token_id, &user, &1000); + client.release(&token_id, &user, &800, &50); + client.refund(&token_id, &user, &150); + + let stats = client.get_treasury_stats(); + assert_eq!(stats.total_funded, 1000); + assert_eq!(stats.total_released, 750); + assert_eq!(stats.total_refunded, 150); + assert_eq!(stats.total_fees_collected, 50); + } +} diff --git a/opsr/contracts/reputation/lib.rs b/opsr/contracts/reputation/lib.rs new file mode 100644 index 00000000..8e2f23cc --- /dev/null +++ b/opsr/contracts/reputation/lib.rs @@ -0,0 +1,100 @@ +#![no_std] +use soroban_sdk::{contract, contractimpl, contracttype, vec, Address, Env, Vec}; + +#[contracttype] +#[derive(Clone)] +pub struct CarrierScore { + pub wallet: Address, + pub score: u32, +} + +const INDEX: &str = "INDEX"; + +#[contract] +pub struct ReputationContract; + +#[contractimpl] +impl ReputationContract { + pub fn set_score(env: Env, wallet: Address, score: u32) { + let mut index: Vec = env.storage().instance().get(&INDEX).unwrap_or(vec![&env]); + + // remove existing entry for this wallet + let mut new_index: Vec = vec![&env]; + for i in 0..index.len() { + let entry = index.get(i).unwrap(); + if entry.wallet != wallet { + new_index.push_back(entry); + } + } + + // insert in sorted position (descending) + let mut inserted = false; + let mut sorted: Vec = vec![&env]; + for i in 0..new_index.len() { + let entry = new_index.get(i).unwrap(); + if !inserted && score >= entry.score { + sorted.push_back(CarrierScore { wallet: wallet.clone(), score }); + inserted = true; + } + sorted.push_back(entry); + } + if !inserted { + sorted.push_back(CarrierScore { wallet, score }); + } + + env.storage().instance().set(&INDEX, &sorted); + } + + pub fn get_top_carriers(env: Env, limit: u32) -> Vec<(Address, u32)> { + let index: Vec = env.storage().instance().get(&INDEX).unwrap_or(vec![&env]); + let mut result: Vec<(Address, u32)> = vec![&env]; + let count = index.len().min(limit); + for i in 0..count { + let entry = index.get(i).unwrap(); + result.push_back((entry.wallet, entry.score)); + } + result + } + + pub fn get_carrier_rank(env: Env, wallet: Address) -> u32 { + let index: Vec = env.storage().instance().get(&INDEX).unwrap_or(vec![&env]); + for i in 0..index.len() { + if index.get(i).unwrap().wallet == wallet { + return i + 1; + } + } + 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::testutils::Address as _; + use soroban_sdk::Env; + + #[test] + fn test_ranking_and_ties() { + let env = Env::default(); + let id = env.register_contract(None, ReputationContract); + let client = ReputationContractClient::new(&env, &id); + + let a = Address::generate(&env); + let b = Address::generate(&env); + let c = Address::generate(&env); + + client.set_score(&a, &80); + client.set_score(&b, &95); + client.set_score(&c, &80); + + let top = client.get_top_carriers(&3); + assert_eq!(top.get(0).unwrap().1, 95); // b is first + + assert_eq!(client.get_carrier_rank(&b), 1); + + // update a's score to top + client.set_score(&a, &100); + assert_eq!(client.get_carrier_rank(&a), 1); + assert_eq!(client.get_carrier_rank(&b), 2); + } +}