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..0fe8f5d9 --- /dev/null +++ b/contract/contracts/predifi-contract/src/integration_test.rs @@ -0,0 +1,200 @@ +#![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); +} + +#[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); +} + +#[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); +} 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.rs b/contract/contracts/predifi-contract/src/test.rs index f118ff47..0301cda4 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 ) { @@ -73,8 +73,8 @@ fn test_claim_winnings() { let env = Env::default(); 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 (_, 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); 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..1dfc52b2 --- /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_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, + admin: token_admin, + } + } + + pub fn mint(&self, to: &Address, amount: i128) { + self.admin.mint(to, &amount); + } +}