From 33c955b9b6cab059aed776f67a8c9bf5d5288071 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 14:41:49 +0100 Subject: [PATCH 1/6] Commit 1: Add mock token deployment utility --- .../contracts/predifi-contract/src/lib.rs | 2 ++ .../predifi-contract/src/test_utils.rs | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 contract/contracts/predifi-contract/src/test_utils.rs diff --git a/contract/contracts/predifi-contract/src/lib.rs b/contract/contracts/predifi-contract/src/lib.rs index e13ecd59..17a93bf3 100644 --- a/contract/contracts/predifi-contract/src/lib.rs +++ b/contract/contracts/predifi-contract/src/lib.rs @@ -324,3 +324,5 @@ impl PredifiContract { } mod test; +mod test_utils; +mod integration_test; diff --git a/contract/contracts/predifi-contract/src/test_utils.rs b/contract/contracts/predifi-contract/src/test_utils.rs new file mode 100644 index 00000000..fcb15608 --- /dev/null +++ b/contract/contracts/predifi-contract/src/test_utils.rs @@ -0,0 +1,27 @@ +#![cfg(test)] + +use soroban_sdk::{token, Address, Env}; + +pub struct TokenTestContext { + pub token_address: Address, + pub token: token::Client<'static>, + pub admin: token::StellarAssetClient<'static>, +} + +impl TokenTestContext { + pub fn deploy(env: &Env, admin: &Address) -> Self { + let token_contract = env.register_stellar_asset_contract(admin.clone()); + let token = token::Client::new(env, &token_contract); + let token_admin = token::StellarAssetClient::new(env, &token_contract); + + Self { + token_address: token_contract, + token, + admin: token_admin, + } + } + + pub fn mint(&self, to: &Address, amount: i128) { + self.admin.mint(to, &amount); + } +} From 51819cd051c45916f2808abdc778a5a89089b229 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 14:42:03 +0100 Subject: [PATCH 2/6] Commit 2: Setup integration test environment and market creation --- .../predifi-contract/src/integration_test.rs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 contract/contracts/predifi-contract/src/integration_test.rs diff --git a/contract/contracts/predifi-contract/src/integration_test.rs b/contract/contracts/predifi-contract/src/integration_test.rs new file mode 100644 index 00000000..9e1bb01e --- /dev/null +++ b/contract/contracts/predifi-contract/src/integration_test.rs @@ -0,0 +1,103 @@ +#![cfg(test)] + +use super::*; +use crate::test_utils::TokenTestContext; +use soroban_sdk::{testutils::Address as _, Address, Env}; + +mod dummy_access_control { + use soroban_sdk::{contract, contractimpl, Address, Env, Symbol}; + + #[contract] + pub struct DummyAccessControl; + + #[contractimpl] + impl DummyAccessControl { + pub fn grant_role(env: Env, user: Address, role: u32) { + let key = (Symbol::new(&env, "role"), user, role); + env.storage().instance().set(&key, &true); + } + + pub fn has_role(env: Env, user: Address, role: u32) -> bool { + let key = (Symbol::new(&env, "role"), user, role); + env.storage().instance().get(&key).unwrap_or(false) + } + } +} + +const ROLE_ADMIN: u32 = 0; +const ROLE_OPERATOR: u32 = 1; + +fn setup_integration(env: &Env) -> ( + PredifiContractClient<'static>, + TokenTestContext, + Address, // Admin + Address, // Operator + Address, // Treasury +) { + let admin = Address::generate(env); + let operator = Address::generate(env); + let treasury = Address::generate(env); + + let ac_id = env.register(dummy_access_control::DummyAccessControl, ()); + let ac_client = dummy_access_control::DummyAccessControlClient::new(env, &ac_id); + ac_client.grant_role(&admin, &ROLE_ADMIN); + ac_client.grant_role(&operator, &ROLE_OPERATOR); + + let contract_id = env.register(PredifiContract, ()); + let client = PredifiContractClient::new(env, &contract_id); + client.init(&ac_id, &treasury, &0u32); + + let token_ctx = TokenTestContext::deploy(env, &admin); + + (client, token_ctx, admin, operator, treasury) +} + +#[test] +fn test_full_market_lifecycle() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, token_ctx, _admin, operator, _treasury) = setup_integration(&env); + + let user1 = Address::generate(&env); + let user2 = Address::generate(&env); + let user3 = Address::generate(&env); + + token_ctx.mint(&user1, 1000); + token_ctx.mint(&user2, 1000); + token_ctx.mint(&user3, 1000); + + // 1. Create Pool + let end_time = 1000u64; + let pool_id = client.create_pool(&end_time, &token_ctx.token_address); + + // 2. Place Predictions + client.place_prediction(&user1, &pool_id, &100, &1); // User 1 bets 100 on Outcome 1 + client.place_prediction(&user2, &pool_id, &200, &2); // User 2 bets 200 on Outcome 2 + client.place_prediction(&user3, &pool_id, &300, &1); // User 3 bets 300 on Outcome 1 (Total Outcome 1 = 400) + + // Total stake = 100 + 200 + 300 = 600 + assert_eq!(token_ctx.token.balance(&client.address), 600); + + // 3. Resolve Pool + client.resolve_pool(&operator, &pool_id, &1u32); // Outcome 1 wins + + // 4. Claim Winnings + // User 1 Winnings: (100 / 400) * 600 = 150 + let w1 = client.claim_winnings(&user1, &pool_id); + assert_eq!(w1, 150); + assert_eq!(token_ctx.token.balance(&user1), 1050); // 1000 - 100 + 150 + + // User 3 Winnings: (300 / 400) * 600 = 450 + let w3 = client.claim_winnings(&user3, &pool_id); + assert_eq!(w3, 450); + assert_eq!(token_ctx.token.balance(&user3), 1150); // 1000 - 300 + 450 + + // User 2 Winnings: 0 (loser) + let w2 = client.claim_winnings(&user2, &pool_id); + assert_eq!(w2, 0); + assert_eq!(token_ctx.token.balance(&user2), 800); // 1000 - 200 + + // Contract balance should be 0 + assert_eq!(token_ctx.token.balance(&client.address), 0); +} From 6f90bb21d47ac33933795acbd5c1398046926a1c Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 14:42:16 +0100 Subject: [PATCH 3/6] Commit 3: Implement multi-user betting integration tests --- .../predifi-contract/src/integration_test.rs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/contract/contracts/predifi-contract/src/integration_test.rs b/contract/contracts/predifi-contract/src/integration_test.rs index 9e1bb01e..c79b5c3d 100644 --- a/contract/contracts/predifi-contract/src/integration_test.rs +++ b/contract/contracts/predifi-contract/src/integration_test.rs @@ -101,3 +101,58 @@ fn test_full_market_lifecycle() { // Contract balance should be 0 assert_eq!(token_ctx.token.balance(&client.address), 0); } + +#[test] +fn test_multi_user_betting_and_balance_verification() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, token_ctx, _admin, operator, _treasury) = setup_integration(&env); + + // 5 users + let users: soroban_sdk::Vec
= soroban_sdk::Vec::from_array(&env, [ + Address::generate(&env), + Address::generate(&env), + Address::generate(&env), + Address::generate(&env), + Address::generate(&env), + ]); + + for user in users.iter() { + token_ctx.mint(&user, 5000); + } + + let pool_id = client.create_pool(&2000u64, &token_ctx.token_address); + + // Bets: + // U0: 500 on 1 + // U1: 1000 on 2 + // U2: 500 on 1 + // U3: 1500 on 3 + // U4: 500 on 1 + // Total 1: 1500, Total 2: 1000, Total 3: 1500. Total Stake: 4000. + + client.place_prediction(&users.get(0).unwrap(), &pool_id, &500, &1); + client.place_prediction(&users.get(1).unwrap(), &pool_id, &1000, &2); + client.place_prediction(&users.get(2).unwrap(), &pool_id, &500, &1); + client.place_prediction(&users.get(3).unwrap(), &pool_id, &1500, &3); + client.place_prediction(&users.get(4).unwrap(), &pool_id, &500, &1); + + assert_eq!(token_ctx.token.balance(&client.address), 4000); + + // Resolve to Outcome 3 + client.resolve_pool(&operator, &pool_id, &3u32); + + // Winner: U3 + // Winnings: (1500 / 1500) * 4000 = 4000 + let w3 = client.claim_winnings(&users.get(3).unwrap(), &pool_id); + assert_eq!(w3, 4000); + assert_eq!(token_ctx.token.balance(&users.get(3).unwrap()), 7500); // 5000 - 1500 + 4000 + + // Losers check + let w0 = client.claim_winnings(&users.get(0).unwrap(), &pool_id); + assert_eq!(w0, 0); + assert_eq!(token_ctx.token.balance(&users.get(0).unwrap()), 4500); // 5000 - 500 + + assert_eq!(token_ctx.token.balance(&client.address), 0); +} From a01663288f744bc5ec2043148939fbe7e40666ce Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 14:42:26 +0100 Subject: [PATCH 4/6] Commit 4: Implement market resolution and withdrawal tests --- .../predifi-contract/src/integration_test.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/contract/contracts/predifi-contract/src/integration_test.rs b/contract/contracts/predifi-contract/src/integration_test.rs index c79b5c3d..0fe8f5d9 100644 --- a/contract/contracts/predifi-contract/src/integration_test.rs +++ b/contract/contracts/predifi-contract/src/integration_test.rs @@ -156,3 +156,45 @@ fn test_multi_user_betting_and_balance_verification() { assert_eq!(token_ctx.token.balance(&client.address), 0); } + +#[test] +fn test_market_resolution_multiple_winners() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, token_ctx, _admin, operator, _treasury) = setup_integration(&env); + + let user1 = Address::generate(&env); + let user2 = Address::generate(&env); + let user3 = Address::generate(&env); + + token_ctx.mint(&user1, 1000); + token_ctx.mint(&user2, 1000); + token_ctx.mint(&user3, 1000); + + let pool_id = client.create_pool(&1500u64, &token_ctx.token_address); + + // Bets: + // U1: 200 on 1 + // U2: 300 on 1 + // U3: 500 on 2 + // Total 1: 500, Total 2: 500. Total Stake: 1000. + + client.place_prediction(&user1, &pool_id, &200, &1); + client.place_prediction(&user2, &pool_id, &300, &1); + client.place_prediction(&user3, &pool_id, &500, &2); + + client.resolve_pool(&operator, &pool_id, &1u32); // Outcome 1 wins + + // U1 Winnings: (200 / 500) * 1000 = 400 + // U2 Winnings: (300 / 500) * 1000 = 600 + + let w1 = client.claim_winnings(&user1, &pool_id); + let w2 = client.claim_winnings(&user2, &pool_id); + + assert_eq!(w1, 400); + assert_eq!(w2, 600); + assert_eq!(token_ctx.token.balance(&user1), 1200); // 1000 - 200 + 400 + assert_eq!(token_ctx.token.balance(&user2), 1300); // 1000 - 300 + 600 + assert_eq!(token_ctx.token.balance(&client.address), 0); +} From db34a8fd3015b848bb9159287f38fb11dec4791b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 14:57:07 +0100 Subject: [PATCH 5/6] Fix clippy and deprecation warnings in tests --- contract/contracts/predifi-contract/src/test.rs | 10 +++++----- contract/contracts/predifi-contract/src/test_utils.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contract/contracts/predifi-contract/src/test.rs b/contract/contracts/predifi-contract/src/test.rs index f118ff47..d9cc7e47 100644 --- a/contract/contracts/predifi-contract/src/test.rs +++ b/contract/contracts/predifi-contract/src/test.rs @@ -31,11 +31,11 @@ const ROLE_OPERATOR: u32 = 1; fn setup( env: &Env, ) -> ( - dummy_access_control::DummyAccessControlClient, - PredifiContractClient, + dummy_access_control::DummyAccessControlClient<'_>, + PredifiContractClient<'_>, Address, // token_address - token::Client, - token::StellarAssetClient, + token::Client<'_>, + token::StellarAssetClient<'_>, Address, // treasury Address, // operator ) { @@ -74,7 +74,7 @@ fn test_claim_winnings() { env.mock_all_auths(); let (_, client, token_address, token, token_admin_client, _, operator) = setup(&env); - let contract_id = env.register(PredifiContract, ()); // get contract address for balance check + let _contract_id = env.register(PredifiContract, ()); // get contract address for balance check // Re-derive contract address from client let contract_addr = client.address.clone(); diff --git a/contract/contracts/predifi-contract/src/test_utils.rs b/contract/contracts/predifi-contract/src/test_utils.rs index fcb15608..1dfc52b2 100644 --- a/contract/contracts/predifi-contract/src/test_utils.rs +++ b/contract/contracts/predifi-contract/src/test_utils.rs @@ -10,10 +10,10 @@ pub struct TokenTestContext { impl TokenTestContext { pub fn deploy(env: &Env, admin: &Address) -> Self { - let token_contract = env.register_stellar_asset_contract(admin.clone()); + let token_contract = env.register_stellar_asset_contract_v2(admin.clone()); let token = token::Client::new(env, &token_contract); let token_admin = token::StellarAssetClient::new(env, &token_contract); - + Self { token_address: token_contract, token, From ca38b4dee9fe10cbf407e1de3e566cf712ea348d Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 14:57:35 +0100 Subject: [PATCH 6/6] Fix clippy and deprecation warnings in tests --- contract/contracts/predifi-contract/src/test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contract/contracts/predifi-contract/src/test.rs b/contract/contracts/predifi-contract/src/test.rs index d9cc7e47..0301cda4 100644 --- a/contract/contracts/predifi-contract/src/test.rs +++ b/contract/contracts/predifi-contract/src/test.rs @@ -73,7 +73,7 @@ fn test_claim_winnings() { let env = Env::default(); env.mock_all_auths(); - let (_, client, token_address, token, token_admin_client, _, operator) = setup(&env); + let (_, client, token_address, _token, token_admin_client, _, operator) = setup(&env); let _contract_id = env.register(PredifiContract, ()); // get contract address for balance check // Re-derive contract address from client let contract_addr = client.address.clone(); @@ -262,7 +262,7 @@ fn test_multiple_pools_independent() { let env = Env::default(); env.mock_all_auths(); - let (_, client, token_address, token, token_admin_client, _, operator) = setup(&env); + let (_, client, token_address, _token, token_admin_client, _, operator) = setup(&env); let user1 = Address::generate(&env); let user2 = Address::generate(&env);