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
34 changes: 14 additions & 20 deletions COMEBACKHERE-contracts/contracts/compliance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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);
Expand All @@ -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(())
}

Expand All @@ -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(())
}

Expand Down
89 changes: 66 additions & 23 deletions COMEBACKHERE-contracts/contracts/invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -509,9 +507,7 @@ impl InvoiceContract {
///
/// Returns `None` if `set_treasury` has not been called yet.
pub fn get_treasury(env: Env) -> Option<Address> {
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.
Expand Down Expand Up @@ -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(())
}
Expand All @@ -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(())
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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);
Expand All @@ -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]
Expand All @@ -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);
Expand All @@ -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::*;
Expand Down
Loading