From e9facbd225790009b675c8e90b19deb428100482 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Sun, 27 Sep 2026 02:19:27 +0100 Subject: [PATCH] test: add shared multi-user contract harness --- Cargo.lock | 12 +++++++ contracts/locked_in/Cargo.toml | 1 + contracts/locked_in/src/test.rs | 8 ++--- contracts/mock_pool/Cargo.toml | 1 + contracts/mock_pool/src/test.rs | 25 +++++++++---- contracts/plans/Cargo.toml | 1 + contracts/plans/src/test.rs | 37 +++----------------- contracts/spend_save/Cargo.toml | 1 + contracts/spend_save/src/test.rs | 10 +++--- contracts/target_savings/Cargo.toml | 1 + contracts/target_savings/src/test.rs | 10 ++---- contracts/test_utils/Cargo.toml | 10 ++++++ contracts/test_utils/src/lib.rs | 52 ++++++++++++++++++++++++++++ 13 files changed, 111 insertions(+), 58 deletions(-) create mode 100644 contracts/test_utils/Cargo.toml create mode 100644 contracts/test_utils/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8035d1d..00708c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,6 +776,7 @@ dependencies = [ "mock_pool", "soroban-sdk", "stellar-xdr", + "test_utils", ] [[package]] @@ -807,6 +808,7 @@ version = "0.0.1" dependencies = [ "soroban-sdk", "stellar-xdr", + "test_utils", ] [[package]] @@ -894,6 +896,7 @@ version = "0.0.1" dependencies = [ "soroban-sdk", "stellar-xdr", + "test_utils", ] [[package]] @@ -1351,6 +1354,7 @@ version = "0.0.1" dependencies = [ "soroban-sdk", "stellar-xdr", + "test_utils", ] [[package]] @@ -1445,6 +1449,14 @@ dependencies = [ "mock_pool", "soroban-sdk", "stellar-xdr", + "test_utils", +] + +[[package]] +name = "test_utils" +version = "0.0.1" +dependencies = [ + "soroban-sdk", ] [[package]] diff --git a/contracts/locked_in/Cargo.toml b/contracts/locked_in/Cargo.toml index d17e015..c162d10 100644 --- a/contracts/locked_in/Cargo.toml +++ b/contracts/locked_in/Cargo.toml @@ -21,3 +21,4 @@ soroban-sdk = "23.0.3" stellar-xdr = { version = "23.0.0", features = ["curr", "serde"] } soroban-sdk = { version = "23.0.3", features = ["testutils"] } mock_pool = { path = "../mock_pool" } +test_utils = { path = "../test_utils" } diff --git a/contracts/locked_in/src/test.rs b/contracts/locked_in/src/test.rs index 183c04f..94049e6 100644 --- a/contracts/locked_in/src/test.rs +++ b/contracts/locked_in/src/test.rs @@ -5,6 +5,7 @@ use super::*; use mock_pool::{MockPool, MockPoolClient}; use soroban_sdk::testutils::{Address as _, Ledger}; use soroban_sdk::{token::StellarAssetClient, Env}; +use test_utils::{create_token_contract, mint_tokens}; const ONE_YEAR: u64 = 31_536_000; @@ -29,11 +30,8 @@ fn setup() -> (Env, Address, Address, Address, LockedInClient<'static>) { let admin = Address::generate(&env); let user = Address::generate(&env); - let issuer = Address::generate(&env); - let asset = env.register_stellar_asset_contract_v2(issuer); - let token_addr = asset.address(); - let token_admin = StellarAssetClient::new(&env, &token_addr); - token_admin.mint(&user, &10_000_000_000_i128); // 1000 USDC + let (token_addr, token) = create_token_contract(&env, &admin); + mint_tokens(&env, &token, &user, 10_000_000_000_i128); // 1000 USDC let contract_id = env.register(LockedIn, (admin.clone(), token_addr.clone())); let client = LockedInClient::new(&env, &contract_id); diff --git a/contracts/mock_pool/Cargo.toml b/contracts/mock_pool/Cargo.toml index 5038a5c..58a30ef 100644 --- a/contracts/mock_pool/Cargo.toml +++ b/contracts/mock_pool/Cargo.toml @@ -20,3 +20,4 @@ soroban-sdk = "23.0.3" [dev-dependencies] stellar-xdr = { version = "23.0.0", features = ["curr", "serde"] } soroban-sdk = { version = "23.0.3", features = ["testutils"] } +test_utils = { path = "../test_utils" } diff --git a/contracts/mock_pool/src/test.rs b/contracts/mock_pool/src/test.rs index 8c4e43f..b16b165 100644 --- a/contracts/mock_pool/src/test.rs +++ b/contracts/mock_pool/src/test.rs @@ -3,7 +3,8 @@ extern crate std; use super::*; use soroban_sdk::testutils::{Address as _, Ledger}; -use soroban_sdk::{token::StellarAssetClient, Env}; +use soroban_sdk::Env; +use test_utils::{assert_solvable, create_token_contract, mint_tokens}; const SECONDS_PER_YEAR_U64: u64 = 31_536_000; @@ -15,12 +16,9 @@ fn setup() -> (Env, Address, Address, Address, MockPoolClient<'static>) { let admin = Address::generate(&env); let supplier = Address::generate(&env); - let issuer = Address::generate(&env); - let asset = env.register_stellar_asset_contract_v2(issuer.clone()); - let token_addr = asset.address(); - let token_admin = StellarAssetClient::new(&env, &token_addr); - token_admin.mint(&supplier, &10_000_000_000_i128); // 1000 USDC (7 decimals) - token_admin.mint(&admin, &10_000_000_000_i128); // 1000 USDC for the reserve + let (token_addr, token) = create_token_contract(&env, &admin); + mint_tokens(&env, &token, &supplier, 10_000_000_000_i128); // 1000 USDC (7 decimals) + mint_tokens(&env, &token, &admin, 10_000_000_000_i128); // 1000 USDC for the reserve // 10% APY (1000 bps) let contract_id = env.register(MockPool, (admin.clone(), token_addr.clone(), 1000u32)); @@ -37,6 +35,19 @@ fn test_supply_records_principal() { assert_eq!(client.get_position(&supplier), 1_000_000_000); // no time elapsed yet } +#[test] +fn test_multiple_users_remain_solvable() { + let (env, _admin, supplier, token_addr, client) = setup(); + let second = Address::generate(&env); + let token = soroban_sdk::token::TokenClient::new(&env, &token_addr); + mint_tokens(&env, &token, &second, 1_000_000_000); + + client.supply(&supplier, &1_000_000_000); + client.supply(&second, &1_000_000_000); + + assert_solvable(&env, &token_addr, &client.address, 2_000_000_000); +} + #[test] fn test_yield_accrues_linearly() { let (env, _admin, supplier, _t, client) = setup(); diff --git a/contracts/plans/Cargo.toml b/contracts/plans/Cargo.toml index 2c340a3..8309b75 100644 --- a/contracts/plans/Cargo.toml +++ b/contracts/plans/Cargo.toml @@ -20,3 +20,4 @@ soroban-sdk = "23.0.3" [dev-dependencies] stellar-xdr = { version = "23.0.0", features = ["curr", "serde"] } soroban-sdk = { version = "23.0.3", features = ["testutils"] } +test_utils = { path = "../test_utils" } diff --git a/contracts/plans/src/test.rs b/contracts/plans/src/test.rs index d75049e..e9c63ca 100644 --- a/contracts/plans/src/test.rs +++ b/contracts/plans/src/test.rs @@ -1,19 +1,9 @@ #![cfg(test)] +extern crate std; use super::*; -use soroban_sdk::{ - testutils::{Address as _, Ledger, LedgerInfo}, - token::{StellarAssetClient, TokenClient}, - Address, Env, String, Vec, -}; - -// Test token setup helper -fn create_token_contract<'a>(env: &Env, admin: &Address) -> (Address, TokenClient<'a>) { - let stellar_asset = env.register_stellar_asset_contract_v2(admin.clone()); - let token_address = stellar_asset.address(); - let token = TokenClient::new(env, &token_address); - (token_address, token) -} +use soroban_sdk::{testutils::Address as _, Address, Env, String, Vec}; +use test_utils::{create_token_contract, mint_tokens, set_ledger_time}; // Contract setup helper fn create_plans_contract<'a>( @@ -26,25 +16,6 @@ fn create_plans_contract<'a>( client } -// Helper to mint tokens to a user -fn mint_tokens(env: &Env, token: &TokenClient, to: &Address, amount: i128) { - let stellar_asset = StellarAssetClient::new(env, &token.address); - stellar_asset.mint(to, &amount); -} - -// Helper to set ledger time -fn set_ledger_time(env: &Env, timestamp: u64, sequence: u32) { - env.ledger().set(LedgerInfo { - timestamp, - protocol_version: 23, - sequence_number: sequence, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_ttl: 10, - min_persistent_entry_ttl: 10, - max_entry_ttl: 3110400, - }); -} #[test] fn test_initialization() { @@ -782,4 +753,4 @@ fn test_recurring_bill_with_recurrence_calendar() { let bill = client.get_bill(&bill_id); assert_eq!(bill.is_recurring, true); assert_eq!(bill.recurrence_calendar.len(), 3); -} \ No newline at end of file +} diff --git a/contracts/spend_save/Cargo.toml b/contracts/spend_save/Cargo.toml index 845b016..7f269a8 100644 --- a/contracts/spend_save/Cargo.toml +++ b/contracts/spend_save/Cargo.toml @@ -20,3 +20,4 @@ soroban-sdk = "23.0.3" [dev-dependencies] stellar-xdr = { version = "23.0.0", features = ["curr", "serde"] } soroban-sdk = { version = "23.0.3", features = ["testutils"] } +test_utils = { path = "../test_utils" } diff --git a/contracts/spend_save/src/test.rs b/contracts/spend_save/src/test.rs index 88e7302..e18f55f 100644 --- a/contracts/spend_save/src/test.rs +++ b/contracts/spend_save/src/test.rs @@ -3,7 +3,8 @@ extern crate std; use super::*; use soroban_sdk::testutils::{Address as _, Ledger}; -use soroban_sdk::{token::StellarAssetClient, Env}; +use soroban_sdk::Env; +use test_utils::{create_token_contract, mint_tokens}; const SECONDS_PER_DAY: u64 = 86_400; @@ -19,11 +20,8 @@ fn setup_at(ts: u64) -> (Env, Address, Address, Address, SpendSaveClient<'static let admin = Address::generate(&env); let user = Address::generate(&env); - let issuer = Address::generate(&env); - let asset = env.register_stellar_asset_contract_v2(issuer); - let token_addr = asset.address(); - let token_admin = StellarAssetClient::new(&env, &token_addr); - token_admin.mint(&user, &10_000_000_000_i128); // 1000 USDC + let (token_addr, token) = create_token_contract(&env, &admin); + mint_tokens(&env, &token, &user, 10_000_000_000_i128); // 1000 USDC let contract_id = env.register(SpendSave, (admin.clone(), token_addr.clone())); let client = SpendSaveClient::new(&env, &contract_id); diff --git a/contracts/target_savings/Cargo.toml b/contracts/target_savings/Cargo.toml index f007feb..945065a 100644 --- a/contracts/target_savings/Cargo.toml +++ b/contracts/target_savings/Cargo.toml @@ -21,3 +21,4 @@ soroban-sdk = "23.0.3" stellar-xdr = { version = "23.0.0", features = ["curr", "serde"] } soroban-sdk = { version = "23.0.3", features = ["testutils"] } mock_pool = { path = "../mock_pool" } +test_utils = { path = "../test_utils" } diff --git a/contracts/target_savings/src/test.rs b/contracts/target_savings/src/test.rs index 11c4b2f..85c7630 100644 --- a/contracts/target_savings/src/test.rs +++ b/contracts/target_savings/src/test.rs @@ -5,6 +5,7 @@ use super::*; use mock_pool::{MockPool, MockPoolClient}; use soroban_sdk::testutils::{Address as _, Ledger}; use soroban_sdk::{token::StellarAssetClient, Env, String}; +use test_utils::{create_token_contract, mint_tokens}; const PERIOD_WEEK: u64 = 604_800; const ONE_YEAR: u64 = 31_536_000; @@ -31,13 +32,8 @@ fn setup() -> (Env, Address, Address, Address, TargetSavingsClient<'static>) { let user = Address::generate(&env); // Stellar Asset Contract for the test token - let issuer = Address::generate(&env); - let asset = env.register_stellar_asset_contract_v2(issuer.clone()); - let token_addr = asset.address(); - let token_admin = StellarAssetClient::new(&env, &token_addr); - - // Mint some USDC to the user - token_admin.mint(&user, &10_000_000_000_i128); // 1000 USDC (7 decimals) + let (token_addr, token) = create_token_contract(&env, &admin); + mint_tokens(&env, &token, &user, 10_000_000_000_i128); // 1000 USDC (7 decimals) let contract_id = env.register(TargetSavings, (admin.clone(), token_addr.clone())); let client = TargetSavingsClient::new(&env, &contract_id); diff --git a/contracts/test_utils/Cargo.toml b/contracts/test_utils/Cargo.toml new file mode 100644 index 0000000..22873f0 --- /dev/null +++ b/contracts/test_utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test_utils" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[dependencies] +soroban-sdk = { version = "23.0.3", features = ["testutils"] } diff --git a/contracts/test_utils/src/lib.rs b/contracts/test_utils/src/lib.rs new file mode 100644 index 0000000..a0a1346 --- /dev/null +++ b/contracts/test_utils/src/lib.rs @@ -0,0 +1,52 @@ +use soroban_sdk::{ + testutils::{Address as _, Ledger, LedgerInfo}, + token::{StellarAssetClient, TokenClient}, + Address, Env, Vec, +}; + +pub fn create_token_contract<'a>(env: &'a Env, admin: &Address) -> (Address, TokenClient<'a>) { + let asset = env.register_stellar_asset_contract_v2(admin.clone()); + let address = asset.address(); + (address.clone(), TokenClient::new(env, &address)) +} + +pub fn create_funded_users( + env: &Env, + token: &TokenClient, + count: u32, + amount: i128, +) -> Vec
{ + let issuer = StellarAssetClient::new(env, &token.address); + let mut users = Vec::new(env); + for _ in 0..count { + let user = Address::generate(env); + issuer.mint(&user, &amount); + users.push_back(user); + } + users +} + +pub fn mint_tokens(env: &Env, token: &TokenClient, to: &Address, amount: i128) { + StellarAssetClient::new(env, &token.address).mint(to, &amount); +} + +pub fn set_ledger_time(env: &Env, timestamp: u64, sequence: u32) { + env.ledger().set(LedgerInfo { + timestamp, + protocol_version: 23, + sequence_number: sequence, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 3_110_400, + }); +} + +pub fn assert_solvable(env: &Env, token_address: &Address, contract: &Address, total_owed: i128) { + let token = TokenClient::new(env, token_address); + assert!( + token.balance(contract) >= total_owed, + "contract token balance is below the sum of outstanding claims" + ); +}