diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml index 9d53b29..f9df19b 100644 --- a/.github/workflows/contracts-ci.yml +++ b/.github/workflows/contracts-ci.yml @@ -21,6 +21,7 @@ jobs: uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown + components: rustfmt, clippy - name: Cache cargo uses: actions/cache@v4 @@ -33,6 +34,9 @@ jobs: restore-keys: | ${{ runner.os }}-cargo- + - name: Check formatting + run: cargo fmt --all --check + # creditline-contract embeds the four dependency WASMs via # soroban_sdk::contractimport!, so those WASMs must exist before # creditline compiles. Build them individually first. @@ -49,6 +53,11 @@ jobs: cargo build --locked --target wasm32-unknown-unknown --release \ -p vouching-contract + # Lint after the dependency WASMs exist so creditline-contract's + # contractimport! clients resolve during the native clippy compile. + - name: Clippy (lint gate) + run: cargo clippy --workspace --all-targets --locked -- -D warnings + - name: Build all contracts run: | cargo build --locked --target wasm32-unknown-unknown --release diff --git a/contracts/creditline-contract/src/events.rs b/contracts/creditline-contract/src/events.rs index 6fa3458..edb9b1d 100644 --- a/contracts/creditline-contract/src/events.rs +++ b/contracts/creditline-contract/src/events.rs @@ -41,7 +41,6 @@ pub fn emit_loan_funded( ); } - /// Emit a loan created event pub fn emit_loan_created( env: &Env, @@ -180,19 +179,10 @@ pub fn emit_loan_in_grace_period( } /// Emitted when a late fee is collected as part of `repay_installment`. -pub fn emit_late_fee_paid( - env: &Env, - loan_id: u64, - installment_index: u32, - fee_amount: i128, -) { +pub fn emit_late_fee_paid(env: &Env, loan_id: u64, installment_index: u32, fee_amount: i128) { env.events().publish( (LATE_FEE_PAID, loan_id), - ( - installment_index, - fee_amount, - env.ledger().timestamp(), - ), + (installment_index, fee_amount, env.ledger().timestamp()), ); } @@ -217,12 +207,7 @@ pub fn emit_upgrade_proposed( } /// Emit an event when a cross-contract reputation score update fails. -pub fn emit_score_update_failed( - env: &Env, - borrower: &Address, - is_increase: bool, - amount: u32, -) { +pub fn emit_score_update_failed(env: &Env, borrower: &Address, is_increase: bool, amount: u32) { env.events().publish( (Symbol::new(env, "ScoreUpdateFailed"), borrower.clone()), (is_increase, amount, env.ledger().timestamp()), diff --git a/contracts/creditline-contract/src/lib.rs b/contracts/creditline-contract/src/lib.rs index 9bea113..571cfba 100644 --- a/contracts/creditline-contract/src/lib.rs +++ b/contracts/creditline-contract/src/lib.rs @@ -107,7 +107,14 @@ impl CreditLineContract { storage::write_loan(&env, &loan); let pool_contribution = safe_math::sub_i128(total_amount, guarantee_amount)?; - Self::fund_loan_from_pool(&env, &user, &vendor, guarantee_amount, pool_contribution, true); + Self::fund_loan_from_pool( + &env, + &user, + &vendor, + guarantee_amount, + pool_contribution, + true, + ); events::emit_loan_created( &env, @@ -658,10 +665,7 @@ impl CreditLineContract { &Symbol::new(&env, "decrease_score"), (updater, loan.borrower.clone(), penalty).into_val(&env), ); - let call_succeeded = match res { - Ok(Ok(())) => true, - _ => false, - }; + let call_succeeded = matches!(res, Ok(Ok(()))); if !call_succeeded { events::emit_score_update_failed(&env, &loan.borrower, false, penalty); panic_with_error!(&env, CreditLineError::ReputationCallFailed); @@ -1175,10 +1179,7 @@ impl CreditLineContract { &Symbol::new(env, "increase_score"), (updater, borrower, score_increase).into_val(env), ); - let call_succeeded = match res { - Ok(Ok(())) => true, - _ => false, - }; + let call_succeeded = matches!(res, Ok(Ok(()))); if !call_succeeded { events::emit_score_update_failed(env, borrower, true, score_increase); panic_with_error!(env, CreditLineError::ReputationCallFailed); @@ -1240,8 +1241,7 @@ impl CreditLineContract { loan.late_fees_outstanding = safe_math::sub_i128(loan.late_fees_outstanding, late_fee_paid)?; - loan.interest_outstanding = - safe_math::sub_i128(loan.interest_outstanding, interest_paid)?; + loan.interest_outstanding = safe_math::sub_i128(loan.interest_outstanding, interest_paid)?; loan.service_fee_outstanding = safe_math::sub_i128(loan.service_fee_outstanding, service_fee_paid)?; loan.principal_outstanding = diff --git a/contracts/creditline-contract/src/storage.rs b/contracts/creditline-contract/src/storage.rs index e118c4f..89900ed 100644 --- a/contracts/creditline-contract/src/storage.rs +++ b/contracts/creditline-contract/src/storage.rs @@ -90,6 +90,7 @@ pub fn get_user_loan_count(env: &Env, borrower: &Address) -> Result bool { - env.storage().instance().get(&symbol_short!("FUND")).unwrap_or(false) + env.storage() + .instance() + .get(&symbol_short!("FUND")) + .unwrap_or(false) } pub fn was_receive_repayment_called(env: Env) -> bool { - env.storage().instance().get(&symbol_short!("REPRD")).unwrap_or(false) + env.storage() + .instance() + .get(&symbol_short!("REPRD")) + .unwrap_or(false) } pub fn was_receive_guarantee_called(env: Env) -> bool { - env.storage().instance().get(&symbol_short!("GUARD")).unwrap_or(false) - } pub fn get_receive_guarantee_amount(env: Env) -> i128 { + env.storage() + .instance() + .get(&symbol_short!("GUARD")) + .unwrap_or(false) + } + pub fn get_receive_guarantee_amount(env: Env) -> i128 { env.storage() .instance() .get(&symbol_short!("GUAMT")) @@ -576,17 +589,28 @@ fn test_admin_upgrade_succeeds_and_bumps_version() { let vendor_registry_id = env.register(vendor_registry_contract::VendorRegistryContract, ()); let lp_id = env.register(MockLiquidityPool, ()); let token_admin = Address::generate(&env); - let token_id = env.register_stellar_asset_contract_v2(token_admin.clone()).address(); + let token_id = env + .register_stellar_asset_contract_v2(token_admin.clone()) + .address(); client.initialize(&admin, &rep_id, &vendor_registry_id, &lp_id, &token_id); - let wasm_hash = env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice(&env, include_bytes!("../../../contracts/test-fixtures/contract.wasm"))); + let wasm_hash = env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); client.propose_upgrade(&wasm_hash); env.ledger().set_timestamp(86_401); client.execute_upgrade(&wasm_hash); use soroban_sdk::IntoVal; - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = env.events().all(); + let events: soroban_sdk::Vec<( + soroban_sdk::Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = env.events().all(); let mut upgraded_new: Option = None; for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(&env); @@ -596,7 +620,11 @@ fn test_admin_upgrade_succeeds_and_bumps_version() { break; } } - assert_eq!(upgraded_new, Some(2u32), "CONTRACTUPGRADED new_version should be 2"); + assert_eq!( + upgraded_new, + Some(2u32), + "CONTRACTUPGRADED new_version should be 2" + ); } #[test] @@ -627,20 +655,6 @@ fn test_creditline_upgrade_with_wrong_hash_fails() { ctx.client.execute_upgrade(&wasm_hash2); } -fn assert_event(env: &Env, expected: soroban_sdk::Symbol) { - use soroban_sdk::IntoVal; - - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = env.events().all(); - for event in events.iter() { - let topics = event.1.clone(); - let topic: soroban_sdk::Symbol = topics.get_unchecked(0).into_val(env); - if topic == expected { - return; - } - } - panic!("expected event was not emitted"); -} - #[test] fn test_set_liquidity_pool() { let env = Env::default(); @@ -2143,7 +2157,12 @@ fn test_insufficient_liquidity_rejects_loan_creation() { let _ = env.try_invoke_contract::<(), soroban_sdk::Error>( &vendor_registry_id, &Symbol::new(&env, "register_vendor"), - (&admin, vendor.clone(), SorobanString::from_str(&env, "Test Vendor")).into_val(&env), + ( + &admin, + vendor.clone(), + SorobanString::from_str(&env, "Test Vendor"), + ) + .into_val(&env), ); let _ = env.try_invoke_contract::<(), soroban_sdk::Error>( &vendor_registry_id, @@ -2626,8 +2645,7 @@ impl RealIntegrationCtx { let vendor_name = SorobanString::from_str(&self.env, name); self.vendor_registry .register_vendor(&self.admin, vendor, &vendor_name); - self.vendor_registry - .approve_vendor(&self.admin, vendor); + self.vendor_registry.approve_vendor(&self.admin, vendor); } fn single_installment( @@ -3247,9 +3265,7 @@ fn test_reputation_call_failure_reverts_default() { .create_loan(&user, &vendor, &1_000, &200, &schedule, &LoanType::Standard); // Advance time past grace period - t.env - .ledger() - .set_timestamp(now + 1_000 + 86_400 * 7 + 1); + t.env.ledger().set_timestamp(now + 1_000 + 86_400 * 7 + 1); // Revoke creditline updater permission on reputation contract t.reputation.set_updater(&t.admin, &t.creditline_id, &false); @@ -3903,12 +3919,14 @@ fn test_waterfall_order_late_fees_paid_first() { let loan_id = t.create_default_loan(&user, &vendor); // Manually set late fees to simulate an overdue loan without time travel. - let mut loan = t.client.get_loan(&loan_id); + let loan = t.client.get_loan(&loan_id); assert_eq!(loan.late_fees_outstanding, 0); // We'll use apply_late_fees after advancing time. let due_date = loan.repayment_schedule.get(0).unwrap().due_date; - t.env.ledger().set_timestamp(due_date + crate::types::SECONDS_PER_DAY); // 1 day overdue + t.env + .ledger() + .set_timestamp(due_date + crate::types::SECONDS_PER_DAY); // 1 day overdue t.client.apply_late_fees(&loan_id); let loan = t.client.get_loan(&loan_id); @@ -3923,8 +3941,14 @@ fn test_waterfall_order_late_fees_paid_first() { let loan = t.client.get_loan(&loan_id); // Late fees, interest, and service fee must be zero after payment. assert_eq!(loan.late_fees_outstanding, 0, "late fees paid first"); - assert_eq!(loan.interest_outstanding, 0, "interest paid after late fees"); - assert_eq!(loan.service_fee_outstanding, 0, "service fee paid after interest"); + assert_eq!( + loan.interest_outstanding, 0, + "interest paid after late fees" + ); + assert_eq!( + loan.service_fee_outstanding, 0, + "service fee paid after interest" + ); // Only principal remains. assert_eq!(loan.principal_outstanding, DEFAULT_PRINCIPAL); assert_bucket_invariant(&t, loan_id); @@ -3954,7 +3978,8 @@ fn test_waterfall_bucket_invariant_after_repay_loan() { // Full payment. let loan = t.client.get_loan(&loan_id); t.mint(&user, loan.remaining_balance); - t.client.repay_loan(&user, &loan_id, &loan.remaining_balance); + t.client + .repay_loan(&user, &loan_id, &loan.remaining_balance); assert_bucket_invariant(&t, loan_id); let loan = t.client.get_loan(&loan_id); @@ -4017,7 +4042,8 @@ fn test_repay_installment_full_payment_sets_paid_and_buckets_zeroed() { t.mint(&user, DEFAULT_TOTAL_DUE); t.env.ledger().set_timestamp(5000); - t.client.repay_installment(&user, &loan_id, &0, &DEFAULT_TOTAL_DUE); + t.client + .repay_installment(&user, &loan_id, &0, &DEFAULT_TOTAL_DUE); let loan = t.client.get_loan(&loan_id); assert_eq!(loan.status, LoanStatus::Paid); @@ -4055,7 +4081,8 @@ fn test_waterfall_bucket_invariant_after_repay_installment() { let loan = t.client.get_loan(&loan_id); t.mint(&user, loan.remaining_balance); t.env.ledger().set_timestamp(25000); - t.client.repay_installment(&user, &loan_id, &2, &loan.remaining_balance); + t.client + .repay_installment(&user, &loan_id, &2, &loan.remaining_balance); assert_bucket_invariant(&t, loan_id); let loan = t.client.get_loan(&loan_id); @@ -4152,9 +4179,9 @@ fn test_mark_defaulted_zero_shortfall_skips_absorb_loss() { t.env.ledger().set_timestamp(1000); let schedule = t.single_installment(1000, 5000); t.mint(&user, 1000); - let loan_id = t - .client - .create_loan(&user, &vendor, &1000, &1000, &schedule, &LoanType::Standard); + let loan_id = + t.client + .create_loan(&user, &vendor, &1000, &1000, &schedule, &LoanType::Standard); t.advance_past(5000); t.client.mark_defaulted(&loan_id); @@ -4177,9 +4204,14 @@ fn test_mark_defaulted_absorb_loss_with_partial_repayment() { t.env.ledger().set_timestamp(1000); let schedule = t.single_installment(DEFAULT_TOTAL_DUE, 5000); t.mint(&user, DEFAULT_GUARANTEE); - let loan_id = t - .client - .create_loan(&user, &vendor, &DEFAULT_PRINCIPAL, &DEFAULT_GUARANTEE, &schedule, &LoanType::Standard); + let loan_id = t.client.create_loan( + &user, + &vendor, + &DEFAULT_PRINCIPAL, + &DEFAULT_GUARANTEE, + &schedule, + &LoanType::Standard, + ); // Repay some of the loan: 500 out of 1050 t.mint(&user, 500); @@ -4222,7 +4254,8 @@ fn test_mark_defaulted_loss_absorption_share_price_impact() { let lp_client = LiquidityPoolContractClient::new(&env, &lp_id); // SAFETY: env outlives client — same pattern as other tests - let lp_client: LiquidityPoolContractClient<'static> = unsafe { core::mem::transmute(lp_client) }; + let lp_client: LiquidityPoolContractClient<'static> = + unsafe { core::mem::transmute(lp_client) }; let admin = Address::generate(&env); let treasury = Address::generate(&env); @@ -4255,6 +4288,7 @@ fn test_mark_defaulted_loss_absorption_share_price_impact() { lp_client.deposit(&lp, &10_000); let share_price_before = lp_client.get_pool_stats().share_price; + assert_eq!(share_price_before, 10_000); // Create and default a loan let user = Address::generate(&env); @@ -4283,14 +4317,8 @@ fn test_mark_defaulted_loss_absorption_share_price_impact() { }); token_sac.mint(&user, &200); - let loan_id = cl_client.create_loan( - &user, - &vendor, - &1000, - &200, - &schedule, - &LoanType::Standard, - ); + let loan_id = + cl_client.create_loan(&user, &vendor, &1000, &200, &schedule, &LoanType::Standard); // Advance past due date and default env.ledger().set_timestamp(5001); @@ -4345,12 +4373,19 @@ fn test_creditline_upgrade_delay_parameterized_via_parameters_contract() { // At 86,401s (1 day), execute_upgrade fails because custom delay is 172,800s ctx.env.ledger().set_timestamp(86_401); let res = ctx.client.try_execute_upgrade(&wasm_hash); - let expected_err = soroban_sdk::Error::from_contract_error(CreditLineError::UpgradeTimelockNotMet as u32); + let expected_err = + soroban_sdk::Error::from_contract_error(CreditLineError::UpgradeTimelockNotMet as u32); assert_eq!(res, Err(Ok(expected_err))); // Advance past custom 2-day delay (172,801s) ctx.env.ledger().set_timestamp(172_801); - let wasm_real = ctx.env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice(&ctx.env, include_bytes!("../../../contracts/test-fixtures/contract.wasm"))); + let wasm_real = ctx + .env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &ctx.env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); ctx.client.propose_upgrade(&wasm_real); ctx.env.ledger().set_timestamp(172_801 + 172_801); ctx.client.execute_upgrade(&wasm_real); @@ -4486,7 +4521,7 @@ fn test_creditline_pause_unpause_requires_admin() { let t = TestCtx::setup(); let non_admin = Address::generate(&t.env); - assert_eq!(t.client.is_paused(), false); + assert!(!t.client.is_paused()); // Non-admin cannot pause let expected_err = soroban_sdk::Error::from_contract_error(CreditLineError::NotAdmin as u32); @@ -4494,14 +4529,14 @@ fn test_creditline_pause_unpause_requires_admin() { // Admin can pause t.client.pause(&t.admin); - assert_eq!(t.client.is_paused(), true); + assert!(t.client.is_paused()); // Non-admin cannot unpause assert_eq!(t.client.try_unpause(&non_admin), Err(Ok(expected_err))); // Admin can unpause t.client.unpause(&t.admin); - assert_eq!(t.client.is_paused(), false); + assert!(!t.client.is_paused()); } #[test] @@ -4528,17 +4563,20 @@ fn test_creditline_paused_state_blocks_mutating_functions_and_allows_repayment_a // Admin pauses creditline contract t.creditline.pause(&t.admin); - assert_eq!(t.creditline.is_paused(), true); + assert!(t.creditline.is_paused()); - let expected_paused_err = soroban_sdk::Error::from_contract_error(CreditLineError::ContractPaused as u32); + let expected_paused_err = + soroban_sdk::Error::from_contract_error(CreditLineError::ContractPaused as u32); // Mutating functions blocked with ContractPaused assert_eq!( - t.creditline.try_create_loan(&user, &vendor, &1_000, &200, &schedule, &LoanType::Standard), + t.creditline + .try_create_loan(&user, &vendor, &1_000, &200, &schedule, &LoanType::Standard), Err(Ok(CreditLineError::ContractPaused)) ); assert_eq!( - t.creditline.try_request_loan(&user, &vendor, &1_000, &200, &schedule, &LoanType::Standard), + t.creditline + .try_request_loan(&user, &vendor, &1_000, &200, &schedule, &LoanType::Standard), Err(Ok(CreditLineError::ContractPaused)) ); assert_eq!( @@ -4561,7 +4599,10 @@ fn test_creditline_paused_state_blocks_mutating_functions_and_allows_repayment_a // Repayment Exception Policy: repay_loan and repay_installment are NOT blocked by pause t.mint(&user, 500); assert!(t.creditline.try_repay_loan(&user, &loan_id, &500).is_ok()); - assert!(t.creditline.try_repay_installment(&user, &loan_id, &0, &500).is_ok()); + assert!(t + .creditline + .try_repay_installment(&user, &loan_id, &0, &500) + .is_ok()); // Query functions work fine while paused assert_eq!(t.creditline.get_loan(&loan_id).loan_id, loan_id); @@ -4571,5 +4612,5 @@ fn test_creditline_paused_state_blocks_mutating_functions_and_allows_repayment_a // Unpause restores operations t.creditline.unpause(&t.admin); - assert_eq!(t.creditline.is_paused(), false); + assert!(!t.creditline.is_paused()); } diff --git a/contracts/liquidity-pool-contract/src/events.rs b/contracts/liquidity-pool-contract/src/events.rs index 3a912e2..538be6b 100644 --- a/contracts/liquidity-pool-contract/src/events.rs +++ b/contracts/liquidity-pool-contract/src/events.rs @@ -74,7 +74,8 @@ pub fn emit_interest_distributed( /// Emitted when a principal shortfall is absorbed from a defaulted loan pub fn emit_loss_absorbed(env: &Env, creditline: &Address, principal_shortfall: i128) { - env.events().publish((LOSS_ABSORBED, creditline), principal_shortfall); + env.events() + .publish((LOSS_ABSORBED, creditline), principal_shortfall); } pub fn emit_contract_upgraded(env: &Env, old_version: u32, new_version: u32) { @@ -97,17 +98,13 @@ pub fn emit_upgrade_proposed( } pub fn emit_paused(env: &Env, admin: &Address) { - env.events().publish( - (symbol_short!("PAUSED"), admin), - env.ledger().timestamp(), - ); + env.events() + .publish((symbol_short!("PAUSED"), admin), env.ledger().timestamp()); } pub fn emit_unpaused(env: &Env, admin: &Address) { - env.events().publish( - (symbol_short!("UNPAUSED"), admin), - env.ledger().timestamp(), - ); + env.events() + .publish((symbol_short!("UNPAUSED"), admin), env.ledger().timestamp()); } const CAPS_UPDATED: Symbol = symbol_short!("CAPSUPD"); diff --git a/contracts/liquidity-pool-contract/src/lib.rs b/contracts/liquidity-pool-contract/src/lib.rs index 184ea32..dd17404 100644 --- a/contracts/liquidity-pool-contract/src/lib.rs +++ b/contracts/liquidity-pool-contract/src/lib.rs @@ -224,8 +224,8 @@ impl LiquidityPoolContract { // Seed dead (unclaimable) shares on the very first deposit so a dust // depositor cannot own 100 % of a yield-bearing pool. - let total_shares = storage::get_total_shares(&env) - .unwrap_or_else(|err| panic_with_error!(&env, err)); + let total_shares = + storage::get_total_shares(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); let is_first_deposit = total_shares == 0; if is_first_deposit { @@ -264,14 +264,14 @@ impl LiquidityPoolContract { storage::set_lp_shares(&env, &provider, new_shares); // Update total shares (includes dead shares) - let total_shares = storage::get_total_shares(&env) - .unwrap_or_else(|err| panic_with_error!(&env, err)); + let total_shares = + storage::get_total_shares(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); let new_total_shares = safe_math::add_i128(total_shares, shares_issued)?; storage::set_total_shares(&env, new_total_shares); // Update total liquidity - let total_liquidity = storage::get_total_liquidity(&env) - .unwrap_or_else(|err| panic_with_error!(&env, err)); + let total_liquidity = + storage::get_total_liquidity(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); let new_total_liquidity = safe_math::add_i128(total_liquidity, amount)?; storage::set_total_liquidity(&env, new_total_liquidity); @@ -378,8 +378,8 @@ impl LiquidityPoolContract { } // Optional vendor cross-check (skipped entirely when no registry set). - if let Some(registry) = storage::get_vendor_registry(&env) - .unwrap_or_else(|err| panic_with_error!(&env, err)) + if let Some(registry) = + storage::get_vendor_registry(&env).unwrap_or_else(|err| panic_with_error!(&env, err)) { if !Self::vendor_is_active(&env, ®istry, &merchant) { return Err(LiquidityPoolError::VendorNotActive); @@ -612,7 +612,11 @@ impl LiquidityPoolContract { // any accounting change, so share price cannot rise without backing. let token = storage::get_token(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); let token_client = token::Client::new(&env, &token); - token_client.transfer(&creditline, &env.current_contract_address(), &interest_amount); + token_client.transfer( + &creditline, + &env.current_contract_address(), + &interest_amount, + ); let res = Self::distribute_interest_internal(&env, interest_amount); Self::exit_non_reentrant(&env); @@ -649,7 +653,11 @@ impl LiquidityPoolContract { // any accounting change, so share price cannot rise without backing. let token = storage::get_token(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); let token_client = token::Client::new(&env, &token); - token_client.transfer(&creditline, &env.current_contract_address(), &interest_amount); + token_client.transfer( + &creditline, + &env.current_contract_address(), + &interest_amount, + ); let res = Self::distribute_interest_internal(&env, interest_amount); Self::exit_non_reentrant(&env); @@ -795,7 +803,8 @@ impl LiquidityPoolContract { if total_shares == 0 { return 0; } - let share_price = Self::calculate_share_price_internal(&env).unwrap_or(types::SHARE_PRICE_PRECISION); + let share_price = + Self::calculate_share_price_internal(&env).unwrap_or(types::SHARE_PRICE_PRECISION); safe_math::div_i128( safe_math::mul_i128(shares, share_price).unwrap_or(0), types::SHARE_PRICE_PRECISION, @@ -876,11 +885,13 @@ impl LiquidityPoolContract { fn get_upgrade_delay_seconds(env: &Env) -> u64 { use soroban_sdk::IntoVal; if let Ok(Some(params_addr)) = storage::get_parameters_contract(env) { - if let Ok(Ok(params)) = env.try_invoke_contract::( - ¶ms_addr, - &soroban_sdk::Symbol::new(env, "get_parameters"), - ().into_val(env), - ) { + if let Ok(Ok(params)) = env + .try_invoke_contract::( + ¶ms_addr, + &soroban_sdk::Symbol::new(env, "get_parameters"), + ().into_val(env), + ) + { if params.upgrade_delay_seconds > 0 { return params.upgrade_delay_seconds; } diff --git a/contracts/liquidity-pool-contract/src/storage.rs b/contracts/liquidity-pool-contract/src/storage.rs index c6844bd..5125155 100644 --- a/contracts/liquidity-pool-contract/src/storage.rs +++ b/contracts/liquidity-pool-contract/src/storage.rs @@ -184,7 +184,9 @@ pub fn get_parameters_contract(env: &Env) -> Result, LiquidityPo } pub fn set_parameters_contract(env: &Env, address: &Address) { - env.storage().instance().set(&PARAMETERS_CONTRACT_KEY, address); + env.storage() + .instance() + .set(&PARAMETERS_CONTRACT_KEY, address); } // --- Paused State --- @@ -227,7 +229,9 @@ pub fn get_merchant_exposure_cap(env: &Env) -> i128 { } pub fn set_merchant_exposure_cap(env: &Env, amount: i128) { - env.storage().instance().set(&MERCHANT_EXPOSURE_CAP_KEY, &amount); + env.storage() + .instance() + .set(&MERCHANT_EXPOSURE_CAP_KEY, &amount); } // --- Optional Vendor Registry (instance) --- @@ -257,11 +261,7 @@ pub const OUTFLOW_USED_KEY: Symbol = symbol_short!("LEDOUT"); /// `fund_loan` on a new ledger sequence resets the used counter to zero. pub fn get_outflow_window(env: &Env) -> (u32, i128) { let seq: u32 = env.storage().instance().get(&OUTFLOW_SEQ_KEY).unwrap_or(0); - let used: i128 = env - .storage() - .instance() - .get(&OUTFLOW_USED_KEY) - .unwrap_or(0); + let used: i128 = env.storage().instance().get(&OUTFLOW_USED_KEY).unwrap_or(0); (seq, used) } diff --git a/contracts/liquidity-pool-contract/src/tests.rs b/contracts/liquidity-pool-contract/src/tests.rs index 77dcc4c..bfb371d 100644 --- a/contracts/liquidity-pool-contract/src/tests.rs +++ b/contracts/liquidity-pool-contract/src/tests.rs @@ -443,16 +443,23 @@ fn test_admin_upgrade_bumps_version() { // default version should be 1 assert_eq!(t.client.get_version(), 1u32); - let wasm_hash = t.env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice( - &t.env, - include_bytes!("../../../contracts/test-fixtures/contract.wasm"), - )); + let wasm_hash = t + .env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &t.env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); t.client.propose_upgrade(&wasm_hash); t.env.ledger().set_timestamp(86_401); t.client.execute_upgrade(&wasm_hash); // event observed - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = t.env.events().all(); + let events: soroban_sdk::Vec<( + soroban_sdk::Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = t.env.events().all(); let mut found = false; for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(&t.env); @@ -986,7 +993,8 @@ fn test_withdrawal_with_active_loans() { assert_eq!(loan_stats.total_shares, expected_initial_shares); // 5. Calculate max withdrawable shares (limited by provider ownership) - let max_withdrawable_shares = (loan_stats.available_liquidity * loan_stats.total_shares) / loan_stats.total_liquidity; + let max_withdrawable_shares = + (loan_stats.available_liquidity * loan_stats.total_shares) / loan_stats.total_liquidity; assert!(max_withdrawable_shares >= withdrawal_shares); // 6. Withdraw up to available amount - this should succeed @@ -2760,14 +2768,24 @@ fn test_timelocked_upgrade_success_bumps_version() { let t = TestEnv::setup(); assert_eq!(t.client.get_version(), 1); - let wasm_hash = t.env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice(&t.env, include_bytes!("../../../contracts/test-fixtures/contract.wasm"))); + let wasm_hash = t + .env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &t.env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); t.client.propose_upgrade(&wasm_hash); // Advance past 1-day timelock delay (86,400 seconds) t.env.ledger().set_timestamp(86_401); t.client.execute_upgrade(&wasm_hash); - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = t.env.events().all(); + let events: soroban_sdk::Vec<( + soroban_sdk::Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = t.env.events().all(); let mut upgraded_new: Option = None; for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(&t.env); @@ -2812,12 +2830,19 @@ fn test_upgrade_delay_parameterized_via_parameters_contract() { // At 86,401s (1 day), execute_upgrade fails because custom delay is 172,800s t.env.ledger().set_timestamp(86_401); let res = t.client.try_execute_upgrade(&wasm_hash); - let expected_err = soroban_sdk::Error::from_contract_error(LiquidityPoolError::UpgradeTimelockNotMet as u32); + let expected_err = + soroban_sdk::Error::from_contract_error(LiquidityPoolError::UpgradeTimelockNotMet as u32); assert_eq!(res, Err(Ok(expected_err))); // Advance past custom 2-day delay (172,801s) t.env.ledger().set_timestamp(172_801); - let wasm_real = t.env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice(&t.env, include_bytes!("../../../contracts/test-fixtures/contract.wasm"))); + let wasm_real = t + .env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &t.env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); t.client.propose_upgrade(&wasm_real); t.env.ledger().set_timestamp(172_801 + 172_801); t.client.execute_upgrade(&wasm_real); @@ -2952,7 +2977,7 @@ fn test_post_default_deposit_does_not_brick() { // Near-zero proportional price, not 0 (would brick) and not 10000 (would hide loss) assert!(stats.share_price > 0 && stats.share_price < 10_000); assert_eq!(stats.share_price, 909); // (0+1000)*10000/11000 - // Subsequent deposit must succeed and not divide-by-zero + // Subsequent deposit must succeed and not divide-by-zero let provider2 = Address::generate(&t.env); t.mint(&provider2, 1_000); let shares2 = t.client.deposit(&provider2, &1_000); @@ -2970,7 +2995,7 @@ fn test_pause_unpause_requires_admin() { let t = TestEnv::setup(); let non_admin = Address::generate(&t.env); - assert_eq!(t.client.is_paused(), false); + assert!(!t.client.is_paused()); // Non-admin cannot pause let expected_err = soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); @@ -2978,14 +3003,14 @@ fn test_pause_unpause_requires_admin() { // Admin can pause t.client.pause(&t.admin); - assert_eq!(t.client.is_paused(), true); + assert!(t.client.is_paused()); // Non-admin cannot unpause assert_eq!(t.client.try_unpause(&non_admin), Err(Ok(expected_err))); // Admin can unpause t.client.unpause(&t.admin); - assert_eq!(t.client.is_paused(), false); + assert!(!t.client.is_paused()); } #[test] @@ -2998,7 +3023,7 @@ fn test_paused_state_blocks_mutating_functions_and_allows_repayment_and_queries( let shares = t.client.deposit(&provider, &5_000); t.client.pause(&t.admin); - assert_eq!(t.client.is_paused(), true); + assert!(t.client.is_paused()); // Mutating functions blocked with ContractPaused assert_eq!( @@ -3044,7 +3069,7 @@ fn test_paused_state_blocks_mutating_functions_and_allows_repayment_and_queries( // Unpause restores operations t.client.unpause(&t.admin); - assert_eq!(t.client.is_paused(), false); + assert!(!t.client.is_paused()); assert!(t.client.deposit(&provider, &1_000) > 0); } @@ -3053,7 +3078,7 @@ fn test_paused_state_blocks_mutating_functions_and_allows_repayment_and_queries( /// Deploy a real VendorRegistryContract, initialize it with `admin`, and wire /// it into the liquidity pool. Returns the registry client for vendor ops. -fn setup_vendor_registry(t: &TestEnv) -> VendorRegistryContractClient { +fn setup_vendor_registry(t: &TestEnv) -> VendorRegistryContractClient<'_> { let registry_id = t.env.register(VendorRegistryContract, ()); let registry = VendorRegistryContractClient::new(&t.env, ®istry_id); registry.initialize(&t.admin); @@ -3099,8 +3124,7 @@ fn test_set_outflow_cap_invalid_bps_fails() { fn test_set_outflow_cap_by_non_admin_fails() { let t = TestEnv::setup(); let non_admin = Address::generate(&t.env); - let expected_err = - soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); + let expected_err = soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); assert_eq!( t.client.try_set_outflow_cap_bps(&non_admin, &5_000), Err(Ok(expected_err)) @@ -3176,8 +3200,7 @@ fn test_set_merchant_exposure_cap_negative_fails() { fn test_set_merchant_exposure_cap_by_non_admin_fails() { let t = TestEnv::setup(); let non_admin = Address::generate(&t.env); - let expected_err = - soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); + let expected_err = soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); assert_eq!( t.client.try_set_merchant_exposure_cap(&non_admin, &5_000), Err(Ok(expected_err)) @@ -3249,7 +3272,10 @@ fn test_vendor_registry_none_by_default() { fn test_vendor_registry_blocks_unregistered_merchant() { let t = TestEnv::setup(); let registry = setup_vendor_registry(&t); - assert_eq!(t.client.get_vendor_registry(), Some(registry.address.clone())); + assert_eq!( + t.client.get_vendor_registry(), + Some(registry.address.clone()) + ); let provider = Address::generate(&t.env); let merchant = Address::generate(&t.env); @@ -3303,7 +3329,7 @@ fn test_vendor_registry_blocks_suspended_merchant() { #[test] fn test_vendor_registry_cleared_restores_legacy_behavior() { let t = TestEnv::setup(); - let registry = setup_vendor_registry(&t); + let _registry = setup_vendor_registry(&t); let merchant = Address::generate(&t.env); let provider = Address::generate(&t.env); @@ -3328,10 +3354,10 @@ fn test_set_vendor_registry_by_non_admin_fails() { let t = TestEnv::setup(); let non_admin = Address::generate(&t.env); let registry_id = t.env.register(VendorRegistryContract, ()); - let expected_err = - soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); + let expected_err = soroban_sdk::Error::from_contract_error(LiquidityPoolError::NotAdmin as u32); assert_eq!( - t.client.try_set_vendor_registry(&non_admin, &Some(registry_id)), + t.client + .try_set_vendor_registry(&non_admin, &Some(registry_id)), Err(Ok(expected_err)) ); } diff --git a/contracts/parameters-contract/src/events.rs b/contracts/parameters-contract/src/events.rs index 0769672..6107c86 100644 --- a/contracts/parameters-contract/src/events.rs +++ b/contracts/parameters-contract/src/events.rs @@ -58,7 +58,8 @@ pub fn emit_proposal_created(env: &Env, id: u64, proposer: &Address) { } pub fn emit_proposal_approved(env: &Env, id: u64, signer: &Address, approvals: u32) { - env.events().publish((PROP_APPROVED, signer), (id, approvals)); + env.events() + .publish((PROP_APPROVED, signer), (id, approvals)); } pub fn emit_proposal_executed(env: &Env, id: u64) { diff --git a/contracts/parameters-contract/src/lib.rs b/contracts/parameters-contract/src/lib.rs index 88053b3..3fcf803 100644 --- a/contracts/parameters-contract/src/lib.rs +++ b/contracts/parameters-contract/src/lib.rs @@ -3,14 +3,11 @@ mod access; mod errors; mod events; -mod safe_math; mod storage; mod types; pub use errors::ParametersError; -pub use types::{ - default_parameters, MultisigConfig, Proposal, ProposalAction, ProtocolParameters, -}; +pub use types::{default_parameters, MultisigConfig, Proposal, ProposalAction, ProtocolParameters}; use soroban_sdk::{contract, contractimpl, panic_with_error, Address, BytesN, Env, Vec}; @@ -131,8 +128,8 @@ impl ParametersContract { signer.require_auth(); access::require_signer(&env, &signer); - let mut proposal = - storage::get_proposal(&env, proposal_id).unwrap_or_else(|err| panic_with_error!(&env, err)); + let mut proposal = storage::get_proposal(&env, proposal_id) + .unwrap_or_else(|err| panic_with_error!(&env, err)); if proposal.executed { panic_with_error!(&env, ParametersError::ProposalAlreadyExecuted); @@ -161,8 +158,8 @@ impl ParametersContract { /// eligible approvals. Permissionless — the collected approvals are the /// authorization. pub fn execute(env: Env, proposal_id: u64) { - let mut proposal = - storage::get_proposal(&env, proposal_id).unwrap_or_else(|err| panic_with_error!(&env, err)); + let mut proposal = storage::get_proposal(&env, proposal_id) + .unwrap_or_else(|err| panic_with_error!(&env, err)); if proposal.executed { panic_with_error!(&env, ParametersError::ProposalAlreadyExecuted); @@ -201,7 +198,6 @@ impl ParametersContract { storage::get_proposal(&env, proposal_id) } - pub fn get_admin(env: Env) -> Result { storage::get_admin(&env) } @@ -214,7 +210,6 @@ impl ParametersContract { storage::get_parameters(&env) } - fn do_update_parameters(env: &Env, params: &ProtocolParameters) { Self::validate_parameters(env, params); let admin = storage::get_admin(env).unwrap_or_else(|err| panic_with_error!(env, err)); @@ -294,7 +289,8 @@ impl ParametersContract { } fn do_set_late_fee_bps(env: &Env, bps: u32) { - let mut params = storage::get_parameters(env).unwrap_or_else(|err| panic_with_error!(env, err)); + let mut params = + storage::get_parameters(env).unwrap_or_else(|err| panic_with_error!(env, err)); params.late_fee_bps = bps; let admin = storage::get_admin(env).unwrap_or_else(|err| panic_with_error!(env, err)); storage::set_parameters(env, ¶ms); diff --git a/contracts/parameters-contract/src/safe_math.rs b/contracts/parameters-contract/src/safe_math.rs deleted file mode 100644 index a53d229..0000000 --- a/contracts/parameters-contract/src/safe_math.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::errors::ParametersError; - -pub fn add_i128(a: i128, b: i128) -> Result { - a.checked_add(b).ok_or(ParametersError::Overflow) -} - -pub fn sub_i128(a: i128, b: i128) -> Result { - a.checked_sub(b).ok_or(ParametersError::Underflow) -} - -pub fn mul_i128(a: i128, b: i128) -> Result { - a.checked_mul(b).ok_or(ParametersError::Overflow) -} - -pub fn div_i128(a: i128, b: i128) -> Result { - a.checked_div(b).ok_or(ParametersError::Overflow) -} diff --git a/contracts/parameters-contract/src/tests.rs b/contracts/parameters-contract/src/tests.rs index e48a0fe..0186a9b 100644 --- a/contracts/parameters-contract/src/tests.rs +++ b/contracts/parameters-contract/src/tests.rs @@ -47,8 +47,11 @@ fn signer_update_action(signers: Vec
, threshold: u32) -> ProposalAction } fn has_event(env: &Env, symbol: &str) -> bool { - let events: soroban_sdk::Vec<(Address, soroban_sdk::Vec, soroban_sdk::Val)> = - env.events().all(); + let events: soroban_sdk::Vec<( + Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = env.events().all(); for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(env); if topic == soroban_sdk::Symbol::new(env, symbol) { @@ -362,10 +365,7 @@ fn test_update_signers_requires_elevated_quorum() { let n1 = Address::generate(&env); let n2 = Address::generate(&env); - let id = client.propose( - &s1, - &signer_update_action(vec![&env, n1, n2], 2), - ); + let id = client.propose(&s1, &signer_update_action(vec![&env, n1, n2], 2)); // s1 (proposer auto-approves) + s2 = 2 approvals, but signer changes in a // 2-of-3 set require threshold + 1 = 3. client.approve(&s2, &id); @@ -405,10 +405,7 @@ fn test_signer_change_quorum_capped_at_full_committee_for_unanimous_set() { let n1 = Address::generate(&env); let n2 = Address::generate(&env); - let id = client.propose( - &s1, - &signer_update_action(vec![&env, n1, n2], 2), - ); + let id = client.propose(&s1, &signer_update_action(vec![&env, n1, n2], 2)); // 2-of-2 committee: elevated quorum min(2 + 1, 2) = 2 = unanimity. One // approval (the proposer) is not enough. assert!(client.try_execute(&id).is_err()); @@ -606,16 +603,21 @@ fn test_upgrade_via_proposal_increments_version() { let (env, client, _admin, s1, s2, _s3) = setup_multisig(); assert_eq!(client.get_version(), 1u32); - let wasm_hash = env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice( - &env, - include_bytes!("../../../contracts/test-fixtures/contract.wasm"), - )); + let wasm_hash = env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); let id = client.propose(&s1, &ProposalAction::Upgrade(wasm_hash)); client.approve(&s2, &id); client.execute(&id); - assert!(has_event(&env, "CONTRACTUPGRADED"), "CONTRACTUPGRADED event not found"); + assert!( + has_event(&env, "CONTRACTUPGRADED"), + "CONTRACTUPGRADED event not found" + ); } #[test] diff --git a/contracts/reputation-contract/src/lib.rs b/contracts/reputation-contract/src/lib.rs index f6eb2f3..40e3a9f 100644 --- a/contracts/reputation-contract/src/lib.rs +++ b/contracts/reputation-contract/src/lib.rs @@ -292,11 +292,13 @@ impl ReputationContract { fn get_upgrade_delay_seconds(env: &Env) -> u64 { use soroban_sdk::IntoVal; if let Ok(Some(params_addr)) = storage::get_parameters_contract(env) { - if let Ok(Ok(params)) = env.try_invoke_contract::( - ¶ms_addr, - &soroban_sdk::Symbol::new(env, "get_parameters"), - ().into_val(env), - ) { + if let Ok(Ok(params)) = env + .try_invoke_contract::( + ¶ms_addr, + &soroban_sdk::Symbol::new(env, "get_parameters"), + ().into_val(env), + ) + { if params.upgrade_delay_seconds > 0 { return params.upgrade_delay_seconds; } diff --git a/contracts/reputation-contract/src/storage.rs b/contracts/reputation-contract/src/storage.rs index 3461f5b..bd26c9e 100644 --- a/contracts/reputation-contract/src/storage.rs +++ b/contracts/reputation-contract/src/storage.rs @@ -124,5 +124,7 @@ pub fn get_parameters_contract(env: &Env) -> Result, ReputationE } pub fn set_parameters_contract(env: &Env, address: &Address) { - env.storage().instance().set(&PARAMETERS_CONTRACT_KEY, address); + env.storage() + .instance() + .set(&PARAMETERS_CONTRACT_KEY, address); } diff --git a/contracts/reputation-contract/src/tests.rs b/contracts/reputation-contract/src/tests.rs index 53a0e9e..ddbe6d2 100644 --- a/contracts/reputation-contract/src/tests.rs +++ b/contracts/reputation-contract/src/tests.rs @@ -302,15 +302,21 @@ fn it_allows_admin_upgrade_and_bumps_version() { client.initialize(&admin); assert_eq!(client.get_version(), 1u32); - let wasm_hash = env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice( - &env, - include_bytes!("../../../contracts/test-fixtures/contract.wasm"), - )); + let wasm_hash = env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); client.propose_upgrade(&wasm_hash); env.ledger().set_timestamp(86_401); client.execute_upgrade(&wasm_hash); - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = env.events().all(); + let events: soroban_sdk::Vec<( + soroban_sdk::Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = env.events().all(); let mut found = false; for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(&env); @@ -1527,12 +1533,18 @@ fn test_reputation_upgrade_delay_parameterized_via_parameters_contract() { // At 86,401s (1 day), execute_upgrade fails because custom delay is 172,800s env.ledger().set_timestamp(86_401); let res = client.try_execute_upgrade(&wasm_hash); - let expected_err = soroban_sdk::Error::from_contract_error(ReputationError::UpgradeTimelockNotMet as u32); + let expected_err = + soroban_sdk::Error::from_contract_error(ReputationError::UpgradeTimelockNotMet as u32); assert_eq!(res, Err(Ok(expected_err))); // Advance past custom 2-day delay (172,801s) env.ledger().set_timestamp(172_801); - let wasm_real = env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice(&env, include_bytes!("../../../contracts/test-fixtures/contract.wasm"))); + let wasm_real = env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); client.propose_upgrade(&wasm_real); env.ledger().set_timestamp(172_801 + 172_801); client.execute_upgrade(&wasm_real); diff --git a/contracts/vendor-registry-contract/src/lib.rs b/contracts/vendor-registry-contract/src/lib.rs index 66f65ef..73d8d7f 100644 --- a/contracts/vendor-registry-contract/src/lib.rs +++ b/contracts/vendor-registry-contract/src/lib.rs @@ -3,7 +3,6 @@ mod access; mod errors; mod events; -mod safe_math; mod storage; mod types; @@ -232,14 +231,16 @@ impl VendorRegistryContract { } pub fn set_parameters_contract(env: Env, address: Address) { - let admin = storage::get_admin(&env).unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); + let admin = storage::get_admin(&env) + .unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); admin.require_auth(); storage::set_parameters_contract(&env, &address); } /// Propose a timelocked contract WASM upgrade — admin only pub fn propose_upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { - let admin = storage::get_admin(&env).unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); + let admin = storage::get_admin(&env) + .unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); admin.require_auth(); let delay = Self::get_upgrade_delay_seconds(&env); @@ -259,7 +260,8 @@ impl VendorRegistryContract { /// Execute a previously proposed and timelocked contract WASM upgrade — admin only pub fn execute_upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { - let admin = storage::get_admin(&env).unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); + let admin = storage::get_admin(&env) + .unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); admin.require_auth(); let pending = storage::get_pending_upgrade(&env) @@ -296,11 +298,13 @@ impl VendorRegistryContract { fn get_upgrade_delay_seconds(env: &Env) -> u64 { use soroban_sdk::IntoVal; if let Ok(Some(params_addr)) = storage::get_parameters_contract(env) { - if let Ok(Ok(params)) = env.try_invoke_contract::( - ¶ms_addr, - &soroban_sdk::Symbol::new(env, "get_parameters"), - ().into_val(env), - ) { + if let Ok(Ok(params)) = env + .try_invoke_contract::( + ¶ms_addr, + &soroban_sdk::Symbol::new(env, "get_parameters"), + ().into_val(env), + ) + { if params.upgrade_delay_seconds > 0 { return params.upgrade_delay_seconds; } diff --git a/contracts/vendor-registry-contract/src/safe_math.rs b/contracts/vendor-registry-contract/src/safe_math.rs deleted file mode 100644 index 176d97b..0000000 --- a/contracts/vendor-registry-contract/src/safe_math.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::errors::Error; - -pub fn add_u64(a: u64, b: u64) -> Result { - a.checked_add(b).ok_or(Error::Overflow) -} - -pub fn sub_u64(a: u64, b: u64) -> Result { - a.checked_sub(b).ok_or(Error::Underflow) -} diff --git a/contracts/vendor-registry-contract/src/storage.rs b/contracts/vendor-registry-contract/src/storage.rs index 72eaaa9..059f47f 100644 --- a/contracts/vendor-registry-contract/src/storage.rs +++ b/contracts/vendor-registry-contract/src/storage.rs @@ -91,9 +91,7 @@ pub fn set_version(env: &Env, v: u32) { pub const PENDING_UPGRADE_KEY: soroban_sdk::Symbol = symbol_short!("PNDUPGD"); pub const DEFAULT_UPGRADE_DELAY_SECONDS: u64 = 86_400; // 1 day -pub fn get_pending_upgrade( - env: &Env, -) -> Result, Error> { +pub fn get_pending_upgrade(env: &Env) -> Result, Error> { Ok(env.storage().instance().get(&PENDING_UPGRADE_KEY)) } @@ -113,5 +111,7 @@ pub fn get_parameters_contract(env: &Env) -> Result, Error> { } pub fn set_parameters_contract(env: &Env, address: &Address) { - env.storage().instance().set(&PARAMETERS_CONTRACT_KEY, address); + env.storage() + .instance() + .set(&PARAMETERS_CONTRACT_KEY, address); } diff --git a/contracts/vendor-registry-contract/src/tests.rs b/contracts/vendor-registry-contract/src/tests.rs index e259503..8af0543 100644 --- a/contracts/vendor-registry-contract/src/tests.rs +++ b/contracts/vendor-registry-contract/src/tests.rs @@ -1,7 +1,7 @@ use super::*; use soroban_sdk::{ testutils::{Address as _, Events, Ledger}, - Address, Env, IntoVal, String, Val, Vec, + Address, Env, IntoVal, String, }; /// Helper function to set up the environment, contract, and test addresses. @@ -465,15 +465,21 @@ fn test_admin_upgrade_increments_version_and_emits_event() { env.mock_all_auths(); assert_eq!(client.get_version(), 1u32); - let wasm_hash = env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice( - &env, - include_bytes!("../../../contracts/test-fixtures/contract.wasm"), - )); + let wasm_hash = env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); client.propose_upgrade(&wasm_hash); env.ledger().set_timestamp(86_401); client.execute_upgrade(&wasm_hash); - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = env.events().all(); + let events: soroban_sdk::Vec<( + soroban_sdk::Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = env.events().all(); let mut found = false; for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(&env); @@ -566,7 +572,12 @@ fn test_vendor_registry_upgrade_delay_parameterized_via_parameters_contract() { // Advance past custom 2-day delay (172,801s) env.ledger().set_timestamp(172_801); - let wasm_real = env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice(&env, include_bytes!("../../../contracts/test-fixtures/contract.wasm"))); + let wasm_real = env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); client.propose_upgrade(&wasm_real); env.ledger().set_timestamp(172_801 + 172_801); client.execute_upgrade(&wasm_real); diff --git a/contracts/vouching-contract/src/lib.rs b/contracts/vouching-contract/src/lib.rs index 78fc8fd..9c7aa31 100644 --- a/contracts/vouching-contract/src/lib.rs +++ b/contracts/vouching-contract/src/lib.rs @@ -131,7 +131,7 @@ impl VouchingContract { } let now = env.ledger().timestamp(); - let expiry = record.ts.checked_add(VOUCH_DURATION).unwrap_or(u64::MAX); + let expiry = record.ts.saturating_add(VOUCH_DURATION); if expiry >= now { panic_with_error!(&env, VouchingError::VouchNotExpired); } @@ -155,9 +155,7 @@ impl VouchingContract { if let Ok(mut record) = storage::get_vouch(&env, &mentor, &learner) { // A vouch whose TTL has elapsed is expired regardless of what // storage says, so readers never observe a stale active boost. - if record.active - && record.ts.checked_add(VOUCH_DURATION).unwrap_or(u64::MAX) < now - { + if record.active && record.ts.saturating_add(VOUCH_DURATION) < now { record.active = false; } records.push_back(record); @@ -176,8 +174,7 @@ impl VouchingContract { } pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { - let admin = storage::get_admin(&env) - .unwrap_or_else(|err| panic_with_error!(&env, err)); + let admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); admin.require_auth(); Self::enter_non_reentrant(&env); diff --git a/contracts/vouching-contract/src/tests.rs b/contracts/vouching-contract/src/tests.rs index 6a7cc4e..c0e4e67 100644 --- a/contracts/vouching-contract/src/tests.rs +++ b/contracts/vouching-contract/src/tests.rs @@ -6,9 +6,7 @@ use soroban_sdk::{ Address, Env, IntoVal, Symbol, Val, Vec, }; -use crate::{ - VouchingContract, VouchingContractClient, DEFAULT_VOUCH_BOOST, VOUCH_DURATION, -}; +use crate::{VouchingContract, VouchingContractClient, DEFAULT_VOUCH_BOOST, VOUCH_DURATION}; #[contract] pub struct MockReputationContract; @@ -332,10 +330,10 @@ fn test_boost_removal_clamped_to_baseline() { // Seed pre-existing reputation so the vouch baseline is non-zero. ctx.env.as_contract(&ctx.reputation, || { - ctx.env.storage().instance().set( - &(symbol_short!("SCORE"), ctx.learner.clone()), - &20u32, - ); + ctx.env + .storage() + .instance() + .set(&(symbol_short!("SCORE"), ctx.learner.clone()), &20u32); }); ctx.env.ledger().with_mut(|l| l.timestamp = 1_000_000); @@ -597,13 +595,20 @@ fn test_upgrade_bumps_version_and_emits_event() { assert_eq!(ctx.client.get_version(), 1u32); - let wasm_hash = ctx.env.deployer().upload_contract_wasm(soroban_sdk::Bytes::from_slice( - &ctx.env, - include_bytes!("../../../contracts/test-fixtures/contract.wasm"), - )); + let wasm_hash = ctx + .env + .deployer() + .upload_contract_wasm(soroban_sdk::Bytes::from_slice( + &ctx.env, + include_bytes!("../../../contracts/test-fixtures/contract.wasm"), + )); ctx.client.upgrade(&wasm_hash); - let events: soroban_sdk::Vec<(soroban_sdk::Address, soroban_sdk::Vec, soroban_sdk::Val)> = ctx.env.events().all(); + let events: soroban_sdk::Vec<( + soroban_sdk::Address, + soroban_sdk::Vec, + soroban_sdk::Val, + )> = ctx.env.events().all(); let mut found = false; for e in events.iter() { let topic: soroban_sdk::Symbol = e.1.get_unchecked(0).into_val(&ctx.env);