Skip to content

Commit 195a65e

Browse files
Merge pull request #374 from DevAyomi/test/custom-token-support-tests
Test/custom token support tests
2 parents e3e62e8 + 66c9405 commit 195a65e

File tree

3 files changed

+459
-29
lines changed

3 files changed

+459
-29
lines changed
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
#![cfg(test)]
2+
3+
use crate::{PredictifyHybrid, PredictifyHybridClient};
4+
use crate::types::{OracleConfig, OracleProvider};
5+
use soroban_sdk::{
6+
testutils::{Address as _, Ledger, LedgerInfo},
7+
token::StellarAssetClient,
8+
vec, Address, Env, String, Symbol,
9+
};
10+
11+
// Test setup with flexible token configuration
12+
struct CustomTokenTestSetup {
13+
env: Env,
14+
contract_id: Address,
15+
admin: Address,
16+
token_id: Address,
17+
token_admin: Address,
18+
market_id: Symbol,
19+
}
20+
21+
impl CustomTokenTestSetup {
22+
fn new() -> Self {
23+
let env = Env::default();
24+
env.mock_all_auths();
25+
26+
// Setup admin
27+
let admin = Address::generate(&env);
28+
29+
// Register contract
30+
let contract_id = env.register(PredictifyHybrid, ());
31+
let client = PredictifyHybridClient::new(&env, &contract_id);
32+
client.initialize(&admin, &None);
33+
34+
// Setup custom token
35+
let token_admin = Address::generate(&env);
36+
let token_contract = env.register_stellar_asset_contract_v2(token_admin.clone());
37+
let token_id = token_contract.address();
38+
39+
// Configure contract to use this token
40+
env.as_contract(&contract_id, || {
41+
env.storage()
42+
.persistent()
43+
.set(&Symbol::new(&env, "TokenID"), &token_id);
44+
});
45+
46+
// Create a test market
47+
let outcomes = vec![
48+
&env,
49+
String::from_str(&env, "yes"),
50+
String::from_str(&env, "no"),
51+
];
52+
53+
let oracle_address = Address::generate(&env);
54+
let market_id = client.create_market(
55+
&admin,
56+
&String::from_str(&env, "Will it rain?"),
57+
&outcomes,
58+
&30,
59+
&OracleConfig {
60+
provider: OracleProvider::Reflector,
61+
oracle_address: oracle_address.clone(),
62+
feed_id: String::from_str(&env, "RAIN"),
63+
threshold: 1,
64+
comparison: String::from_str(&env, "gt"),
65+
},
66+
&None, // fallback_oracle_config
67+
&3600, // resolution_timeout
68+
&None, // min_pool_size
69+
&None, // bet_deadline_mins_before_end
70+
&None, // dispute_window_seconds
71+
);
72+
73+
Self {
74+
env,
75+
contract_id,
76+
admin,
77+
token_id,
78+
token_admin,
79+
market_id,
80+
}
81+
}
82+
83+
fn client(&self) -> PredictifyHybridClient<'_> {
84+
PredictifyHybridClient::new(&self.env, &self.contract_id)
85+
}
86+
87+
fn token_admin_client(&self) -> StellarAssetClient<'_> {
88+
StellarAssetClient::new(&self.env, &self.token_id)
89+
}
90+
91+
fn token_client(&self) -> soroban_sdk::token::Client<'_> {
92+
soroban_sdk::token::Client::new(&self.env, &self.token_id)
93+
}
94+
}
95+
96+
#[test]
97+
fn test_bet_placement_with_custom_token() {
98+
let setup = CustomTokenTestSetup::new();
99+
let client = setup.client();
100+
let token_admin_client = setup.token_admin_client();
101+
let token_client = setup.token_client();
102+
103+
let user = Address::generate(&setup.env);
104+
let bet_amount = 10_000_000; // 1 token
105+
106+
// Mint tokens to user
107+
token_admin_client.mint(&user, &100_000_000); // 10 tokens
108+
109+
// Place bet
110+
client.place_bet(
111+
&user,
112+
&setup.market_id,
113+
&String::from_str(&setup.env, "yes"),
114+
&bet_amount,
115+
);
116+
117+
// Verify balance decreased
118+
assert_eq!(token_client.balance(&user), 90_000_000);
119+
120+
// Verify contract balance increased
121+
assert_eq!(token_client.balance(&setup.contract_id), bet_amount);
122+
}
123+
124+
#[test]
125+
fn test_insufficient_balance() {
126+
let setup = CustomTokenTestSetup::new();
127+
let client = setup.client();
128+
let token_admin_client = setup.token_admin_client();
129+
130+
let user = Address::generate(&setup.env);
131+
let bet_amount = 10_000_000;
132+
133+
// Mint LESS tokens than bet amount
134+
token_admin_client.mint(&user, &5_000_000); // 0.5 tokens
135+
136+
// Attempt to place bet using try_place_bet to avoid panics/segfaults
137+
let result = client.try_place_bet(
138+
&user,
139+
&setup.market_id,
140+
&String::from_str(&setup.env, "yes"),
141+
&bet_amount,
142+
);
143+
144+
// Should return an error (likely HostError due to transfer failure)
145+
assert!(result.is_err());
146+
}
147+
148+
#[test]
149+
fn test_payout_distribution_flow() {
150+
let setup = CustomTokenTestSetup::new();
151+
let client = setup.client();
152+
let token_admin_client = setup.token_admin_client();
153+
let token_client = setup.token_client();
154+
155+
let user_winner = Address::generate(&setup.env);
156+
let user_loser = Address::generate(&setup.env);
157+
let bet_amount = 10_000_000;
158+
159+
// Mint tokens
160+
token_admin_client.mint(&user_winner, &100_000_000);
161+
token_admin_client.mint(&user_loser, &100_000_000);
162+
163+
// Place bets
164+
client.place_bet(
165+
&user_winner,
166+
&setup.market_id,
167+
&String::from_str(&setup.env, "yes"),
168+
&bet_amount,
169+
);
170+
171+
client.place_bet(
172+
&user_loser,
173+
&setup.market_id,
174+
&String::from_str(&setup.env, "no"),
175+
&bet_amount,
176+
);
177+
178+
// Advance time to end market but NOT past dispute window
179+
let market = client.get_market(&setup.market_id).unwrap();
180+
setup.env.ledger().set(LedgerInfo {
181+
timestamp: market.end_time + 1,
182+
protocol_version: 22,
183+
sequence_number: setup.env.ledger().sequence(),
184+
network_id: Default::default(),
185+
base_reserve: 10,
186+
min_temp_entry_ttl: 1,
187+
min_persistent_entry_ttl: 1,
188+
max_entry_ttl: 10000,
189+
});
190+
191+
// Resolve market manually (Admin wins "yes")
192+
client.resolve_market_manual(
193+
&setup.admin,
194+
&setup.market_id,
195+
&String::from_str(&setup.env, "yes"),
196+
);
197+
198+
// Verify market is resolved but no payout distributed yet (due to dispute window)
199+
let market_after = client.get_market(&setup.market_id).unwrap();
200+
assert!(market_after.winning_outcomes.is_some());
201+
assert_eq!(token_client.balance(&user_winner), 90_000_000); // Only initial balance minus bet
202+
203+
// Advance time past dispute window (24h default)
204+
setup.env.ledger().set(LedgerInfo {
205+
timestamp: market.end_time + 86400 + 1,
206+
protocol_version: 22,
207+
sequence_number: setup.env.ledger().sequence(),
208+
network_id: Default::default(),
209+
base_reserve: 10,
210+
min_temp_entry_ttl: 1,
211+
min_persistent_entry_ttl: 1,
212+
max_entry_ttl: 10000,
213+
});
214+
215+
// Distribute payouts
216+
let total_distributed = client.distribute_payouts(&setup.market_id);
217+
assert!(total_distributed > 0);
218+
219+
// Verify winner received payout (Original stake + winnings - fees)
220+
// Winner staked 10M, Loser staked 10M. Total pool 20M.
221+
// Fee 2% = 400k. Payout pool = 19.6M.
222+
// Winner share = 100%. Payout = 19.6M.
223+
// Final balance = 90M + 19.6M = 109.6M
224+
let winner_balance = token_client.balance(&user_winner);
225+
assert_eq!(winner_balance, 109_600_000);
226+
227+
// Loser gets nothing
228+
assert_eq!(token_client.balance(&user_loser), 90_000_000);
229+
}
230+
231+
#[test]
232+
fn test_switch_token_support() {
233+
// This test verifies that we can switch the token used by the contract
234+
// by updating the TokenID storage key.
235+
236+
let setup = CustomTokenTestSetup::new();
237+
let token1_client = setup.token_client();
238+
let client = setup.client();
239+
240+
// 1. Verify betting with Token 1
241+
let user1 = Address::generate(&setup.env);
242+
setup.token_admin_client().mint(&user1, &10_000_000);
243+
244+
client.place_bet(
245+
&user1,
246+
&setup.market_id,
247+
&String::from_str(&setup.env, "yes"),
248+
&10_000_000,
249+
);
250+
assert_eq!(token1_client.balance(&user1), 0);
251+
252+
// 2. Create and switch to Token 2
253+
let token2_admin = Address::generate(&setup.env);
254+
let token2_contract = setup.env.register_stellar_asset_contract_v2(token2_admin.clone());
255+
let token2_id = token2_contract.address();
256+
let token2_admin_client = StellarAssetClient::new(&setup.env, &token2_id);
257+
let token2_client = soroban_sdk::token::Client::new(&setup.env, &token2_id);
258+
259+
// Update contract storage to use Token 2
260+
setup.env.as_contract(&setup.contract_id, || {
261+
setup.env.storage()
262+
.persistent()
263+
.set(&Symbol::new(&setup.env, "TokenID"), &token2_id);
264+
});
265+
266+
// 3. Verify betting with Token 2
267+
let user2 = Address::generate(&setup.env);
268+
token2_admin_client.mint(&user2, &20_000_000);
269+
270+
// Bet on existing one is fine.
271+
client.place_bet(
272+
&user2,
273+
&setup.market_id,
274+
&String::from_str(&setup.env, "no"),
275+
&20_000_000,
276+
);
277+
278+
// Verify balances for Token 2
279+
assert_eq!(token2_client.balance(&user2), 0);
280+
assert_eq!(token2_client.balance(&setup.contract_id), 20_000_000);
281+
282+
// Verify Token 1 balances are unchanged
283+
assert_eq!(token1_client.balance(&setup.contract_id), 10_000_000);
284+
}
285+
286+
#[test]
287+
fn test_cancel_refund_custom_token() {
288+
let setup = CustomTokenTestSetup::new();
289+
let client = setup.client();
290+
let token_admin_client = setup.token_admin_client();
291+
let token_client = setup.token_client();
292+
293+
let user = Address::generate(&setup.env);
294+
let bet_amount = 10_000_000;
295+
296+
// Mint tokens
297+
token_admin_client.mint(&user, &20_000_000);
298+
299+
// Place bet
300+
client.place_bet(
301+
&user,
302+
&setup.market_id,
303+
&String::from_str(&setup.env, "yes"),
304+
&bet_amount,
305+
);
306+
307+
// Verify balance before cancellation
308+
assert_eq!(token_client.balance(&user), 10_000_000);
309+
assert_eq!(token_client.balance(&setup.contract_id), bet_amount);
310+
311+
// Cancel event
312+
client.cancel_event(
313+
&setup.admin,
314+
&setup.market_id,
315+
&Some(String::from_str(&setup.env, "Technical issue")),
316+
);
317+
318+
// Verify balance after cancellation (should be refunded)
319+
assert_eq!(token_client.balance(&user), 20_000_000);
320+
assert_eq!(token_client.balance(&setup.contract_id), 0);
321+
}
322+
323+
#[test]
324+
fn test_fee_collection_custom_token() {
325+
let setup = CustomTokenTestSetup::new();
326+
let client = setup.client();
327+
let token_admin_client = setup.token_admin_client();
328+
let token_client = setup.token_client();
329+
330+
let user = Address::generate(&setup.env);
331+
let bet_amount = 100_000_000;
332+
333+
// Mint tokens
334+
token_admin_client.mint(&user, &bet_amount);
335+
336+
// Place bet
337+
client.place_bet(
338+
&user,
339+
&setup.market_id,
340+
&String::from_str(&setup.env, "yes"),
341+
&bet_amount,
342+
);
343+
344+
// Advance time to end market
345+
let market = client.get_market(&setup.market_id).unwrap();
346+
setup.env.ledger().set(LedgerInfo {
347+
timestamp: market.end_time + 1,
348+
protocol_version: 22,
349+
sequence_number: setup.env.ledger().sequence(),
350+
network_id: Default::default(),
351+
base_reserve: 10,
352+
min_temp_entry_ttl: 1,
353+
min_persistent_entry_ttl: 1,
354+
max_entry_ttl: 10000,
355+
});
356+
357+
// Resolve market
358+
client.resolve_market_manual(
359+
&setup.admin,
360+
&setup.market_id,
361+
&String::from_str(&setup.env, "yes"),
362+
);
363+
364+
// Collect fees
365+
let fee_amount = client.collect_fees(
366+
&setup.admin,
367+
&setup.market_id,
368+
);
369+
370+
// Verify admin balance has not increased yet (as fees are in vault)
371+
let admin_balance_before = token_client.balance(&setup.admin);
372+
assert_eq!(admin_balance_before, 0);
373+
374+
// Withdraw fees from vault
375+
let withdrawn_amount = client.withdraw_fees(&setup.admin, &fee_amount);
376+
assert_eq!(withdrawn_amount, fee_amount);
377+
378+
// Verify admin balance increased by withdrawn amount
379+
let admin_balance_after = token_client.balance(&setup.admin);
380+
assert_eq!(admin_balance_after, fee_amount);
381+
}

0 commit comments

Comments
 (0)