diff --git a/COMEBACKHERE-contracts/contracts/compliance/src/lib.rs b/COMEBACKHERE-contracts/contracts/compliance/src/lib.rs index 7b10e8d..53bc477 100644 --- a/COMEBACKHERE-contracts/contracts/compliance/src/lib.rs +++ b/COMEBACKHERE-contracts/contracts/compliance/src/lib.rs @@ -32,7 +32,10 @@ pub enum DataKey { pub struct ComplianceContract; fn is_paused(e: &Env) -> bool { - e.storage().instance().get(&DataKey::Paused).unwrap_or(false) + e.storage() + .instance() + .get(&DataKey::Paused) + .unwrap_or(false) } fn check_not_paused(e: &Env) -> Result<(), ContractError> { @@ -100,21 +103,16 @@ impl ComplianceContract { ) -> Result<(), ContractError> { check_not_paused(&e)?; admin.require_auth(); - e.storage() - .instance() - .set(&DataKey::Status(addr.clone()), &AddressStatus::AllowedUntil(until)); - e.events().publish( - (Symbol::new(&e, "address_allowed_until"),), - (addr, until), + e.storage().instance().set( + &DataKey::Status(addr.clone()), + &AddressStatus::AllowedUntil(until), ); + e.events() + .publish((Symbol::new(&e, "address_allowed_until"),), (addr, until)); Ok(()) } - pub fn transfer_admin( - e: Env, - admin: Address, - new_admin: Address, - ) -> Result<(), ContractError> { + pub fn transfer_admin(e: Env, admin: Address, new_admin: Address) -> Result<(), ContractError> { check_not_paused(&e)?; admin.require_auth(); e.storage().instance().set(&DataKey::Admin, &new_admin); @@ -133,13 +131,9 @@ impl ComplianceContract { return Err(ContractError::Unauthorized); } e.storage().instance().set(&DataKey::Admin, &new_admin); - e.storage() - .instance() - .remove(&DataKey::PendingAdmin); - e.events().publish( - (Symbol::new(&e, "accept_admin"),), - &new_admin, - ); + e.storage().instance().remove(&DataKey::PendingAdmin); + e.events() + .publish((Symbol::new(&e, "accept_admin"),), &new_admin); Ok(()) } @@ -160,7 +154,7 @@ impl ComplianceContract { .instance() .remove(&DataKey::Status(addr.clone())); e.events() - .publish((Symbol::new(&e, "address_cleared"),), addr); + .publish((Symbol::new(&e, "address_cleared"),), (addr, status)); Ok(()) } diff --git a/COMEBACKHERE-contracts/contracts/invoice/src/lib.rs b/COMEBACKHERE-contracts/contracts/invoice/src/lib.rs index 1a55be7..1ce090e 100644 --- a/COMEBACKHERE-contracts/contracts/invoice/src/lib.rs +++ b/COMEBACKHERE-contracts/contracts/invoice/src/lib.rs @@ -114,18 +114,14 @@ impl InvoiceContract { if env.storage().persistent().has(&DataKey::Admin) { return Err(ContractError::AlreadyInitialized); } - env.storage() - .persistent() - .set(&DataKey::Admin, &admin); + env.storage().persistent().set(&DataKey::Admin, &admin); env.storage() .persistent() .set(&DataKey::GraceWindow, &86400u64); env.storage() .persistent() .set(&DataKey::InvoiceCount, &0u64); - env.storage() - .persistent() - .set(&DataKey::Paused, &false); + env.storage().persistent().set(&DataKey::Paused, &false); Ok(()) } @@ -169,16 +165,14 @@ impl InvoiceContract { if env.storage().persistent().has(&nonce_key) { return Err(ContractError::DuplicateNonce); } - env.storage() - .persistent() - .set(&nonce_key, &true); + env.storage().persistent().set(&nonce_key, &true); let mut count: u64 = env .storage() .persistent() .get(&DataKey::InvoiceCount) .unwrap_or(0); - count += 1; + count = count.checked_add(1).ok_or(ContractError::Overflow)?; env.storage() .persistent() .set(&DataKey::InvoiceCount, &count); @@ -440,7 +434,11 @@ impl InvoiceContract { .persistent() .get(&DataKey::GraceWindow) .unwrap(); - if env.ledger().timestamp() < invoice.created_at + grace_window { + let release_at = invoice + .created_at + .checked_add(grace_window) + .ok_or(ContractError::Overflow)?; + if env.ledger().timestamp() < release_at { return Err(ContractError::GraceWindowNotExpired); } invoice.status = InvoiceStatus::Released; @@ -509,9 +507,7 @@ impl InvoiceContract { /// /// Returns `None` if `set_treasury` has not been called yet. pub fn get_treasury(env: Env) -> Option
{ - env.storage() - .persistent() - .get(&DataKey::TreasuryContract) + env.storage().persistent().get(&DataKey::TreasuryContract) } /// Raises a dispute on an invoice via a cross-contract call to the treasury. @@ -588,9 +584,7 @@ impl InvoiceContract { /// Emits `contract_paused()` on success. pub fn pause(env: Env, caller: Address) -> Result<(), ContractError> { check_admin(&env, &caller)?; - env.storage() - .persistent() - .set(&DataKey::Paused, &true); + env.storage().persistent().set(&DataKey::Paused, &true); events::contract_paused(&env); Ok(()) } @@ -607,9 +601,7 @@ impl InvoiceContract { /// Emits `contract_unpaused()` on success. pub fn unpause(env: Env, caller: Address) -> Result<(), ContractError> { check_admin(&env, &caller)?; - env.storage() - .persistent() - .set(&DataKey::Paused, &false); + env.storage().persistent().set(&DataKey::Paused, &false); events::contract_unpaused(&env); Ok(()) } @@ -734,6 +726,45 @@ mod tests { assert_eq!(result, Err(Ok(ContractError::ContractPaused))); } + #[test] + fn test_create_invoice_near_u64_max_count_returns_overflow() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let contract_id = env.register(InvoiceContract, ()); + let client = InvoiceContractClient::new(&env, &contract_id); + client.initialize(&admin); + env.storage() + .persistent() + .set(&DataKey::InvoiceCount, &u64::MAX); + + let merchant = Address::generate(&env); + let customer = Address::generate(&env); + let token = Address::generate(&env); + let result = client.try_create_invoice(&merchant, &customer, &1000i128, &token, &5000, &1); + assert_eq!(result, Err(Ok(ContractError::Overflow))); + } + + #[test] + fn test_release_escrow_overflow_returns_error() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let contract_id = env.register(InvoiceContract, ()); + let client = InvoiceContractClient::new(&env, &contract_id); + client.initialize(&admin); + env.ledger().with_mut(|li| li.timestamp = u64::MAX - 1); + + let merchant = Address::generate(&env); + let customer = Address::generate(&env); + let token = Address::generate(&env); + let invoice_id = client.create_invoice(&merchant, &customer, &1000i128, &token, &5000, &1); + client.mark_paids(&soroban_sdk::vec![&env, invoice_id]); + client.request_refund(&invoice_id, &customer); + let result = client.try_release_escrow(&invoice_id, &merchant); + assert_eq!(result, Err(Ok(ContractError::Overflow))); + } + #[test] fn test_unpause_restores_create_invoice() { let env = Env::default(); @@ -843,7 +874,13 @@ mod tests { invoice_client.initialize(&admin); invoice_client.set_treasury(&admin, &treasury_cid); env.ledger().with_mut(|li| li.timestamp = ts); - (env, invoice_cid, treasury_cid, admin, Address::generate(&env)) + ( + env, + invoice_cid, + treasury_cid, + admin, + Address::generate(&env), + ) } #[test] @@ -860,7 +897,10 @@ mod tests { invoice_client.raise_dispute(&invoice_id, &1u64, &merchant, &1u32); - assert!(treasury_client.was_held(&1u64), "settlement should be on hold"); + assert!( + treasury_client.was_held(&1u64), + "settlement should be on hold" + ); } #[test] @@ -878,7 +918,10 @@ mod tests { // invoice_created + dispute_raised = at least 2 events let all_events = env.events().all(); - assert!(all_events.len() >= 2, "dispute_raised event should be emitted"); + assert!( + all_events.len() >= 2, + "dispute_raised event should be emitted" + ); } #[test] diff --git a/COMEBACKHERE-contracts/contracts/treasury/src/integration_settlement_multisig.rs b/COMEBACKHERE-contracts/contracts/treasury/src/integration_settlement_multisig.rs index a075081..3ef8388 100644 --- a/COMEBACKHERE-contracts/contracts/treasury/src/integration_settlement_multisig.rs +++ b/COMEBACKHERE-contracts/contracts/treasury/src/integration_settlement_multisig.rs @@ -43,14 +43,22 @@ fn test_multisig_propose_collect_2_of_3_execute() { client.approve_settlement(&signer_a, &settlement_id); let pending_mid = client.get_pending_settlements(&None, &None); - assert_eq!(pending_mid.len(), 1, "settlement should still be pending after 1-of-2 approvals"); + assert_eq!( + pending_mid.len(), + 1, + "settlement should still be pending after 1-of-2 approvals" + ); client.approve_settlement(&signer_b, &settlement_id); client.execute_settlement(&signer_a, &settlement_id, &token); let pending_after = client.get_pending_settlements(&None, &None); - assert_eq!(pending_after.len(), 0, "settlement should no longer be pending after execution"); + assert_eq!( + pending_after.len(), + 0, + "settlement should no longer be pending after execution" + ); } #[test] @@ -79,7 +87,10 @@ fn test_single_signer_insufficient_for_threshold_2() { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { client.execute_settlement(&signer_a, &settlement_id, &token); })); - assert!(result.is_err(), "execution should fail with insufficient approvals"); + assert!( + result.is_err(), + "execution should fail with insufficient approvals" + ); } #[test] @@ -109,7 +120,11 @@ fn test_weighted_signers_reach_threshold() { client.execute_settlement(&signer_a, &settlement_id, &token); let pending = client.get_pending_settlements(&None, &None); - assert_eq!(pending.len(), 0, "weighted signer with weight=2 should meet threshold=2"); + assert_eq!( + pending.len(), + 0, + "weighted signer with weight=2 should meet threshold=2" + ); } #[test] @@ -123,11 +138,7 @@ fn test_multiple_settlements_independent_approvals() { let token = Address::generate(&env); let merchant = Address::generate(&env); - let signers = vec![ - &env, - (signer_a.clone(), 1u64), - (signer_b.clone(), 1u64), - ]; + let signers = vec![&env, (signer_a.clone(), 1u64), (signer_b.clone(), 1u64)]; client.initialize(&signers, &2u64, &admin); let s1 = client.propose_settlement(&signer_a, &token, &1_000_000u64, &merchant); @@ -144,7 +155,10 @@ fn test_multiple_settlements_independent_approvals() { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { client.execute_settlement(&signer_a, &s2, &token); })); - assert!(result.is_err(), "s2 should not be executable without approvals"); + assert!( + result.is_err(), + "s2 should not be executable without approvals" + ); } #[test] @@ -158,11 +172,7 @@ fn test_execute_settlement_verifies_token_transfer_setup() { let token = Address::generate(&env); let merchant = Address::generate(&env); - let signers = vec![ - &env, - (signer_a.clone(), 1u64), - (signer_b.clone(), 1u64), - ]; + let signers = vec![&env, (signer_a.clone(), 1u64), (signer_b.clone(), 1u64)]; client.initialize(&signers, &2u64, &admin); let settlement_id = client.propose_settlement(&signer_a, &token, &10_000_000u64, &merchant); @@ -173,7 +183,11 @@ fn test_execute_settlement_verifies_token_transfer_setup() { client.execute_settlement(&signer_a, &settlement_id, &token); let pending = client.get_pending_settlements(&None, &None); - assert_eq!(pending.len(), 0, "executed settlement should no longer appear in pending list"); + assert_eq!( + pending.len(), + 0, + "executed settlement should no longer appear in pending list" + ); } use proptest::prelude::*; diff --git a/COMEBACKHERE-contracts/contracts/treasury/src/lib.rs b/COMEBACKHERE-contracts/contracts/treasury/src/lib.rs index 93de47b..8586c1c 100644 --- a/COMEBACKHERE-contracts/contracts/treasury/src/lib.rs +++ b/COMEBACKHERE-contracts/contracts/treasury/src/lib.rs @@ -80,7 +80,10 @@ pub enum DataKey { } fn is_paused(e: &Env) -> bool { - e.storage().instance().get(&DataKey::Paused).unwrap_or(false) + e.storage() + .instance() + .get(&DataKey::Paused) + .unwrap_or(false) } fn check_not_paused(e: &Env) -> Result<(), TreasuryError> { @@ -310,13 +313,21 @@ impl TreasuryContract { e: Env, offset: Option