diff --git a/contracts/vault/src/lib.rs b/contracts/vault/src/lib.rs index 0c76e5f2..eba93c7b 100644 --- a/contracts/vault/src/lib.rs +++ b/contracts/vault/src/lib.rs @@ -22,7 +22,7 @@ pub struct VaultMeta { /// Eliminates duplication and ensures audit clarity. #[contracttype] pub enum StorageKey { - Meta, + MetaKey, Admin, UsdcToken, Settlement, @@ -79,6 +79,12 @@ impl CalloraVault { let max_d = max_deduct.unwrap_or(DEFAULT_MAX_DEDUCT); assert!(max_d > 0, "max_deduct must be positive"); assert!(min_d <= max_d, "min_deposit cannot exceed max_deduct"); + if let Some(ac) = &authorized_caller { + assert!( + ac != &env.current_contract_address(), + "authorized_caller cannot be vault address" + ); + } if balance > 0 { let onchain_usdc_balance = token::Client::new(&env, &usdc_token).balance(&env.current_contract_address()); @@ -124,7 +130,7 @@ impl CalloraVault { // Ensure Admin fallback exists if !inst.has(&StorageKey::Admin) { - if let Some(meta) = inst.get::<_, VaultMeta>(&StorageKey::Meta) { + if let Some(meta) = inst.get::<_, VaultMeta>(&StorageKey::MetaKey) { inst.set(&StorageKey::Admin, &meta.owner); } } @@ -196,7 +202,7 @@ impl CalloraVault { pub fn get_meta(env: Env) -> VaultMeta { env.storage() .instance() - .get(&StorageKey::Meta) + .get(&StorageKey::MetaKey) .unwrap_or_else(|| panic!("vault not initialized")) } @@ -236,6 +242,16 @@ impl CalloraVault { .set(&StorageKey::DepositorList, &Vec::
::new(&env)); } + fn require_authorized_deduct_caller(env: Env, caller: &Address) { + let meta = Self::get_meta(env.clone()); + let owner = meta.owner.clone(); + let auth = match meta.authorized_caller { + Some(ac) => *caller == ac || *caller == owner, + None => *caller == owner, + }; + assert!(auth, "unauthorized caller"); + } + pub fn get_allowed_depositors(env: Env) -> Vec
{ env.storage() .instance() @@ -243,14 +259,14 @@ impl CalloraVault { .unwrap_or(Vec::new(&env)) } - pub fn set_authorized_caller(env: Env, caller: Address) { + 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); env.events().publish( - (Symbol::new(&env, "set_auth_caller"), meta.owner.clone()), - caller, + (Symbol::new(&env, "set_authorized_caller"), meta.owner.clone()), + (old_authorized_caller, caller), ); } @@ -350,7 +366,7 @@ impl CalloraVault { .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"),), (amount, meta.balance)); meta.balance @@ -367,12 +383,8 @@ impl CalloraVault { .unwrap_or(DEFAULT_MAX_DEDUCT); assert!(amount <= max_d, "deduct amount exceeds max_deduct"); let meta = Self::get_meta(env.clone()); - let auth = match &meta.authorized_caller { - Some(ac) => caller == *ac || caller == meta.owner, - None => true, - }; - assert!(auth, "unauthorized caller"); assert!(meta.balance >= amount, "insufficient balance"); + Self::require_authorized_deduct_caller(env.clone(), &caller); let mut meta = Self::get_meta(env.clone()); meta.balance = meta .balance @@ -410,11 +422,7 @@ impl CalloraVault { .get(&StorageKey::MaxDeduct) .unwrap_or(DEFAULT_MAX_DEDUCT); let mut meta = Self::get_meta(env.clone()); - let auth = match &meta.authorized_caller { - Some(ac) => caller == *ac || caller == meta.owner, - None => true, - }; - assert!(auth, "unauthorized caller"); + Self::require_authorized_deduct_caller(env.clone(), &caller); let mut running = meta.balance; let mut total: i128 = 0; for item in items.iter() { diff --git a/contracts/vault/src/test.rs b/contracts/vault/src/test.rs index cac06ad8..666b526a 100644 --- a/contracts/vault/src/test.rs +++ b/contracts/vault/src/test.rs @@ -734,19 +734,20 @@ fn set_authorized_caller_sets_and_emits_event() { fund_vault(&usdc_admin, &vault_address, 200); client.init(&owner, &usdc, &Some(200), &None, &None, &None, &None); - client.set_authorized_caller(&new_caller); + client.set_authorized_caller(&Some(new_caller.clone())); let events = env.events().all(); - let ev = events.last().expect("expected set_auth_caller event"); + let ev = events.last().expect("expected set_authorized_caller event"); assert_eq!(ev.1.len(), 2); let topic0: Symbol = ev.1.get(0).unwrap().into_val(&env); let topic1: Address = ev.1.get(1).unwrap().into_val(&env); - assert_eq!(topic0, Symbol::new(&env, "set_auth_caller")); + assert_eq!(topic0, Symbol::new(&env, "set_authorized_caller")); assert_eq!(topic1, owner); - let data: Address = ev.2.into_val(&env); - assert_eq!(data, new_caller); + let (old, now): (Option
, Option
) = ev.2.into_val(&env); + assert_eq!(old, None); + assert_eq!(now, Some(new_caller.clone())); let remaining = client.deduct(&new_caller, &50, &None); assert_eq!(remaining, 150); @@ -762,7 +763,7 @@ fn deduct_reduces_balance() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 300); - client.init(&owner, &usdc, &Some(300), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(300), &Some(caller.clone()), &None, &None, &None); let returned = client.deduct(&caller, &50, &None); assert_eq!(returned, 250); @@ -779,7 +780,7 @@ fn deduct_with_request_id() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 1000); - client.init(&owner, &usdc, &Some(1000), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(1000), &Some(caller.clone()), &None, &None, &None); let remaining = client.deduct(&caller, &100, &Some(Symbol::new(&env, "req123"))); assert_eq!(remaining, 900); @@ -795,7 +796,7 @@ fn deduct_insufficient_balance_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 10); - client.init(&owner, &usdc, &Some(10), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(10), &Some(caller.clone()), &None, &None, &None); let result = client.try_deduct(&caller, &100, &None); assert!(result.is_err(), "expected error for insufficient balance"); @@ -811,7 +812,7 @@ fn deduct_exact_balance_succeeds() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 75); - client.init(&owner, &usdc, &Some(75), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(75), &Some(caller.clone()), &None, &None, &None); let remaining = client.deduct(&caller, &75, &None); assert_eq!(remaining, 0); @@ -828,7 +829,7 @@ fn deduct_event_contains_request_id() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 500); - client.init(&owner, &usdc, &Some(500), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(500), &Some(caller.clone()), &None, &None, &None); let request_id = Symbol::new(&env, "api_call_42"); client.deduct(&caller, &150, &Some(request_id.clone())); @@ -860,7 +861,7 @@ 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), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(300), &Some(caller.clone()), &None, &None, &None); client.deduct(&caller, &100, &None); let events = env.events().all(); @@ -890,7 +891,7 @@ fn deduct_zero_panics() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 500); - client.init(&owner, &usdc, &Some(500), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(500), &Some(caller.clone()), &None, &None, &None); client.deduct(&caller, &0, &None); } @@ -905,7 +906,7 @@ fn deduct_negative_panics() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); client.deduct(&caller, &-50, &None); } @@ -920,7 +921,7 @@ fn deduct_exceeds_balance_panics() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 50); - client.init(&owner, &usdc, &Some(50), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(50), &Some(caller.clone()), &None, &None, &None); client.deduct(&caller, &100, &None); } @@ -934,7 +935,7 @@ fn balance_unchanged_after_failed_deduct() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); let _ = client.try_deduct(&caller, &200, &None); assert_eq!(client.balance(), 100); @@ -954,7 +955,7 @@ fn batch_deduct_multiple_items() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 1000); - client.init(&owner, &usdc, &Some(1000), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(1000), &Some(caller.clone()), &None, &None, &None); let items = soroban_sdk::vec![ &env, @@ -987,7 +988,7 @@ fn batch_deduct_events_contain_request_ids() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 1000); - client.init(&owner, &usdc, &Some(1000), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(1000), &Some(caller.clone()), &None, &None, &None); let rid_a = Symbol::new(&env, "batch_a"); let rid_b = Symbol::new(&env, "batch_b"); @@ -1031,7 +1032,7 @@ fn batch_deduct_insufficient_balance_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); let items = soroban_sdk::vec![ &env, @@ -1061,7 +1062,7 @@ fn batch_deduct_empty_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &None, &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); @@ -1078,7 +1079,7 @@ fn batch_deduct_zero_amount_fails() { env.mock_all_auths(); fund_vault(&usdc_admin, &vault_address, 100); - client.init(&owner, &usdc, &Some(100), &None, &None, &None, &None); + client.init(&owner, &usdc, &Some(100), &Some(caller.clone()), &None, &None, &None); let items = soroban_sdk::vec![ &env,