@@ -140,3 +140,182 @@ fn benchmark_all_functions_summary() {
140140 std:: println!( "| {name:<14} | {cpu:>16} | {mem:>14} |" ) ;
141141 }
142142}
143+
144+ // ================================================================
145+ // Tests for get_contract_stats with multiple tokens (Issue #485)
146+ // ================================================================
147+
148+ use soroban_sdk:: { contract, contractimpl, token:: Client as TokenClient } ;
149+
150+ #[ contract]
151+ struct MockPriceOracle ;
152+
153+ #[ contractimpl]
154+ impl MockPriceOracle {
155+ pub fn get_price ( _env : soroban_sdk:: Env , _token : Address ) -> i128 {
156+ 20_000
157+ }
158+ }
159+
160+ struct MultiTokenBenchEnv {
161+ env : Env ,
162+ contract : InvoiceLiquidityContractClient < ' static > ,
163+ usdc : Address ,
164+ usdc_client : TokenClient < ' static > ,
165+ xlm : Address ,
166+ eurc : Address ,
167+ freelancer : Address ,
168+ payer : Address ,
169+ lp : Address ,
170+ admin : Address ,
171+ }
172+
173+ fn setup_multi_token ( ) -> MultiTokenBenchEnv {
174+ let env = Env :: default ( ) ;
175+ env. mock_all_auths ( ) ;
176+
177+ let mut ledger = env. ledger ( ) . get ( ) ;
178+ ledger. timestamp = 1_700_000_000 ;
179+ env. ledger ( ) . set ( ledger) ;
180+
181+ let usdc_admin = Address :: generate ( & env) ;
182+ let usdc = env. register_stellar_asset_contract_v2 ( usdc_admin. clone ( ) ) ;
183+ let xlm_admin = Address :: generate ( & env) ;
184+ let xlm = env. register_stellar_asset_contract_v2 ( xlm_admin) ;
185+ let eurc_admin = Address :: generate ( & env) ;
186+ let eurc = env. register_stellar_asset_contract_v2 ( eurc_admin) ;
187+
188+ let admin = usdc_admin. clone ( ) ;
189+ let contract_id = env. register_contract ( None , InvoiceLiquidityContract ) ;
190+ let contract = InvoiceLiquidityContractClient :: new ( & env, & contract_id) ;
191+ contract. initialize ( & admin, & usdc. address ( ) , & eurc. address ( ) , & xlm. address ( ) ) ;
192+
193+ let freelancer = Address :: generate ( & env) ;
194+ let payer = Address :: generate ( & env) ;
195+ let lp = Address :: generate ( & env) ;
196+
197+ let usdc_client = StellarAssetClient :: new ( & env, & usdc. address ( ) ) ;
198+ let usdc_token = TokenClient :: new ( & env, & usdc. address ( ) ) ;
199+ let xlm_client = StellarAssetClient :: new ( & env, & xlm. address ( ) ) ;
200+ let eurc_client = StellarAssetClient :: new ( & env, & eurc. address ( ) ) ;
201+
202+ usdc_client. mint ( & lp, & 10_000_000_000_000 ) ;
203+ usdc_client. mint ( & payer, & 10_000_000_000_000 ) ;
204+ xlm_client. mint ( & lp, & 10_000_000_000_000 ) ;
205+ xlm_client. mint ( & payer, & 10_000_000_000_000 ) ;
206+ eurc_client. mint ( & lp, & 10_000_000_000_000 ) ;
207+ eurc_client. mint ( & payer, & 10_000_000_000_000 ) ;
208+
209+ MultiTokenBenchEnv {
210+ env,
211+ contract,
212+ usdc : usdc. address ( ) ,
213+ usdc_client,
214+ xlm : xlm. address ( ) ,
215+ eurc : eurc. address ( ) ,
216+ freelancer,
217+ payer,
218+ lp,
219+ admin,
220+ }
221+ }
222+
223+ fn submit_and_fund (
224+ t : & MultiTokenBenchEnv ,
225+ token : & Address ,
226+ amount : i128 ,
227+ ) -> u64 {
228+ let due_date = t. env . ledger ( ) . timestamp ( ) + 86_400 * 30 ;
229+ let id = t. contract . submit_invoice (
230+ & t. freelancer ,
231+ & t. payer ,
232+ & amount,
233+ & due_date,
234+ & 300u32 ,
235+ token,
236+ & ReferralCode :: None ,
237+ ) ;
238+ t. contract . fund_invoice ( & t. lp , & id, & amount, & false ) ;
239+ t. contract . mark_paid ( & id, & amount) ;
240+ id
241+ }
242+
243+ #[ test]
244+ fn test_stats_tracks_usdc_volume ( ) {
245+ let t = setup_multi_token ( ) ;
246+ submit_and_fund ( & t, & t. usdc , 500_000_000 ) ;
247+
248+ let stats = t. contract . get_contract_stats ( ) ;
249+ assert_eq ! ( stats. total_volume_usdc, 500_000_000 ) ;
250+ assert_eq ! ( stats. total_invoices, 1 ) ;
251+ assert_eq ! ( stats. total_funded, 1 ) ;
252+ assert_eq ! ( stats. total_paid, 1 ) ;
253+ }
254+
255+ #[ test]
256+ fn test_stats_tracks_xlm_volume ( ) {
257+ let t = setup_multi_token ( ) ;
258+ submit_and_fund ( & t, & t. xlm , 2_000_000_000 ) ;
259+
260+ let stats = t. contract . get_contract_stats ( ) ;
261+ assert_eq ! ( stats. total_volume_xlm, 2_000_000_000 ) ;
262+ assert_eq ! ( stats. total_volume_usdc, 0 ) ;
263+ }
264+
265+ #[ test]
266+ fn test_stats_tracks_eurc_volume ( ) {
267+ let t = setup_multi_token ( ) ;
268+ submit_and_fund ( & t, & t. eurc , 750_000_000 ) ;
269+
270+ let stats = t. contract . get_contract_stats ( ) ;
271+ assert_eq ! ( stats. total_volume_eurc, 750_000_000 ) ;
272+ assert_eq ! ( stats. total_volume_usdc, 0 ) ;
273+ assert_eq ! ( stats. total_volume_xlm, 0 ) ;
274+ }
275+
276+ #[ test]
277+ fn test_stats_multi_token_aggregation ( ) {
278+ let t = setup_multi_token ( ) ;
279+ submit_and_fund ( & t, & t. usdc , 1_000_000_000 ) ;
280+ submit_and_fund ( & t, & t. xlm , 2_000_000_000 ) ;
281+ submit_and_fund ( & t, & t. eurc , 3_000_000_000 ) ;
282+
283+ let stats = t. contract . get_contract_stats ( ) ;
284+ assert_eq ! ( stats. total_invoices, 3 ) ;
285+ assert_eq ! ( stats. total_funded, 3 ) ;
286+ assert_eq ! ( stats. total_paid, 3 ) ;
287+ assert_eq ! ( stats. total_volume_usdc, 1_000_000_000 ) ;
288+ assert_eq ! ( stats. total_volume_xlm, 2_000_000_000 ) ;
289+ assert_eq ! ( stats. total_volume_eurc, 3_000_000_000 ) ;
290+ }
291+
292+ #[ test]
293+ fn test_stats_usd_normalized_with_price_oracle ( ) {
294+ let t = setup_multi_token ( ) ;
295+ submit_and_fund ( & t, & t. usdc , 1_000_000_000 ) ;
296+
297+ // Install a mock price oracle that returns a fixed price.
298+ let oracle_id = t. env . register_contract ( None , MockPriceOracle ) ;
299+ t. env . as_contract ( & t. contract . address , || {
300+ let mut config = crate :: storage:: get_config ( & t. env ) . unwrap ( ) ;
301+ config. price_oracle = Some ( oracle_id. clone ( ) ) ;
302+ crate :: storage:: set_config ( & t. env , & config) ;
303+ } ) ;
304+
305+ let stats = t. contract . get_contract_stats ( ) ;
306+ // MockPriceOracle returns 20_000; normalized = 1_000_000_000 * 20_000 / 10_000
307+ assert_eq ! (
308+ stats. total_volume_usd_normalized,
309+ 1_000_000_000 * 20_000 / 10_000
310+ ) ;
311+ }
312+
313+ #[ test]
314+ fn test_stats_without_price_oracle_returns_zero_normalized ( ) {
315+ let t = setup_multi_token ( ) ;
316+ submit_and_fund ( & t, & t. usdc , 1_000_000_000 ) ;
317+
318+ // No oracle configured — should return 0 for normalized volume.
319+ let stats = t. contract . get_contract_stats ( ) ;
320+ assert_eq ! ( stats. total_volume_usd_normalized, 0 ) ;
321+ }
0 commit comments