diff --git a/Cargo.toml b/Cargo.toml index 381f7743..973f004a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,15 @@ [workspace] resolver = "2" -members = ["contracts/vault", "contracts/settlement", "contracts/revenue_pool"] +members = [ + "contracts/vault", + "contracts/revenue_pool", + "contracts/settlement", +] +default-members = [ + "contracts/vault", + "contracts/revenue_pool", + "contracts/settlement", +] [workspace.dependencies] soroban-sdk = "22" diff --git a/contracts/revenue_pool/src/lib.rs b/contracts/revenue_pool/src/lib.rs index 3d62c703..463fea4e 100644 --- a/contracts/revenue_pool/src/lib.rs +++ b/contracts/revenue_pool/src/lib.rs @@ -315,7 +315,7 @@ impl RevenuePool { let mut total_amount: i128 = 0; for payment in payments.iter() { let (_, amount) = payment; - + // Validate each amount is strictly positive if amount <= 0 { panic!("{}", ERR_AMOUNT_NOT_POSITIVE); @@ -346,7 +346,7 @@ impl RevenuePool { let (to, amount) = payment; Self::validate_recipient(&to, &contract_address); usdc.transfer(&contract_address, &to, &amount); - + // Emit event for this leg of the batch env.events() .publish((Symbol::new(&env, "batch_distribute"), to), amount); diff --git a/contracts/vault/src/lib.rs b/contracts/vault/src/lib.rs index bd2dd0c7..7c67e89e 100644 --- a/contracts/vault/src/lib.rs +++ b/contracts/vault/src/lib.rs @@ -82,7 +82,7 @@ impl CalloraVault { ) -> VaultMeta { owner.require_auth(); let inst = env.storage().instance(); - if inst.has(&StorageKey::Meta) { + if inst.has(&StorageKey::MetaKey) { panic!("vault already initialized"); } assert!( @@ -122,7 +122,7 @@ impl CalloraVault { authorized_caller, min_deposit: min_d, }; - inst.set(&StorageKey::Meta, &meta); + inst.set(&StorageKey::MetaKey, &meta); inst.set(&StorageKey::UsdcToken, &usdc_token); inst.set(&StorageKey::Admin, &owner); if let Some(p) = revenue_pool { @@ -231,7 +231,7 @@ impl CalloraVault { pub fn set_allowed_depositor(env: Env, caller: Address, depositor: Option
) { caller.require_auth(); - Self::require_owner(env.clone(), caller); + Self::require_owner(env.clone(), caller.clone()); match depositor { Some(d) => { @@ -285,10 +285,14 @@ impl CalloraVault { pub fn set_authorized_caller(env: Env, caller: Option
) { let mut meta = Self::get_meta(env.clone()); meta.owner.require_auth(); - meta.authorized_caller = Some(caller.clone()); - env.storage().instance().set(&StorageKey::Meta, &meta); + let old_authorized_caller = meta.authorized_caller.clone(); + meta.authorized_caller = caller.clone(); + env.storage().instance().set(&StorageKey::MetaKey, &meta); env.events().publish( - (Symbol::new(&env, "set_authorized_caller"), meta.owner.clone()), + ( + Symbol::new(&env, "set_authorized_caller"), + meta.owner.clone(), + ), (old_authorized_caller, caller), ); } @@ -374,7 +378,7 @@ impl CalloraVault { caller.require_auth(); assert!(amount > 0, "amount must be positive"); assert!( - Self::is_authorized_depositor(env.clone(), depositor.clone()), + Self::is_authorized_depositor(env.clone(), caller.clone()), "unauthorized: only owner or allowed depositor can deposit" ); let meta = Self::get_meta(env.clone()); @@ -390,13 +394,13 @@ impl CalloraVault { .get(&StorageKey::UsdcToken) .expect("vault not initialized"); let usdc = token::Client::new(&env, &usdc_addr); - usdc.transfer(&depositor, &env.current_contract_address(), &amount); + usdc.transfer(&caller, &env.current_contract_address(), &amount); let mut meta = Self::get_meta(env.clone()); meta.balance = meta .balance .checked_add(amount) .unwrap_or_else(|| panic!("balance overflow")); - env.storage().instance().set(&StorageKey::Meta, &meta); + env.storage().instance().set(&StorageKey::MetaKey, &meta); env.events().publish( (Symbol::new(&env, "deposit"), caller), (amount, meta.balance), @@ -425,23 +429,18 @@ impl CalloraVault { Self::require_not_paused(env.clone()); caller.require_auth(); assert!(amount > 0, "amount must be positive"); - let max_d: i128 = env - .storage() - .instance() - .get(&StorageKey::MaxDeduct) - .unwrap_or(DEFAULT_MAX_DEDUCT); + Self::require_authorized_deduct_caller(env.clone(), &caller); + let max_d = Self::get_max_deduct(env.clone()); assert!(amount <= max_d, "deduct amount exceeds max_deduct"); let meta = Self::get_meta(env.clone()); - let auth = caller == meta.owner || meta.authorized_caller.as_ref() == Some(&caller); - assert!(auth, "unauthorized caller"); assert!(meta.balance >= amount, "insufficient balance"); - Self::require_authorized_deduct_caller(env.clone(), &caller); + let settlement = Self::require_settlement(&env); let mut meta = Self::get_meta(env.clone()); meta.balance = meta .balance .checked_sub(amount) .unwrap_or_else(|| panic!("balance underflow")); - env.storage().instance().set(&StorageKey::Meta, &meta); + env.storage().instance().set(&StorageKey::MetaKey, &meta); let ut: Address = env .storage() .instance() @@ -474,16 +473,12 @@ impl CalloraVault { pub fn batch_deduct(env: Env, caller: Address, items: Vec) -> i128 { Self::require_not_paused(env.clone()); caller.require_auth(); + Self::require_authorized_deduct_caller(env.clone(), &caller); let n = items.len(); assert!(n > 0, "batch_deduct requires at least one item"); assert!(n <= MAX_BATCH_SIZE, "batch too large"); - let max_d: i128 = env - .storage() - .instance() - .get(&StorageKey::MaxDeduct) - .unwrap_or(DEFAULT_MAX_DEDUCT); + let max_d = Self::get_max_deduct(env.clone()); let mut meta = Self::get_meta(env.clone()); - Self::require_authorized_deduct_caller(env.clone(), &caller); let mut running = meta.balance; let mut total: i128 = 0; for item in items.iter() { @@ -497,6 +492,7 @@ impl CalloraVault { .checked_add(item.amount) .unwrap_or_else(|| panic!("total overflow")); } + let settlement = Self::require_settlement(&env); meta.balance = running; @@ -508,7 +504,14 @@ impl CalloraVault { Self::transfer_funds(&env, &ut, &settlement, total); meta.balance = running; - env.storage().instance().set(&StorageKey::Meta, &meta); + env.storage().instance().set(&StorageKey::MetaKey, &meta); + for item in items.iter() { + let rid = item.request_id.unwrap_or(Symbol::new(&env, "")); + env.events().publish( + (Symbol::new(&env, "deduct"), caller.clone(), rid), + (item.amount, meta.balance), + ); + } meta.balance } @@ -546,7 +549,7 @@ impl CalloraVault { let mut meta = Self::get_meta(env.clone()); let old = meta.owner.clone(); meta.owner = pending; - env.storage().instance().set(&StorageKey::Meta, &meta); + env.storage().instance().set(&StorageKey::MetaKey, &meta); env.storage().instance().remove(&StorageKey::PendingOwner); env.events().publish( (Symbol::new(&env, "ownership_accepted"), old, meta.owner), @@ -570,13 +573,10 @@ impl CalloraVault { .balance .checked_sub(amount) .unwrap_or_else(|| panic!("balance underflow")); - env.storage().instance().set(&StorageKey::Meta, &meta); + env.storage().instance().set(&StorageKey::MetaKey, &meta); env.events().publish( (Symbol::new(&env, "withdraw"), meta.owner.clone()), - WithdrawEventData { - amount, - new_balance: meta.balance, - }, + (amount, meta.balance), ); meta.balance } @@ -597,13 +597,10 @@ impl CalloraVault { .balance .checked_sub(amount) .unwrap_or_else(|| panic!("balance underflow")); - env.storage().instance().set(&StorageKey::Meta, &meta); + env.storage().instance().set(&StorageKey::MetaKey, &meta); env.events().publish( (Symbol::new(&env, "withdraw_to"), meta.owner.clone(), to), - WithdrawEventData { - amount, - new_balance: meta.balance, - }, + (amount, meta.balance), ); meta.balance } @@ -784,13 +781,6 @@ impl CalloraVault { .unwrap_or_else(|| panic!("settlement address not set")) } - fn get_max_deduct(env: Env) -> i128 { - env.storage() - .instance() - .get(&StorageKey::MaxDeduct) - .unwrap_or(DEFAULT_MAX_DEDUCT) - } - fn require_not_paused(env: Env) { assert!(!Self::is_paused(env), "vault is paused"); } @@ -807,6 +797,22 @@ impl CalloraVault { "unauthorized: caller is not admin or owner" ); } + + pub fn add_address(env: Env, caller: Address, depositor: Address) { + Self::set_allowed_depositor(env.clone(), caller.clone(), Some(depositor.clone())); + env.events() + .publish((Symbol::new(&env, "allowlist_add"), caller, depositor), ()); + } + + pub fn get_allowlist(env: Env) -> Vec
{ + Self::get_allowed_depositors(env) + } + + pub fn clear_all(env: Env, caller: Address) { + Self::clear_allowed_depositors(env.clone(), caller.clone()); + env.events() + .publish((Symbol::new(&env, "allowlist_clear"), caller), ()); + } } #[cfg(test)] diff --git a/contracts/vault/src/test.rs b/contracts/vault/src/test.rs index 80c92d24..d28f839b 100644 --- a/contracts/vault/src/test.rs +++ b/contracts/vault/src/test.rs @@ -1118,7 +1118,17 @@ fn deduct_reduces_balance() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 300); - client.init(&owner, &usdc, &Some(300), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(300), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); let returned = client.deduct(&owner, &50, &None); assert_eq!(returned, 250); @@ -1134,7 +1144,17 @@ fn deduct_with_request_id() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 1000); - client.init(&owner, &usdc, &Some(1000), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(1000), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); let remaining = client.deduct(&owner, &100, &Some(Symbol::new(&env, "req123"))); assert_eq!(remaining, 900); @@ -1149,7 +1169,15 @@ fn deduct_insufficient_balance_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 10); - client.init(&owner, &usdc, &Some(10), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(10), + &Some(caller.clone()), + &None, + &None, + &None, + ); let result = client.try_deduct(&owner, &100, &None); assert!(result.is_err(), "expected error for insufficient balance"); @@ -1164,7 +1192,17 @@ fn deduct_exact_balance_succeeds() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 75); - client.init(&owner, &usdc, &Some(75), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(75), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); let remaining = client.deduct(&owner, &75, &None); assert_eq!(remaining, 0); @@ -1180,7 +1218,17 @@ fn deduct_event_contains_request_id() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 500); - client.init(&owner, &usdc, &Some(500), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(500), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); let request_id = Symbol::new(&env, "api_call_42"); client.deduct(&owner, &150, &Some(request_id.clone())); @@ -1274,7 +1322,17 @@ fn deduct_event_no_request_id_uses_empty_symbol() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 300); - client.init(&owner, &usdc, &Some(300), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(300), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); client.deduct(&caller, &100, &None); let events = env.events().all(); @@ -1304,7 +1362,15 @@ fn deduct_zero_panics() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 500); - client.init(&owner, &usdc, &Some(500), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(500), + &Some(caller.clone()), + &None, + &None, + &None, + ); client.deduct(&caller, &0, &None); } @@ -1319,7 +1385,15 @@ fn deduct_negative_panics() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(100), + &Some(caller.clone()), + &None, + &None, + &None, + ); client.deduct(&caller, &-50, &None); } @@ -1333,7 +1407,15 @@ fn deduct_exceeds_balance_panics() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 50); - client.init(&owner, &usdc, &Some(50), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(50), + &Some(caller.clone()), + &None, + &None, + &None, + ); client.deduct(&caller, &100, &None); } @@ -1346,7 +1428,15 @@ fn balance_unchanged_after_failed_deduct() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(100), + &Some(caller.clone()), + &None, + &None, + &None, + ); let _ = client.try_deduct(&owner, &200, &None); assert_eq!(client.balance(), 100); @@ -1366,7 +1456,17 @@ fn batch_deduct_multiple_items() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 1000); - client.init(&owner, &usdc, &Some(1000), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(1000), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); let items = soroban_sdk::vec![ &env, @@ -1399,7 +1499,17 @@ fn batch_deduct_events_contain_request_ids() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 1000); - client.init(&owner, &usdc, &Some(1000), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(1000), + &Some(caller.clone()), + &None, + &None, + &None, + ); + let settlement = Address::generate(&env); + client.set_settlement(&owner, &settlement); let rid_a = Symbol::new(&env, "batch_a"); let rid_b = Symbol::new(&env, "batch_b"); @@ -1455,7 +1565,15 @@ fn batch_deduct_insufficient_balance_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(100), + &Some(caller.clone()), + &None, + &None, + &None, + ); let items = soroban_sdk::vec![ &env, @@ -1485,7 +1603,15 @@ fn batch_deduct_empty_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(100), + &Some(caller.clone()), + &None, + &None, + &None, + ); let items: soroban_sdk::Vec = soroban_sdk::vec![&env]; let result = client.try_batch_deduct(&caller, &items); @@ -1502,7 +1628,15 @@ fn batch_deduct_zero_amount_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); + client.init( + &owner, + &usdc, + &Some(100), + &Some(caller.clone()), + &None, + &None, + &None, + ); let items = soroban_sdk::vec![ &env, @@ -2188,7 +2322,15 @@ fn vault_full_lifecycle() { // Init with 500 balance, min_deposit = 10 fund_vault(&usdc_admin, &vault_address, 500); - let meta = client.init(&owner, &usdc, &Some(500), &None, &Some(10), &None, &None); + let meta = client.init( + &owner, + &usdc, + &Some(500), + &Some(caller.clone()), + &Some(10), + &None, + &None, + ); assert_eq!(meta.balance, 500); assert_eq!(meta.owner, owner); assert_eq!(client.balance(), 500); @@ -2962,7 +3104,7 @@ fn test_set_authorized_caller() { env.mock_all_auths(); client.init(&owner, &usdc, &None, &None, &None, &None, &None); - client.set_authorized_caller(&auth_caller); + client.set_authorized_caller(&Some(auth_caller.clone())); let meta = client.get_meta(); assert_eq!(meta.authorized_caller, Some(auth_caller)); } @@ -3750,9 +3892,6 @@ fn withdraw_emits_event() { } }) .expect("expected withdraw event"); - let payload: WithdrawEventData = ev.2.into_val(&env); - assert_eq!(payload.amount, 100); - assert_eq!(payload.new_balance, 200); let (amt, bal): (i128, i128) = ev.2.into_val(&env); assert_eq!(amt, 100); assert_eq!(bal, 200); @@ -3779,9 +3918,6 @@ fn withdraw_to_emits_event() { } }) .expect("expected withdraw_to event"); - let payload: WithdrawEventData = ev.2.into_val(&env); - assert_eq!(payload.amount, 150); - assert_eq!(payload.new_balance, 150); let (amt, bal): (i128, i128) = ev.2.into_val(&env); assert_eq!(amt, 150); assert_eq!(bal, 150); @@ -3884,7 +4020,7 @@ mod fuzz { let (usdc_addr, usdc_client, usdc_admin) = create_usdc(&env, &owner); let (vault_addr, client) = create_vault(&env); - let _settlement = Address::generate(&env); + let settlement = Address::generate(&env); // Pre-fund vault so initial_balance is valid. usdc_admin.mint(&vault_addr, &initial); client.init( @@ -3931,7 +4067,9 @@ mod fuzz { if paused { // deposit must fail while paused assert!(client.try_deposit(&owner, &amount).is_err()); - } else if let (Some(new_sim), Some(new_token_sim)) = (sim.checked_add(amount), token_sim.checked_add(amount)) { + } else if let (Some(new_sim), Some(new_token_sim)) = + (sim.checked_add(amount), token_sim.checked_add(amount)) + { // mint amount to owner to avoid insufficient balance on large fuzz tests usdc_admin.mint(&owner, &amount); sim = new_sim; @@ -3963,11 +4101,6 @@ mod fuzz { let mut batch_total: i128 = 0; let mut valid = true; for _ in 0..n { - let amt: i128 = rng.gen_range(1..=max_deduct_val); - items.push_back(DeductItem { - amount: amt, - request_id: None, - }); let amt: i128 = rng.gen_range(1..=op_cap); batch_total = match batch_total.checked_add(amt) { Some(v) => v, @@ -4079,7 +4212,7 @@ mod fuzz { &owner, &usdc_addr, &Some(300), - &None, + &Some(caller.clone()), &None, &None, &Some(200), @@ -4128,7 +4261,7 @@ mod fuzz { &owner, &usdc_addr, &Some(10_000), - &None, + &Some(caller.clone()), &None, &None, &Some(max_d), diff --git a/coverage/cobertura.xml b/coverage/cobertura.xml index ab114dad..c4ad4231 100644 --- a/coverage/cobertura.xml +++ b/coverage/cobertura.xml @@ -1 +1 @@ -/home/chukwuemekadr/Documents/Drips/Wave4/Callora-Contracts \ No newline at end of file +/home/merlinthewhiz/Documents/Callora-Contracts \ No newline at end of file diff --git a/coverage/tarpaulin-report.html b/coverage/tarpaulin-report.html index 52411463..60e8cea5 100644 --- a/coverage/tarpaulin-report.html +++ b/coverage/tarpaulin-report.html @@ -193,8 +193,8 @@