Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 155 additions & 27 deletions swaptrade-contracts/counter/src/analytics_dashboard_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fn test_win_rate_calculation() {
}

let summary = portfolio.get_analytics_summary(&env, user);

// Win rate should be 60% = 6_000_000 in fixed-point
assert_eq!(summary.winning_trades, 3);
assert_eq!(summary.losing_trades, 2);
Expand All @@ -77,12 +77,36 @@ fn test_realized_pnl_tracking() {
let user = Address::generate(&env);

// Record multiple trades
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1200, 1000);
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 800, 1000);
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1300, 1000);
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1200,
1000,
);
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
800,
1000,
);
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1300,
1000,
);

let summary = portfolio.get_analytics_summary(&env, user);

// Total PnL: 200 - 200 + 300 = 300
assert_eq!(summary.realized_pnl, 300);
}
Expand All @@ -93,12 +117,36 @@ fn test_best_and_worst_trade() {
let mut portfolio = Portfolio::new(&env);
let user = Address::generate(&env);

portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1500, 1000); // +500
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 700, 1000); // -300
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1200, 1000); // +200
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1500,
1000,
); // +500
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
700,
1000,
); // -300
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1200,
1000,
); // +200

let summary = portfolio.get_analytics_summary(&env, user);

assert_eq!(summary.best_trade, 500);
assert_eq!(summary.worst_trade, -300);
}
Expand All @@ -110,15 +158,39 @@ fn test_avg_trade_metrics() {
let user = Address::generate(&env);

// Record trades with different sizes
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1100, 1000);
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 2000, 2200, 1000);
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 3000, 3300, 1000);
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1100,
1000,
);
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
2000,
2200,
1000,
);
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
3000,
3300,
1000,
);

let summary = portfolio.get_analytics_summary(&env, user);

// Average trade size: (1000 + 2000 + 3000) / 3 = 2000
assert_eq!(summary.avg_trade_size, 2000);

// Average winning trade: (100 + 200 + 300) / 3 = 200
assert_eq!(summary.avg_winning_trade, 200);
}
Expand All @@ -130,7 +202,7 @@ fn test_empty_analytics_summary() {
let user = Address::generate(&env);

let summary = portfolio.get_analytics_summary(&env, user);

assert_eq!(summary.total_trades, 0);
assert_eq!(summary.winning_trades, 0);
assert_eq!(summary.losing_trades, 0);
Expand Down Expand Up @@ -159,7 +231,7 @@ fn test_sharpe_ratio_calculation() {
}

let summary = portfolio.get_analytics_summary(&env, user);

// Sharpe ratio should be positive
assert!(summary.sharpe_ratio > 0);
}
Expand All @@ -171,12 +243,36 @@ fn test_max_drawdown_calculation() {
let user = Address::generate(&env);

// Record trades that create a drawdown scenario
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 10000, 11000, 1000); // +1000
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 10000, 10500, 1000); // +500
portfolio.record_trade_with_pnl(&env, user.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 10000, 9000, 1000); // -1000
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
10000,
11000,
1000,
); // +1000
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
10000,
10500,
1000,
); // +500
portfolio.record_trade_with_pnl(
&env,
user.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
10000,
9000,
1000,
); // -1000

let summary = portfolio.get_analytics_summary(&env, user);

// Max drawdown should be calculated
assert!(summary.max_drawdown >= 0);
}
Expand Down Expand Up @@ -208,19 +304,51 @@ fn test_multiple_users_analytics() {
let user2 = Address::generate(&env);

// User 1: profitable trader
portfolio.record_trade_with_pnl(&env, user1.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1500, 1000);
portfolio.record_trade_with_pnl(&env, user1.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 1600, 1000);
portfolio.record_trade_with_pnl(
&env,
user1.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1500,
1000,
);
portfolio.record_trade_with_pnl(
&env,
user1.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
1600,
1000,
);

// User 2: losing trader
portfolio.record_trade_with_pnl(&env, user2.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 800, 1000);
portfolio.record_trade_with_pnl(&env, user2.clone(), symbol_short!("XLM"), symbol_short!("USDC"), 1000, 700, 1000);
portfolio.record_trade_with_pnl(
&env,
user2.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
800,
1000,
);
portfolio.record_trade_with_pnl(
&env,
user2.clone(),
symbol_short!("XLM"),
symbol_short!("USDC"),
1000,
700,
1000,
);

let summary1 = portfolio.get_analytics_summary(&env, user1);
let summary2 = portfolio.get_analytics_summary(&env, user2);

// User 1 should have positive PnL
assert!(summary1.realized_pnl > 0);

// User 2 should have negative PnL
assert!(summary2.realized_pnl < 0);
}
Expand Down Expand Up @@ -249,9 +377,9 @@ fn test_trade_history_storage() {
.trade_history
.get(user.clone())
.unwrap_or_else(|| Vec::new(&env));

assert_eq!(history.len(), 5);

// Check first trade
let first_trade = history.get(0).unwrap();
assert_eq!(first_trade.amount_in, 1000);
Expand Down
2 changes: 1 addition & 1 deletion swaptrade-contracts/counter/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ pub fn get_bridge_request(env: &Env, request_id: u64) -> BridgeRequest {
.persistent()
.get(&key)
.expect("Bridge request not found")
}
}
4 changes: 3 additions & 1 deletion swaptrade-contracts/counter/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use soroban_sdk::contracterror;
pub enum SwapTradeError {
// ── Admin / access control ──────────────────────────────────────────────
NotAdmin = 1,
NotAuthorized = 2,
InvalidAddress = 3,

// ── Trading / contract state ────────────────────────────────────────────
TradingPaused = 10,
Expand Down Expand Up @@ -93,4 +95,4 @@ pub enum SwapTradeError {
}

/// Alias kept for modules that still import `ContractError` by name.
pub type ContractError = SwapTradeError;
pub type ContractError = SwapTradeError;
37 changes: 37 additions & 0 deletions swaptrade-contracts/counter/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,43 @@ impl Events {
.publish((Symbol::new(env, "AdminPaused"), admin), (timestamp,));
}

pub fn fees_collected(env: &Env, token: Symbol, amount: i128, pool_id: u64) {
env.events().publish(
(Symbol::new(env, "FeesCollected"), token, pool_id),
(amount, env.ledger().timestamp()),
);
}

pub fn fee_parameters_updated(
env: &Env,
pool_id: u64,
new_fee_rate: u32,
new_treasury: Option<Address>,
) {
env.events().publish(
(Symbol::new(env, "FeeParametersUpdated"), pool_id),
(new_fee_rate, new_treasury, env.ledger().timestamp()),
);
}

pub fn fees_distributed(
env: &Env,
pool_id: u64,
token: Symbol,
amount: i128,
recipient: Address,
) {
env.events().publish(
(
Symbol::new(env, "FeesDistributed"),
token,
pool_id,
recipient,
),
(amount, env.ledger().timestamp()),
);
}

pub fn admin_resumed(env: &Env, admin: Address, timestamp: i64) {
env.events()
.publish((Symbol::new(env, "AdminResumed"), admin), (timestamp,));
Expand Down
Loading