diff --git a/swaptrade-contracts/counter/src/analytics_dashboard_tests.rs b/swaptrade-contracts/counter/src/analytics_dashboard_tests.rs
index bfc3899..ddaa805 100644
--- a/swaptrade-contracts/counter/src/analytics_dashboard_tests.rs
+++ b/swaptrade-contracts/counter/src/analytics_dashboard_tests.rs
@@ -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);
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
diff --git a/swaptrade-contracts/counter/src/bridge.rs b/swaptrade-contracts/counter/src/bridge.rs
index 5acf858..baa9eb0 100644
--- a/swaptrade-contracts/counter/src/bridge.rs
+++ b/swaptrade-contracts/counter/src/bridge.rs
@@ -107,4 +107,4 @@ pub fn get_bridge_request(env: &Env, request_id: u64) -> BridgeRequest {
.persistent()
.get(&key)
.expect("Bridge request not found")
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/errors.rs b/swaptrade-contracts/counter/src/errors.rs
index 479456f..94b1be5 100644
--- a/swaptrade-contracts/counter/src/errors.rs
+++ b/swaptrade-contracts/counter/src/errors.rs
@@ -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,
@@ -93,4 +95,4 @@ pub enum SwapTradeError {
}
/// Alias kept for modules that still import `ContractError` by name.
-pub type ContractError = SwapTradeError;
\ No newline at end of file
+pub type ContractError = SwapTradeError;
diff --git a/swaptrade-contracts/counter/src/events.rs b/swaptrade-contracts/counter/src/events.rs
index 6bcf0b9..435acd9 100644
--- a/swaptrade-contracts/counter/src/events.rs
+++ b/swaptrade-contracts/counter/src/events.rs
@@ -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
,
+ ) {
+ 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,));
diff --git a/swaptrade-contracts/counter/src/farming.rs b/swaptrade-contracts/counter/src/farming.rs
index 11c7f03..e7135fa 100644
--- a/swaptrade-contracts/counter/src/farming.rs
+++ b/swaptrade-contracts/counter/src/farming.rs
@@ -1,9 +1,9 @@
+use crate::errors::SwapTradeError;
/// Yield Farming / Liquidity Mining Module
///
/// Rewards users for staking LP tokens over time using an accumulator-per-share
/// pattern that ensures proportional reward distribution.
use soroban_sdk::{contracttype, symbol_short, Address, Env, Map};
-use crate::errors::SwapTradeError;
// ────────────────────────────────────────────────────────────────────────────
// Constants
@@ -78,7 +78,8 @@ impl FarmingManager {
/// Get the current admin address
fn get_admin(env: &Env) -> Address {
- env.storage().persistent()
+ env.storage()
+ .persistent()
.get(&FarmingKey::Admin)
.expect("Farming module not initialized")
}
@@ -89,7 +90,9 @@ impl FarmingManager {
/// Update the pool's reward accumulator - must be called before any state changes
fn update_pool_accumulator(env: &Env, pool_id: u64) -> Result<(), SwapTradeError> {
- let mut pool_state = env.storage().persistent()
+ let mut pool_state = env
+ .storage()
+ .persistent()
.get(&FarmingKey::PoolState(pool_id))
.unwrap_or_else(|| PoolFarmState {
total_staked_lp: 0,
@@ -102,48 +105,61 @@ impl FarmingManager {
if pool_state.total_staked_lp == 0 || pool_state.emission_rate == 0 {
// No stakers or no emissions, just update the timestamp
pool_state.last_update_timestamp = env.ledger().timestamp();
- env.storage().persistent().set(&FarmingKey::PoolState(pool_id), &pool_state);
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::PoolState(pool_id), &pool_state);
return Ok(());
}
let current_timestamp = env.ledger().timestamp();
let time_elapsed = current_timestamp - pool_state.last_update_timestamp;
-
+
if time_elapsed == 0 {
return Ok(());
}
// Calculate rewards generated during this period
let new_rewards = (time_elapsed as i128) * pool_state.emission_rate;
-
+
// Calculate the additional reward per share (scaled to maintain precision)
- let reward_per_share_increase = (new_rewards * Self::SCALE_FACTOR) / pool_state.total_staked_lp;
-
+ let reward_per_share_increase =
+ (new_rewards * Self::SCALE_FACTOR) / pool_state.total_staked_lp;
+
// Update the accumulator
pool_state.reward_per_share_accumulator += reward_per_share_increase;
pool_state.last_update_timestamp = current_timestamp;
pool_state.total_rewards_distributed += new_rewards;
// Save the updated pool state
- env.storage().persistent().set(&FarmingKey::PoolState(pool_id), &pool_state);
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::PoolState(pool_id), &pool_state);
// Update global total
- let mut global_total: i128 = env.storage().persistent()
+ let mut global_total: i128 = env
+ .storage()
+ .persistent()
.get(&FarmingKey::TotalRewardsDistributed)
.unwrap_or(0);
global_total += new_rewards;
- env.storage().persistent().set(&FarmingKey::TotalRewardsDistributed, &global_total);
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::TotalRewardsDistributed, &global_total);
Ok(())
}
/// Update a user's pending rewards based on the current pool accumulator
fn update_user_position(env: &Env, pool_id: u64, user: Address) -> Result<(), SwapTradeError> {
- let pool_state = env.storage().persistent()
+ let pool_state = env
+ .storage()
+ .persistent()
.get(&FarmingKey::PoolState(pool_id))
.ok_or(SwapTradeError::LPPositionNotFound)?;
- let mut user_position = env.storage().persistent()
+ let mut user_position = env
+ .storage()
+ .persistent()
.get(&FarmingKey::UserPosition(pool_id, user.clone()))
.unwrap_or_else(|| UserFarmPosition {
staked_lp_amount: 0,
@@ -155,15 +171,19 @@ impl FarmingManager {
if user_position.staked_lp_amount > 0 {
// Calculate the accumulated rewards since last update
- let accumulated_rewards = ((pool_state.reward_per_share_accumulator - user_position.reward_per_share_debt)
- * user_position.staked_lp_amount) / Self::SCALE_FACTOR;
+ let accumulated_rewards = ((pool_state.reward_per_share_accumulator
+ - user_position.reward_per_share_debt)
+ * user_position.staked_lp_amount)
+ / Self::SCALE_FACTOR;
user_position.pending_rewards += accumulated_rewards;
}
// Update the user's debt to the current pool accumulator
user_position.reward_per_share_debt = pool_state.reward_per_share_accumulator;
-
- env.storage().persistent().set(&FarmingKey::UserPosition(pool_id, user), &user_position);
+
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::UserPosition(pool_id, user), &user_position);
Ok(())
}
@@ -186,7 +206,9 @@ impl FarmingManager {
Self::update_user_position(env, pool_id, user.clone())?;
// Get and update pool state
- let mut pool_state = env.storage().persistent()
+ let mut pool_state = env
+ .storage()
+ .persistent()
.get(&FarmingKey::PoolState(pool_id))
.unwrap_or_else(|| PoolFarmState {
total_staked_lp: 0,
@@ -197,7 +219,9 @@ impl FarmingManager {
});
// Get and update user position
- let mut user_position = env.storage().persistent()
+ let mut user_position = env
+ .storage()
+ .persistent()
.get(&FarmingKey::UserPosition(pool_id, user.clone()))
.unwrap_or_else(|| UserFarmPosition {
staked_lp_amount: 0,
@@ -213,8 +237,13 @@ impl FarmingManager {
user_position.is_active = true;
// Save updated states
- env.storage().persistent().set(&FarmingKey::PoolState(pool_id), &pool_state);
- env.storage().persistent().set(&FarmingKey::UserPosition(pool_id, user.clone()), &user_position);
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::PoolState(pool_id), &pool_state);
+ env.storage().persistent().set(
+ &FarmingKey::UserPosition(pool_id, user.clone()),
+ &user_position,
+ );
// Emit event
env.events().publish(
@@ -243,7 +272,9 @@ impl FarmingManager {
Self::update_user_position(env, pool_id, user.clone())?;
// Get user position
- let mut user_position = env.storage().persistent()
+ let mut user_position = env
+ .storage()
+ .persistent()
.get(&FarmingKey::UserPosition(pool_id, user.clone()))
.ok_or(SwapTradeError::LPPositionNotFound)?;
@@ -252,21 +283,28 @@ impl FarmingManager {
}
// Get and update pool state
- let mut pool_state = env.storage().persistent()
+ let mut pool_state = env
+ .storage()
+ .persistent()
.get(&FarmingKey::PoolState(pool_id))
.ok_or(SwapTradeError::LPPositionNotFound)?;
// Update totals
pool_state.total_staked_lp -= amount;
user_position.staked_lp_amount -= amount;
-
+
if user_position.staked_lp_amount == 0 {
user_position.is_active = false;
}
// Save updated states
- env.storage().persistent().set(&FarmingKey::PoolState(pool_id), &pool_state);
- env.storage().persistent().set(&FarmingKey::UserPosition(pool_id, user.clone()), &user_position);
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::PoolState(pool_id), &pool_state);
+ env.storage().persistent().set(
+ &FarmingKey::UserPosition(pool_id, user.clone()),
+ &user_position,
+ );
// Emit event
env.events().publish(
@@ -290,7 +328,9 @@ impl FarmingManager {
Self::update_user_position(env, pool_id, user.clone())?;
// Get user position
- let mut user_position = env.storage().persistent()
+ let mut user_position = env
+ .storage()
+ .persistent()
.get(&FarmingKey::UserPosition(pool_id, user.clone()))
.ok_or(SwapTradeError::LPPositionNotFound)?;
@@ -303,7 +343,10 @@ impl FarmingManager {
user_position.pending_rewards = 0; // Zero out pending rewards after claim
// Save the updated position
- env.storage().persistent().set(&FarmingKey::UserPosition(pool_id, user.clone()), &user_position);
+ env.storage().persistent().set(
+ &FarmingKey::UserPosition(pool_id, user.clone()),
+ &user_position,
+ );
// Emit event
env.events().publish(
@@ -325,7 +368,9 @@ impl FarmingManager {
Self::update_pool_accumulator(env, pool_id)?;
Self::update_user_position(env, pool_id, user.clone())?;
- let user_position = env.storage().persistent()
+ let user_position = env
+ .storage()
+ .persistent()
.get(&FarmingKey::UserPosition(pool_id, user))
.ok_or(SwapTradeError::LPPositionNotFound)?;
@@ -340,7 +385,7 @@ impl FarmingManager {
admin: Address,
) -> Result<(), SwapTradeError> {
admin.require_auth();
-
+
let current_admin = Self::get_admin(env);
if admin != current_admin {
return Err(SwapTradeError::NotAdmin);
@@ -355,7 +400,9 @@ impl FarmingManager {
Self::update_pool_accumulator(env, pool_id)?;
// Get and update pool state
- let mut pool_state = env.storage().persistent()
+ let mut pool_state = env
+ .storage()
+ .persistent()
.get(&FarmingKey::PoolState(pool_id))
.unwrap_or_else(|| PoolFarmState {
total_staked_lp: 0,
@@ -367,7 +414,9 @@ impl FarmingManager {
let old_rate = pool_state.emission_rate;
pool_state.emission_rate = new_emission_rate;
- env.storage().persistent().set(&FarmingKey::PoolState(pool_id), &pool_state);
+ env.storage()
+ .persistent()
+ .set(&FarmingKey::PoolState(pool_id), &pool_state);
// Emit event
env.events().publish(
@@ -382,9 +431,10 @@ impl FarmingManager {
pub fn get_pool_state(env: &Env, pool_id: u64) -> Result {
// Update before returning to ensure latest state
Self::update_pool_accumulator(env, pool_id)?;
-
- env.storage().persistent()
+
+ env.storage()
+ .persistent()
.get(&FarmingKey::PoolState(pool_id))
.ok_or(SwapTradeError::LPPositionNotFound)
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/farming_tests.rs b/swaptrade-contracts/counter/src/farming_tests.rs
index 19417ad..f1b1668 100644
--- a/swaptrade-contracts/counter/src/farming_tests.rs
+++ b/swaptrade-contracts/counter/src/farming_tests.rs
@@ -1,8 +1,8 @@
#[cfg(test)]
use super::*;
-use soroban_sdk::{Env, Address, testutils::Address as _};
-use crate::farming::FarmingManager;
use crate::errors::SwapTradeError;
+use crate::farming::FarmingManager;
+use soroban_sdk::{testutils::Address as _, Address, Env};
#[test]
fn test_farming_proportional_rewards() {
@@ -10,40 +10,40 @@ fn test_farming_proportional_rewards() {
let admin = Address::generate(&env);
let user1 = Address::generate(&env);
let user2 = Address::generate(&env);
-
+
// Initialize farming module
FarmingManager::initialize(&env, admin.clone());
-
+
let pool_id: u64 = 1;
let emission_rate: i128 = 10; // 10 reward tokens per second
-
+
// Admin sets emission rate
FarmingManager::set_farm_emission_rate(&env, pool_id, emission_rate, admin.clone()).unwrap();
-
+
// User1 stakes 100 LP tokens
user1.require_auth();
FarmingManager::stake_lp(&env, pool_id, 100, user1.clone()).unwrap();
-
+
// Advance time by 100 seconds
env.ledger().set_timestamp(env.ledger().timestamp() + 100);
-
+
// User2 stakes 200 LP tokens (total staked now 300)
user2.require_auth();
FarmingManager::stake_lp(&env, pool_id, 200, user2.clone()).unwrap();
-
+
// Advance time by another 100 seconds (total 200 seconds)
env.ledger().set_timestamp(env.ledger().timestamp() + 100);
-
+
// Calculate expected rewards:
// User1: first 100s: 100% of 10/s * 100s = 1000
// next 100s: 1/3 of 10/s * 100s = ~333.333
// Total user1: 1333
// User2: only second 100s: 2/3 of 10/s *100s = ~666.666
// Total user2: 666
-
+
let pending1 = FarmingManager::get_pending_farm_rewards(&env, pool_id, user1.clone()).unwrap();
let pending2 = FarmingManager::get_pending_farm_rewards(&env, pool_id, user2.clone()).unwrap();
-
+
assert!(pending1 > pending2);
assert_eq!(pending1, 1333);
assert_eq!(pending2, 666);
@@ -55,27 +55,27 @@ fn test_claim_and_double_claim() {
let env = Env::default();
let admin = Address::generate(&env);
let user = Address::generate(&env);
-
+
FarmingManager::initialize(&env, admin.clone());
-
+
let pool_id: u64 = 1;
FarmingManager::set_farm_emission_rate(&env, pool_id, 10, admin.clone()).unwrap();
-
+
// User stakes 100 LP
user.require_auth();
FarmingManager::stake_lp(&env, pool_id, 100, user.clone()).unwrap();
-
+
// Advance time
env.ledger().set_timestamp(env.ledger().timestamp() + 100);
-
+
// First claim should work
let claimed = FarmingManager::claim_farm_rewards(&env, pool_id, user.clone()).unwrap();
assert_eq!(claimed, 1000);
-
+
// Second claim should return error (no rewards left)
let result = FarmingManager::claim_farm_rewards(&env, pool_id, user.clone());
assert!(matches!(result, Err(SwapTradeError::NoClaimableBonuses)));
-
+
// Pending rewards should be 0
let pending = FarmingManager::get_pending_farm_rewards(&env, pool_id, user.clone()).unwrap();
assert_eq!(pending, 0);
@@ -86,26 +86,26 @@ fn test_unstake_pays_accrued_rewards() {
let env = Env::default();
let admin = Address::generate(&env);
let user = Address::generate(&env);
-
+
FarmingManager::initialize(&env, admin.clone());
-
+
let pool_id: u64 = 1;
FarmingManager::set_farm_emission_rate(&env, pool_id, 10, admin.clone()).unwrap();
-
+
// Stake
user.require_auth();
FarmingManager::stake_lp(&env, pool_id, 100, user.clone()).unwrap();
-
+
// Wait 50 seconds
env.ledger().set_timestamp(env.ledger().timestamp() + 50);
-
+
// Unstake half
FarmingManager::unstake_lp(&env, pool_id, 50, user.clone()).unwrap();
-
+
// Check rewards are accrued
let pending = FarmingManager::get_pending_farm_rewards(&env, pool_id, user.clone()).unwrap();
assert_eq!(pending, 500);
-
+
// Wait another 50 seconds - only 50 LP still staked, so should accumulate another 500
env.ledger().set_timestamp(env.ledger().timestamp() + 50);
let pending = FarmingManager::get_pending_farm_rewards(&env, pool_id, user.clone()).unwrap();
@@ -117,25 +117,25 @@ fn test_emission_rate_change_only_affects_future() {
let env = Env::default();
let admin = Address::generate(&env);
let user = Address::generate(&env);
-
+
FarmingManager::initialize(&env, admin.clone());
-
+
let pool_id: u64 = 1;
// Initial rate: 10 per second
FarmingManager::set_farm_emission_rate(&env, pool_id, 10, admin.clone()).unwrap();
-
+
user.require_auth();
FarmingManager::stake_lp(&env, pool_id, 100, user.clone()).unwrap();
-
+
// First 100 seconds with rate 10/s: should get 1000 rewards
env.ledger().set_timestamp(env.ledger().timestamp() + 100);
-
+
// Admin updates rate to 20 per second
FarmingManager::set_farm_emission_rate(&env, pool_id, 20, admin.clone()).unwrap();
-
+
// Another 100 seconds with rate 20/s: should get another 2000 rewards
env.ledger().set_timestamp(env.ledger().timestamp() + 100);
-
+
let pending = FarmingManager::get_pending_farm_rewards(&env, pool_id, user.clone()).unwrap();
assert_eq!(pending, 3000); // Total 3000 = 1000 + 2000
}
@@ -145,12 +145,12 @@ fn test_invalid_stake_amount() {
let env = Env::default();
let admin = Address::generate(&env);
let user = Address::generate(&env);
-
+
FarmingManager::initialize(&env, admin.clone());
-
+
let pool_id: u64 = 1;
FarmingManager::set_farm_emission_rate(&env, pool_id, 10, admin.clone()).unwrap();
-
+
// Try to stake less than minimum
user.require_auth();
let result = FarmingManager::stake_lp(&env, pool_id, 50, user.clone());
@@ -162,11 +162,11 @@ fn test_non_admin_cannot_set_emission_rate() {
let env = Env::default();
let admin = Address::generate(&env);
let non_admin = Address::generate(&env);
-
+
FarmingManager::initialize(&env, admin.clone());
-
+
let pool_id: u64 = 1;
non_admin.require_auth();
let result = FarmingManager::set_farm_emission_rate(&env, pool_id, 20, non_admin.clone());
assert!(matches!(result, Err(SwapTradeError::NotAdmin)));
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/governance_params.rs b/swaptrade-contracts/counter/src/governance_params.rs
index 4a5764a..64623c7 100644
--- a/swaptrade-contracts/counter/src/governance_params.rs
+++ b/swaptrade-contracts/counter/src/governance_params.rs
@@ -163,9 +163,10 @@ impl GovernanceParams {
}
// Commit only the targeted parameter — isolated write (#156).
- env.storage()
- .persistent()
- .set(&GovParamStorageKey::ParamValue(update.param.clone()), &update.new_value);
+ env.storage().persistent().set(
+ &GovParamStorageKey::ParamValue(update.param.clone()),
+ &update.new_value,
+ );
update.executed = true;
env.storage()
@@ -247,13 +248,7 @@ mod tests {
fn test_propose_and_execute_after_timelock() {
let (env, contract_id, admin) = setup();
env.as_contract(&contract_id, || {
- let id = GovernanceParams::propose_update(
- &env,
- &admin,
- ParamKey::FeeBps,
- 300,
- )
- .unwrap();
+ let id = GovernanceParams::propose_update(&env, &admin, ParamKey::FeeBps, 300).unwrap();
// Execution before delay must fail.
assert_eq!(
@@ -319,8 +314,14 @@ mod tests {
GovernanceParams::execute_update(&env, &admin, id).unwrap();
// MaxSwapAmount must remain unset — no side effects.
- assert_eq!(GovernanceParams::get_param(&env, ParamKey::MaxSwapAmount), None);
- assert_eq!(GovernanceParams::get_param(&env, ParamKey::FeeBps), Some(200));
+ assert_eq!(
+ GovernanceParams::get_param(&env, ParamKey::MaxSwapAmount),
+ None
+ );
+ assert_eq!(
+ GovernanceParams::get_param(&env, ParamKey::FeeBps),
+ Some(200)
+ );
});
}
}
diff --git a/swaptrade-contracts/counter/src/governance_system.rs b/swaptrade-contracts/counter/src/governance_system.rs
index 2c78ee0..14d5891 100644
--- a/swaptrade-contracts/counter/src/governance_system.rs
+++ b/swaptrade-contracts/counter/src/governance_system.rs
@@ -1,7 +1,7 @@
-use soroban_sdk::{contracttype, Address, Env, Symbol, Vec, Map};
use crate::errors::SwapTradeError;
-use crate::governance_types::*;
use crate::events::Events;
+use crate::governance_types::*;
+use soroban_sdk::{contracttype, Address, Env, Map, Symbol, Vec};
/// On-chain governance system for SwapTrade
pub struct GovernanceSystem;
@@ -26,7 +26,8 @@ impl GovernanceSystem {
}
// Check proposal cooldown
- let last_proposal_time = env.storage()
+ let last_proposal_time = env
+ .storage()
.persistent()
.get(&GovernanceKey::VoterLastProposal(proposer.clone()))
.unwrap_or(0u64);
@@ -58,7 +59,8 @@ impl GovernanceSystem {
votes_against: 0,
votes_abstain: 0,
total_voting_power: Self::get_total_voting_power(env),
- quorum_required: (config.quorum_threshold as u128 * Self::get_total_voting_power(env)) / 10000,
+ quorum_required: (config.quorum_threshold as u128 * Self::get_total_voting_power(env))
+ / 10000,
approval_threshold: config.approval_threshold,
executed: false,
};
@@ -69,9 +71,10 @@ impl GovernanceSystem {
.set(&GovernanceKey::Proposal(proposal_id), &proposal);
// Update last proposal time
- env.storage()
- .persistent()
- .set(&GovernanceKey::VoterLastProposal(proposer.clone()), ¤t_time);
+ env.storage().persistent().set(
+ &GovernanceKey::VoterLastProposal(proposer.clone()),
+ ¤t_time,
+ );
// Emit event
env.events().publish(
@@ -105,7 +108,8 @@ impl GovernanceSystem {
// Check if voter already voted
let votes_key = GovernanceKey::ProposalVotes(proposal_id);
- let mut votes: Map = env.storage()
+ let mut votes: Map = env
+ .storage()
.persistent()
.get(&votes_key)
.unwrap_or_else(|| Map::new(env));
@@ -139,7 +143,9 @@ impl GovernanceSystem {
// Store updated data
env.storage().persistent().set(&votes_key, &votes);
- env.storage().persistent().set(&GovernanceKey::Proposal(proposal_id), &proposal);
+ env.storage()
+ .persistent()
+ .set(&GovernanceKey::Proposal(proposal_id), &proposal);
// Emit event
env.events().publish(
@@ -192,7 +198,9 @@ impl GovernanceSystem {
} else {
// Set execution time if not set
proposal.execution_time = Some(current_time + config.execution_delay);
- env.storage().persistent().set(&GovernanceKey::Proposal(proposal_id), &proposal);
+ env.storage()
+ .persistent()
+ .set(&GovernanceKey::Proposal(proposal_id), &proposal);
return Err(SwapTradeError::InvalidAmount); // Not ready for execution yet
}
@@ -202,7 +210,9 @@ impl GovernanceSystem {
// Mark as executed
proposal.executed = true;
proposal.status = ProposalStatus::Executed;
- env.storage().persistent().set(&GovernanceKey::Proposal(proposal_id), &proposal);
+ env.storage()
+ .persistent()
+ .set(&GovernanceKey::Proposal(proposal_id), &proposal);
// Emit event
env.events().publish(
@@ -235,7 +245,9 @@ impl GovernanceSystem {
}
proposal.status = ProposalStatus::Cancelled;
- env.storage().persistent().set(&GovernanceKey::Proposal(proposal_id), &proposal);
+ env.storage()
+ .persistent()
+ .set(&GovernanceKey::Proposal(proposal_id), &proposal);
// Emit event
env.events().publish(
@@ -271,7 +283,11 @@ impl GovernanceSystem {
}
/// Set governance configuration (admin only)
- pub fn set_config(env: &Env, admin: &Address, config: &GovernanceConfig) -> Result<(), SwapTradeError> {
+ pub fn set_config(
+ env: &Env,
+ admin: &Address,
+ config: &GovernanceConfig,
+ ) -> Result<(), SwapTradeError> {
admin.require_auth();
crate::admin::require_admin(env, admin)?;
@@ -283,7 +299,9 @@ impl GovernanceSystem {
return Err(SwapTradeError::InvalidAmount);
}
- env.storage().persistent().set(&GovernanceKey::Config, config);
+ env.storage()
+ .persistent()
+ .set(&GovernanceKey::Config, config);
Ok(())
}
@@ -333,27 +351,41 @@ impl GovernanceSystem {
}
}
- fn execute_parameter_change(env: &Env, param_key: &ParamKey, new_value: i128) -> Result<(), SwapTradeError> {
+ fn execute_parameter_change(
+ env: &Env,
+ param_key: &ParamKey,
+ new_value: i128,
+ ) -> Result<(), SwapTradeError> {
match param_key {
ParamKey::MaxSwapAmount => {
// Update max swap amount
- env.storage().instance().set(&Symbol::short("max_swap"), &new_value);
+ env.storage()
+ .instance()
+ .set(&Symbol::short("max_swap"), &new_value);
}
ParamKey::FeeBps => {
// Update fee
- env.storage().instance().set(&Symbol::short("fee_bps"), &(new_value as u32));
+ env.storage()
+ .instance()
+ .set(&Symbol::short("fee_bps"), &(new_value as u32));
}
ParamKey::RateLimitWindow => {
// Update rate limit window
- env.storage().instance().set(&Symbol::short("rate_win"), &new_value);
+ env.storage()
+ .instance()
+ .set(&Symbol::short("rate_win"), &new_value);
}
ParamKey::MaxSlippageBps => {
// Update max slippage
- env.storage().instance().set(&Symbol::short("max_slip"), &(new_value as u32));
+ env.storage()
+ .instance()
+ .set(&Symbol::short("max_slip"), &(new_value as u32));
}
ParamKey::EmergencyPause => {
// Emergency pause/unpause
- env.storage().instance().set(&Symbol::short("paused"), &(new_value != 0));
+ env.storage()
+ .instance()
+ .set(&Symbol::short("paused"), &(new_value != 0));
}
ParamKey::RiskConfigMaxPosition => {
// Update risk config
@@ -389,7 +421,9 @@ impl GovernanceSystem {
}
fn execute_emergency_action(env: &Env, pause: bool) -> Result<(), SwapTradeError> {
- env.storage().instance().set(&Symbol::short("paused"), &pause);
+ env.storage()
+ .instance()
+ .set(&Symbol::short("paused"), &pause);
Ok(())
}
@@ -405,7 +439,8 @@ impl GovernanceSystem {
}
fn get_next_proposal_id(env: &Env) -> u64 {
- let current_id: u64 = env.storage()
+ let current_id: u64 = env
+ .storage()
.persistent()
.get(&GovernanceKey::NextProposalId)
.unwrap_or(1);
@@ -416,4 +451,4 @@ impl GovernanceSystem {
current_id
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/governance_tests.rs b/swaptrade-contracts/counter/src/governance_tests.rs
index ef7d7ab..a562b05 100644
--- a/swaptrade-contracts/counter/src/governance_tests.rs
+++ b/swaptrade-contracts/counter/src/governance_tests.rs
@@ -1,11 +1,10 @@
#[cfg(test)]
mod governance_tests {
use super::*;
- use crate::CounterContract;
- use soroban_sdk::{testutils::Address as _, testutils::Ledger as _, Address, Env, Symbol, symbol_short};
- use crate::governance_types::*;
use crate::governance_system::GovernanceSystem;
+ use crate::governance_types::*;
use crate::staking_bonus::StakingBonusManager;
+ use soroban_sdk::{symbol_short, testutils::Address as _, Address, Env, Symbol};
// ===== GOVERNANCE PROPOSAL TESTS =====
@@ -47,13 +46,8 @@ mod governance_tests {
let proposal_type = ProposalType::ParameterChange(ParamKey::FeeBps, 25);
let description = Symbol::new(&env, "test_proposal");
- let result = GovernanceSystem::create_proposal(
- &env,
- &proposer,
- proposal_type,
- description,
- 86400,
- );
+ let result =
+ GovernanceSystem::create_proposal(&env, &proposer, proposal_type, description, 86400);
assert!(result.is_err());
}
@@ -99,15 +93,11 @@ mod governance_tests {
proposal_type,
symbol_short!("test"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Cast vote
- let result = GovernanceSystem::cast_vote(
- &env,
- &voter,
- proposal_id,
- VoteOption::For,
- );
+ let result = GovernanceSystem::cast_vote(&env, &voter, proposal_id, VoteOption::For);
assert!(result.is_ok());
@@ -137,7 +127,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::FeeBps, 25),
symbol_short!("test"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Cast first vote
GovernanceSystem::cast_vote(&env, &voter, proposal_id, VoteOption::For).unwrap();
@@ -167,7 +158,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::FeeBps, 25),
Symbol::new(&env, "fee_change"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Cast votes - 800 + 700 = 1500 votes for (>50% of 2000 total)
GovernanceSystem::cast_vote(&env, &voter1, proposal_id, VoteOption::For).unwrap();
@@ -206,7 +198,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::FeeBps, 25),
Symbol::new(&env, "fee_change"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Cast votes - not enough for quorum (20% required)
GovernanceSystem::cast_vote(&env, &voter1, proposal_id, VoteOption::For).unwrap();
@@ -235,7 +228,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::FeeBps, 25),
symbol_short!("test"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Cancel by proposer
let result = GovernanceSystem::cancel_proposal(&env, &proposer, proposal_id);
@@ -270,7 +264,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::FeeBps, 50),
Symbol::new(&env, "fee_change"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Vote for
GovernanceSystem::cast_vote(&env, &voter, proposal_id, VoteOption::For).unwrap();
@@ -280,7 +275,11 @@ mod governance_tests {
GovernanceSystem::execute_proposal(&env, &executor, proposal_id).unwrap();
// Verify fee was changed
- let new_fee: u32 = env.storage().instance().get(&symbol_short!("fee_bps")).unwrap_or(30);
+ let new_fee: u32 = env
+ .storage()
+ .instance()
+ .get(&symbol_short!("fee_bps"))
+ .unwrap_or(30);
assert_eq!(new_fee, 50);
}
@@ -300,10 +299,13 @@ mod governance_tests {
let proposal_id = GovernanceSystem::create_proposal(
&env,
&proposer,
- ProposalType::AdminUpgrade(new_admin.clone()),
- Symbol::new(&env, "admin_upgrade"),
+ ProposalType::AdminUpgrade {
+ new_admin: new_admin.clone(),
+ },
+ symbol_short!("admin_upgrade"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Vote for
GovernanceSystem::cast_vote(&env, &voter, proposal_id, VoteOption::For).unwrap();
@@ -379,7 +381,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::MaxSwapAmount, 1000000),
Symbol::new(&env, "increase_max_swap"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// 2. Cast votes (3500 for, 800 abstain = 72% participation, 87% approval)
CounterContract::cast_governance_vote(
@@ -387,38 +390,42 @@ mod governance_tests {
voter1.clone(),
proposal_id,
VoteOption::For,
- ).unwrap();
+ )
+ .unwrap();
CounterContract::cast_governance_vote(
env.clone(),
voter2.clone(),
proposal_id,
VoteOption::For,
- ).unwrap();
+ )
+ .unwrap();
CounterContract::cast_governance_vote(
env.clone(),
voter3.clone(),
proposal_id,
VoteOption::Abstain,
- ).unwrap();
+ )
+ .unwrap();
// 3. Fast forward past voting period
env.ledger().set_timestamp(env.ledger().timestamp() + 86500);
// 4. Execute proposal
- CounterContract::execute_governance_proposal(
- env.clone(),
- executor.clone(),
- proposal_id,
- ).unwrap();
+ CounterContract::execute_governance_proposal(env.clone(), executor.clone(), proposal_id)
+ .unwrap();
// 5. Verify execution
let proposal = CounterContract::get_governance_proposal(env.clone(), proposal_id).unwrap();
assert_eq!(proposal.status, ProposalStatus::Executed);
// 6. Verify parameter change
- let max_swap: i128 = env.storage().instance().get(&symbol_short!("max_swap")).unwrap_or(0);
+ let max_swap: i128 = env
+ .storage()
+ .instance()
+ .get(&symbol_short!("max_swap"))
+ .unwrap_or(0);
assert_eq!(max_swap, 1000000);
}
@@ -440,7 +447,8 @@ mod governance_tests {
ProposalType::EmergencyAction(true),
Symbol::new(&env, "emergency_pause"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Cast vote (only 9% participation)
CounterContract::cast_governance_vote(
@@ -448,7 +456,8 @@ mod governance_tests {
voter.clone(),
proposal_id,
VoteOption::For,
- ).unwrap();
+ )
+ .unwrap();
// Fast forward and try to execute
env.ledger().set_timestamp(env.ledger().timestamp() + 86500);
@@ -476,7 +485,8 @@ mod governance_tests {
ProposalType::ParameterChange(ParamKey::FeeBps, 20),
symbol_short!("proposal1"),
86400,
- ).unwrap();
+ )
+ .unwrap();
// Try to create another proposal immediately - should fail
let result = CounterContract::create_governance_proposal(
@@ -489,4 +499,4 @@ mod governance_tests {
assert!(result.is_err());
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/governance_types.rs b/swaptrade-contracts/counter/src/governance_types.rs
index 5232c10..7b9f1d7 100644
--- a/swaptrade-contracts/counter/src/governance_types.rs
+++ b/swaptrade-contracts/counter/src/governance_types.rs
@@ -1,5 +1,5 @@
-use soroban_sdk::{contracttype, Address, Env, Symbol, Vec, Map};
use crate::errors::SwapTradeError;
+use soroban_sdk::{contracttype, Address, Env, Map, Symbol, Vec};
/// Governance proposal types
#[contracttype]
@@ -8,11 +8,11 @@ pub enum ProposalType {
/// Change protocol parameters (fees, limits, etc.)
ParameterChange(ParamKey, i128),
/// Upgrade admin address
- AdminUpgrade(Address),
+ AdminUpgrade { new_admin: Address },
/// Emergency pause/unpause
- EmergencyAction(bool),
+ EmergencyAction { pause: bool },
/// Custom proposal with description
- Custom(Symbol, Symbol),
+ Custom { title: Symbol, description: Symbol },
}
/// Governance proposal status
@@ -123,12 +123,12 @@ pub enum ParamKey {
impl Default for GovernanceConfig {
fn default() -> Self {
Self {
- min_voting_period: 86400, // 1 day
- max_voting_period: 604800, // 7 days
- quorum_threshold: 2000, // 20%
- approval_threshold: 5000, // 50%
- execution_delay: 172800, // 2 days
- proposal_cooldown: 3600, // 1 hour
+ min_voting_period: 86400, // 1 day
+ max_voting_period: 604800, // 7 days
+ quorum_threshold: 2000, // 20%
+ approval_threshold: 5000, // 50%
+ execution_delay: 172800, // 2 days
+ proposal_cooldown: 3600, // 1 hour
}
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/kyc.rs b/swaptrade-contracts/counter/src/kyc.rs
index 0af5256..f4d9a67 100644
--- a/swaptrade-contracts/counter/src/kyc.rs
+++ b/swaptrade-contracts/counter/src/kyc.rs
@@ -280,7 +280,6 @@ impl KYCSystem {
}
}
-
// ===== KYC RECORD MANAGEMENT =====
/// Get KYC record for a user
@@ -400,7 +399,7 @@ impl KYCSystem {
let timestamp = env.ledger().timestamp();
let expiry_duration = Self::get_pending_expiry_duration(env);
-
+
let mut new_record = record;
new_record.status = KYCStatus::Pending;
new_record.updated_at = timestamp;
@@ -742,6 +741,9 @@ mod mxllv_tests {
fn test_validate_helpers_are_private() {
// `validate_reason` and `validate_symbol_length` are module-private (`fn`).
// Attempting to call them from outside the module would fail to compile.
- assert!(true, "internal validation helpers are private by construction");
+ assert!(
+ true,
+ "internal validation helpers are private by construction"
+ );
}
}
diff --git a/swaptrade-contracts/counter/src/lib.rs b/swaptrade-contracts/counter/src/lib.rs
index 0bb1081..6bb86c2 100644
--- a/swaptrade-contracts/counter/src/lib.rs
+++ b/swaptrade-contracts/counter/src/lib.rs
@@ -5,10 +5,10 @@ use soroban_sdk::{
// Bring in modules from parent directory
mod admin;
-mod bridge;
#[cfg(test)]
mod alert_tests;
mod alerts;
+mod bridge;
mod errors;
mod events;
mod faucet;
@@ -18,39 +18,38 @@ mod kyc;
mod kyc_tests;
mod liquidity_pool;
mod rate_limit;
+mod referral_system;
mod state_snapshot;
#[cfg(test)]
mod state_snapshot_tests;
mod storage;
-mod referral_system;
mod batch {
include!("../batch.rs");
}
mod tiers {
include!("../tiers.rs");
}
+#[cfg(test)]
+mod analytics_dashboard_tests;
#[cfg(all(test, feature = "experimental"))]
mod batch_event_tests;
#[cfg(all(test, feature = "experimental"))]
mod batch_opt_simple_test;
#[cfg(all(test, feature = "experimental"))]
mod batch_performance_tests;
+mod governance_params;
+mod governance_system;
+mod governance_types;
+#[cfg(test)]
+mod multihop_swap_tests;
+mod nonce;
mod oracle;
mod oracle_adapter;
-mod orders;
-mod flash_loan;
-#[cfg(test)]
-mod orders_tests;
-#[cfg(test)]
-mod analytics_dashboard_tests;
#[cfg(test)]
mod oracle_adapter_tests;
+mod orders;
#[cfg(test)]
-mod multihop_swap_tests;
-mod governance_types;
-mod governance_system;
-mod governance_params;
-mod nonce;
+mod orders_tests;
mod risk_management;
pub use governance_params::{GovernanceParams, ParamKey, PendingParamUpdate};
@@ -205,6 +204,66 @@ pub fn set_admin(env: Env, caller: Address, new_admin: Address) -> Result<(), Sw
Ok(())
}
+pub fn set_treasury(
+ env: Env,
+ caller: Address,
+ new_treasury: Address,
+) -> Result<(), SwapTradeError> {
+ caller.require_auth();
+ crate::admin::require_admin(&env, &caller)?;
+ env.storage()
+ .persistent()
+ .set(&crate::storage::DEFAULT_TREASURY_KEY, &new_treasury);
+ crate::events::fee_parameters_updated(&env, 0, 0, Some(new_treasury));
+ Ok(())
+}
+
+pub fn update_pool_fee_tier(
+ env: Env,
+ caller: Address,
+ pool_id: u64,
+ new_fee_tier: u32,
+) -> Result<(), ContractError> {
+ caller.require_auth();
+ let mut registry = load_pool_registry(&env);
+ registry.update_fee_tier(&env, pool_id, new_fee_tier, caller)?;
+ save_pool_registry(&env, ®istry);
+ Ok(())
+}
+
+pub fn claim_pool_fees(
+ env: Env,
+ caller: Address,
+ pool_id: u64,
+) -> Result<(i128, i128), ContractError> {
+ caller.require_auth();
+ let mut registry = load_pool_registry(&env);
+ let fees = registry.claim_fees(&env, pool_id, caller)?;
+ save_pool_registry(&env, ®istry);
+ Ok(fees)
+}
+
+pub fn withdraw_treasury_fees(
+ env: Env,
+ caller: Address,
+ pool_id: u64,
+) -> Result<(i128, i128), ContractError> {
+ caller.require_auth();
+ // Verify caller is the treasury
+ let treasury: Address = env
+ .storage()
+ .persistent()
+ .get(&crate::storage::DEFAULT_TREASURY_KEY)
+ .ok_or(ContractError::InvalidAddress)?;
+ if caller != treasury {
+ return Err(ContractError::NotAuthorized);
+ }
+ let mut registry = load_pool_registry(&env);
+ let fees = registry.withdraw_treasury_fees(&env, pool_id, caller)?;
+ save_pool_registry(&env, ®istry);
+ Ok(fees)
+}
+
// Batch imports
use batch::{execute_batch_atomic, execute_batch_best_effort, BatchOperation, BatchResult};
@@ -368,11 +427,13 @@ impl CounterContract {
}
/// Swap tokens using simplified AMM (1:1 XLM <-> USDC-SIM)
- pub fn swap(env: Env, from: Symbol, to: Symbol, amount: i128, min_amount_out: i128, user: Address, deadline: u64) -> Result {
- if env.ledger().timestamp() > deadline {
- return Err(ContractError::Expired);
- }
-
+ pub fn swap(
+ env: Env,
+ from: Symbol,
+ to: Symbol,
+ amount: i128,
+ user: Address,
+ ) -> Result {
require_authenticated_verified_user(&env, &user)?;
// Oracle validation
@@ -468,7 +529,11 @@ impl CounterContract {
portfolio.collect_fee(fee_amount);
// Distribute referral commissions
- crate::referral_system::calculate_and_distribute_commission(&env, user.clone(), fee_amount);
+ crate::referral_system::calculate_and_distribute_commission(
+ &env,
+ user.clone(),
+ fee_amount,
+ );
}
let out_amount = perform_swap(
@@ -780,7 +845,7 @@ impl CounterContract {
// Extract caller from first operation for authentication and rate limiting
let caller = match operations.get(0) {
- Some(BatchOperation::Swap(_, _, _, user))
+ Some(BatchOperation::Swap(_, _, _, user))
| Some(BatchOperation::AddLiquidity(_, _, user))
| Some(BatchOperation::RemoveLiquidity(_, _, user)) => Some(user.clone()),
Some(BatchOperation::MintToken(_, _, _)) => None,
@@ -807,7 +872,10 @@ impl CounterContract {
if let Some(caller_addr) = &caller {
let user_tier = portfolio.get_user_tier(&env, caller_addr.clone());
// Count swap operations in batch
- let swap_count = operations.iter().filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _))).count();
+ let swap_count = operations
+ .iter()
+ .filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _)))
+ .count();
if swap_count > 0 {
// Apply rate limit check for batch swaps
if RateLimiter::check_swap_limit(&env, caller_addr, &user_tier).is_err() {
@@ -823,13 +891,20 @@ impl CounterContract {
match result {
Ok(res) => {
env.storage().instance().set(&(), &portfolio);
-
+
// Record rate limit usage for executed swaps
if let Some(caller_addr) = &caller {
- let swap_count = operations.iter().filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _))).count();
+ let swap_count = operations
+ .iter()
+ .filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _)))
+ .count();
if swap_count > 0 && res.operations_executed > 0 {
for _ in 0..res.operations_executed {
- RateLimiter::record_swap(&env, caller_addr, env.ledger().timestamp());
+ RateLimiter::record_swap_op(
+ &env,
+ caller_addr,
+ env.ledger().timestamp(),
+ );
}
}
}
@@ -856,7 +931,7 @@ impl CounterContract {
// Extract caller from first operation for authentication and rate limiting
let caller = match operations.get(0) {
- Some(BatchOperation::Swap(_, _, _, user))
+ Some(BatchOperation::Swap(_, _, _, user))
| Some(BatchOperation::AddLiquidity(_, _, user))
| Some(BatchOperation::RemoveLiquidity(_, _, user)) => Some(user.clone()),
Some(BatchOperation::MintToken(_, _, _)) => None,
@@ -883,7 +958,10 @@ impl CounterContract {
if let Some(caller_addr) = &caller {
let user_tier = portfolio.get_user_tier(&env, caller_addr.clone());
// Count swap operations in batch
- let swap_count = operations.iter().filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _))).count();
+ let swap_count = operations
+ .iter()
+ .filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _)))
+ .count();
if swap_count > 0 {
// Apply rate limit check for batch swaps
if RateLimiter::check_swap_limit(&env, caller_addr, &user_tier).is_err() {
@@ -899,13 +977,20 @@ impl CounterContract {
match result {
Ok(res) => {
env.storage().instance().set(&(), &portfolio);
-
+
// Record rate limit usage for executed swaps
if let Some(caller_addr) = &caller {
- let swap_count = operations.iter().filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _))).count();
+ let swap_count = operations
+ .iter()
+ .filter(|op| matches!(op, BatchOperation::Swap(_, _, _, _)))
+ .count();
if swap_count > 0 && res.operations_executed > 0 {
for _ in 0..res.operations_executed {
- RateLimiter::record_swap(&env, caller_addr, env.ledger().timestamp());
+ RateLimiter::record_swap_op(
+ &env,
+ caller_addr,
+ env.ledger().timestamp(),
+ );
}
}
}
@@ -930,7 +1015,12 @@ impl CounterContract {
/// Add liquidity to the pool and mint LP tokens
/// Returns the number of LP tokens minted
- pub fn add_liquidity(env: Env, xlm_amount: i128, usdc_amount: i128, user: Address) -> Result {
+ pub fn add_liquidity(
+ env: Env,
+ xlm_amount: i128,
+ usdc_amount: i128,
+ user: Address,
+ ) -> Result {
require_authenticated_verified_user(&env, &user)?;
if xlm_amount <= 0 || usdc_amount <= 0 {
@@ -1061,7 +1151,11 @@ impl CounterContract {
/// Remove liquidity from the pool by burning LP tokens
/// Returns (xlm_amount, usdc_amount) returned to user
- pub fn remove_liquidity(env: Env, lp_tokens: i128, user: Address) -> Result<(i128, i128), ContractError> {
+ pub fn remove_liquidity(
+ env: Env,
+ lp_tokens: i128,
+ user: Address,
+ ) -> Result<(i128, i128), ContractError> {
require_authenticated_verified_user(&env, &user)?;
if lp_tokens <= 0 {
@@ -1194,9 +1288,10 @@ impl CounterContract {
require_verified_user(&env, &provider)?;
let mut registry = load_pool_registry(&env);
- let lp_tokens = registry.add_liquidity(&env, pool_id, amount_a, amount_b, provider.clone())?;
+ let lp_tokens =
+ registry.add_liquidity(&env, pool_id, amount_a, amount_b, provider.clone())?;
save_pool_registry(&env, ®istry);
-
+
// Emit LiquidityAdded event with correct signature
env.events().publish(
(
@@ -1206,7 +1301,7 @@ impl CounterContract {
),
(amount_a, amount_b, lp_tokens, env.ledger().timestamp()),
);
-
+
Ok(lp_tokens)
}
@@ -1220,9 +1315,10 @@ impl CounterContract {
require_verified_user(&env, &provider)?;
let mut registry = load_pool_registry(&env);
- let (amount_a, amount_b) = registry.remove_liquidity(&env, pool_id, lp_tokens, provider.clone())?;
+ let (amount_a, amount_b) =
+ registry.remove_liquidity(&env, pool_id, lp_tokens, provider.clone())?;
save_pool_registry(&env, ®istry);
-
+
// Emit LiquidityRemoved event with correct signature
env.events().publish(
(
@@ -1232,7 +1328,7 @@ impl CounterContract {
),
(amount_a, amount_b, lp_tokens, env.ledger().timestamp()),
);
-
+
Ok((amount_a, amount_b))
}
@@ -1299,7 +1395,7 @@ impl CounterContract {
) -> Result {
trader.require_auth();
require_verified_user(&env, &trader)?;
-
+
trading::execute_multihop_swap(&env, &route, amount_in, min_amount_out, &trader)
}
@@ -1382,7 +1478,12 @@ impl CounterContract {
/// Stake tokens for a specified duration to earn bonuses
/// Supports: 30, 60, 90, or 365-day stakes
- pub fn stake(env: Env, user: Address, amount: i128, duration_days: u32) -> Result {
+ pub fn stake(
+ env: Env,
+ user: Address,
+ amount: i128,
+ duration_days: u32,
+ ) -> Result {
require_authenticated_verified_user(&env, &user)?;
let result = StakingBonusManager::stake(&env, user, amount, duration_days)?;
invalidate_query_cache(&env);
@@ -1409,7 +1510,11 @@ impl CounterContract {
/// Unstake early before lock period (incurs 10% penalty)
/// Returns (principal_after_penalty, penalty_amount)
- pub fn unstake_early(env: Env, user: Address, stake_id: u32) -> Result<(i128, i128), ContractError> {
+ pub fn unstake_early(
+ env: Env,
+ user: Address,
+ stake_id: u32,
+ ) -> Result<(i128, i128), ContractError> {
require_authenticated_verified_user(&env, &user)?;
let result = StakingBonusManager::unstake_early(&env, user, stake_id)?;
invalidate_query_cache(&env);
@@ -1422,7 +1527,11 @@ impl CounterContract {
}
/// Get specific stake details
- pub fn get_stake_details(env: Env, user: Address, stake_id: u32) -> Result {
+ pub fn get_stake_details(
+ env: Env,
+ user: Address,
+ stake_id: u32,
+ ) -> Result {
StakingBonusManager::get_stake_details(&env, user, stake_id)
}
@@ -1466,7 +1575,15 @@ impl CounterContract {
user: Address,
) -> Result {
require_authenticated_verified_user(&env, &user)?;
- orders::OrderManager::place_limit_order(&env, user, token_in, token_out, amount_in, limit_price, expires_at)
+ orders::OrderManager::place_limit_order(
+ &env,
+ user,
+ token_in,
+ token_out,
+ amount_in,
+ limit_price,
+ expires_at,
+ )
}
/// Place a stop-loss order that executes when price reaches trigger_price
@@ -1480,7 +1597,15 @@ impl CounterContract {
user: Address,
) -> Result {
require_authenticated_verified_user(&env, &user)?;
- orders::OrderManager::place_stop_loss(&env, user, token_in, token_out, amount_in, trigger_price, expires_at)
+ orders::OrderManager::place_stop_loss(
+ &env,
+ user,
+ token_in,
+ token_out,
+ amount_in,
+ trigger_price,
+ expires_at,
+ )
}
/// Cancel an existing order
@@ -1566,12 +1691,20 @@ impl CounterContract {
// ────────────────────────────────────────────────────────────────────────
/// Add a KYC operator (admin only)
- pub fn kyc_add_operator(env: Env, admin: Address, operator: Address) -> Result<(), ContractError> {
+ pub fn kyc_add_operator(
+ env: Env,
+ admin: Address,
+ operator: Address,
+ ) -> Result<(), ContractError> {
kyc::KYCSystem::add_operator(&env, &admin, operator)
}
/// Remove a KYC operator (admin only)
- pub fn kyc_remove_operator(env: Env, admin: Address, operator: Address) -> Result<(), ContractError> {
+ pub fn kyc_remove_operator(
+ env: Env,
+ admin: Address,
+ operator: Address,
+ ) -> Result<(), ContractError> {
kyc::KYCSystem::remove_operator(&env, &admin, operator)
}
@@ -1612,7 +1745,11 @@ impl CounterContract {
}
/// Set timelock duration for governance overrides (admin only)
- pub fn kyc_set_timelock_duration(env: Env, admin: Address, duration: u64) -> Result<(), ContractError> {
+ pub fn kyc_set_timelock_duration(
+ env: Env,
+ admin: Address,
+ duration: u64,
+ ) -> Result<(), ContractError> {
kyc::KYCSystem::set_timelock_duration(&env, &admin, duration)
}
@@ -1622,7 +1759,11 @@ impl CounterContract {
}
/// Set pending KYC expiry duration (admin only)
- pub fn kyc_set_pending_expiry_duration(env: Env, admin: Address, duration: u64) -> Result<(), ContractError> {
+ pub fn kyc_set_pending_expiry_duration(
+ env: Env,
+ admin: Address,
+ duration: u64,
+ ) -> Result<(), ContractError> {
kyc::KYCSystem::set_pending_expiry_duration(&env, &admin, duration)
}
@@ -1643,7 +1784,11 @@ impl CounterContract {
}
/// Execute governance override after timelock (admin only)
- pub fn kyc_execute_override(env: Env, admin: Address, override_id: u64) -> Result<(), ContractError> {
+ pub fn kyc_execute_override(
+ env: Env,
+ admin: Address,
+ override_id: u64,
+ ) -> Result<(), ContractError> {
kyc::KYCSystem::execute_override(&env, &admin, override_id)
}
@@ -1655,7 +1800,11 @@ impl CounterContract {
// ── Referral System ─────────────────────────────────────────────────────
/// Register a referral relationship
- pub fn register_referral(env: Env, referrer: Address, referred: Address) -> Result<(), ContractError> {
+ pub fn register_referral(
+ env: Env,
+ referrer: Address,
+ referred: Address,
+ ) -> Result<(), ContractError> {
referral_system::register_referral(&env, referrer, referred)
}
@@ -1824,13 +1973,8 @@ impl CounterContract {
}
}
+mod governance_tests;
#[cfg(all(test, feature = "experimental"))]
mod migration_tests;
#[cfg(test)]
mod risk_management_tests;
-#[cfg(test)]
-mod governance_tests;
-#[cfg(test)]
-mod referral_system_tests;
-#[cfg(test)]
-mod referral_integration_test;
\ No newline at end of file
diff --git a/swaptrade-contracts/counter/src/liquidity_pool.rs b/swaptrade-contracts/counter/src/liquidity_pool.rs
index 06755e3..cf5bed9 100644
--- a/swaptrade-contracts/counter/src/liquidity_pool.rs
+++ b/swaptrade-contracts/counter/src/liquidity_pool.rs
@@ -15,6 +15,8 @@ pub struct LiquidityPool {
pub reserve_b: i128,
pub total_lp_tokens: i128,
pub fee_tier: u32,
+ pub accumulated_fees_a: i128,
+ pub accumulated_fees_b: i128,
}
#[derive(Clone, Debug, PartialEq)]
@@ -112,6 +114,8 @@ impl PoolRegistry {
reserve_b,
total_lp_tokens: initial_lp,
fee_tier,
+ accumulated_fees_a: 0,
+ accumulated_fees_b: 0,
},
);
self.pair_to_pool.set((norm_a, norm_b), pool_id);
@@ -182,6 +186,100 @@ impl PoolRegistry {
Ok(lp_tokens)
}
+ pub fn claim_fees(
+ &mut self,
+ env: &Env,
+ pool_id: u64,
+ provider: Address,
+ ) -> Result<(i128, i128), ContractError> {
+ let pool = self
+ .pools
+ .get(pool_id)
+ .ok_or(ContractError::LPPositionNotFound)?;
+ let key = (pool_id, provider.clone());
+ let lp_balance = self.lp_balances.get(key.clone()).unwrap_or(0);
+ if lp_balance <= 0 {
+ return Err(ContractError::InsufficientLPTokens);
+ }
+
+ // Calculate proportional share of accumulated fees
+ let share = (lp_balance as u128) / (pool.total_lp_tokens as u128);
+ let fees_a = ((pool.accumulated_fees_a as u128) * share) as i128;
+ let fees_b = ((pool.accumulated_fees_b as u128) * share) as i128;
+
+ // Update accumulated fees (subtract claimed amount)
+ let mut updated_pool = pool.clone();
+ updated_pool.accumulated_fees_a = pool.accumulated_fees_a - fees_a;
+ updated_pool.accumulated_fees_b = pool.accumulated_fees_b - fees_b;
+ self.pools.set(pool_id, updated_pool);
+
+ // Publish events
+ if fees_a > 0 {
+ crate::events::fees_distributed(env, pool_id, pool.token_a, fees_a, provider.clone());
+ }
+ if fees_b > 0 {
+ crate::events::fees_distributed(env, pool_id, pool.token_b, fees_b, provider);
+ }
+
+ Ok((fees_a, fees_b))
+ }
+
+ pub fn withdraw_treasury_fees(
+ &mut self,
+ env: &Env,
+ pool_id: u64,
+ treasury: Address,
+ ) -> Result<(i128, i128), ContractError> {
+ treasury.require_auth();
+ let mut pool = self
+ .pools
+ .get(pool_id)
+ .ok_or(ContractError::LPPositionNotFound)?;
+
+ let fees_a = pool.accumulated_fees_a;
+ let fees_b = pool.accumulated_fees_b;
+
+ // Reset accumulated fees
+ pool.accumulated_fees_a = 0;
+ pool.accumulated_fees_b = 0;
+ self.pools.set(pool_id, pool);
+
+ // Publish events
+ if fees_a > 0 {
+ crate::events::fees_distributed(env, pool_id, pool.token_a, fees_a, treasury.clone());
+ }
+ if fees_b > 0 {
+ crate::events::fees_distributed(env, pool_id, pool.token_b, fees_b, treasury);
+ }
+
+ Ok((fees_a, fees_b))
+ }
+
+ pub fn update_fee_tier(
+ &mut self,
+ env: &Env,
+ pool_id: u64,
+ new_fee_tier: u32,
+ admin: Address,
+ ) -> Result<(), ContractError> {
+ admin.require_auth();
+ crate::admin::require_admin(env, &admin)?;
+
+ if ![1, 5, 30].contains(&new_fee_tier) {
+ return Err(ContractError::InvalidAmount);
+ }
+
+ let mut pool = self
+ .pools
+ .get(pool_id)
+ .ok_or(ContractError::LPPositionNotFound)?;
+ pool.fee_tier = new_fee_tier;
+ self.pools.set(pool_id, pool);
+
+ crate::events::fee_parameters_updated(env, pool_id, new_fee_tier, None);
+ Ok(())
+ }
+
pub fn remove_liquidity(
&mut self,
env: &Env,
@@ -189,6 +287,9 @@ impl PoolRegistry {
lp_tokens: i128,
provider: Address,
) -> Result<(i128, i128), ContractError> {
+ // First claim any pending fees before removing liquidity
+ self.claim_fees(env, pool_id, provider.clone())?;
+
let mut pool = self
.pools
.get(pool_id)
@@ -246,18 +347,19 @@ impl PoolRegistry {
return Err(ContractError::InvalidAmount);
}
- let (reserve_in, reserve_out) = if token_in == pool.token_a {
- (pool.reserve_a, pool.reserve_b)
+ let (reserve_in, reserve_out, is_token_a) = if token_in == pool.token_a {
+ (pool.reserve_a, pool.reserve_b, true)
} else if token_in == pool.token_b {
- (pool.reserve_b, pool.reserve_a)
+ (pool.reserve_b, pool.reserve_a, false)
} else {
return Err(ContractError::InvalidTokenSymbol);
};
- let amount_in_with_fee = (amount_in as u128)
- .checked_mul(10000 - pool.fee_tier as u128)
- .ok_or(ContractError::AmountOverflow)?
- / 10000;
+ // Calculate fee and amount after fee
+ let fee_amount = ((amount_in as u128) * (pool.fee_tier as u128) / 10000) as i128;
+ let amount_in_after_fee = amount_in - fee_amount;
+
+ let amount_in_with_fee = amount_in_after_fee as u128;
let numerator = (reserve_out as u128)
.checked_mul(amount_in_with_fee)
.ok_or(ContractError::AmountOverflow)?;
@@ -270,25 +372,38 @@ impl PoolRegistry {
return Err(ContractError::SlippageExceeded);
}
- if token_in == pool.token_a {
+ // Accumulate fees
+ if is_token_a {
+ pool.accumulated_fees_a = pool
+ .accumulated_fees_a
+ .checked_add(fee_amount)
+ .ok_or(ContractError::AmountOverflow)?;
pool.reserve_a = pool
.reserve_a
- .checked_add(amount_in)
+ .checked_add(amount_in_after_fee)
.ok_or(ContractError::AmountOverflow)?;
pool.reserve_b = pool
.reserve_b
.checked_sub(amount_out)
.ok_or(ContractError::InsufficientBalance)?;
} else {
+ pool.accumulated_fees_b = pool
+ .accumulated_fees_b
+ .checked_add(fee_amount)
+ .ok_or(ContractError::AmountOverflow)?;
pool.reserve_b = pool
.reserve_b
- .checked_add(amount_in)
+ .checked_add(amount_in_after_fee)
.ok_or(ContractError::AmountOverflow)?;
pool.reserve_a = pool
.reserve_a
.checked_sub(amount_out)
.ok_or(ContractError::InsufficientBalance)?;
}
+
+ // Publish fees collected event
+ crate::events::fees_collected(env, token_in, fee_amount, pool_id);
+
self.pools.set(pool_id, pool);
Ok(amount_out)
}
@@ -338,9 +453,9 @@ impl PoolRegistry {
if let Some(pool2_id) = self.pair_to_pool.get((norm_int, norm_out)) {
if let Some(pool2) = self.pools.get(pool2_id) {
let out1 =
- self.calculate_output(&pool1, token_in.clone(), amount_in).ok()?;
+ self.calculate_output(&pool1, token_in.clone(), amount_in)?;
let out2 =
- self.calculate_output(&pool2, intermediate.clone(), out1).ok()?;
+ self.calculate_output(&pool2, intermediate.clone(), out1)?;
let impact1 = self.calculate_price_impact(
&pool1,
token_in.clone(),
@@ -375,31 +490,12 @@ impl PoolRegistry {
best_route
}
- pub fn simulate_route(
+ fn calculate_output(
&self,
- route: &Route,
+ pool: &LiquidityPool,
+ token_in: Symbol,
amount_in: i128,
- ) -> Option<(i128, u32)> {
- if route.pools.len() == 0 || route.tokens.len() < 2 {
- return None;
- }
- let mut current_amount = amount_in;
- let mut total_fees_bps: u32 = 0;
-
- for idx in 0..route.pools.len() {
- let pool_id = route.pools.get(idx)?;
- let pool = self.pools.get(pool_id)?;
- let token_in = route.tokens.get(idx)?;
-
- let output = self.calculate_output(&pool, token_in, current_amount).ok()?;
- total_fees_bps = total_fees_bps.saturating_add(pool.fee_tier);
- current_amount = output;
- }
-
- Some((current_amount, total_fees_bps))
- }
-
- fn calculate_output(&self, pool: &LiquidityPool, token_in: Symbol, amount_in: i128) -> Result {
+ ) -> Result {
let (reserve_in, reserve_out) = if token_in == pool.token_a {
(pool.reserve_a, pool.reserve_b)
} else {
diff --git a/swaptrade-contracts/counter/src/multihop_swap_tests.rs b/swaptrade-contracts/counter/src/multihop_swap_tests.rs
index 243b0e3..d795d76 100644
--- a/swaptrade-contracts/counter/src/multihop_swap_tests.rs
+++ b/swaptrade-contracts/counter/src/multihop_swap_tests.rs
@@ -50,7 +50,7 @@ fn test_two_hop_swap_execution() {
// Execute multi-hop swap
let min_out = ((r.expected_output as u128).saturating_mul(9500) / 10000) as i128; // 5% slippage
let result = client.execute_multi_hop_swap(&r, &100, &min_out, &trader);
-
+
assert!(result > 0);
}
@@ -81,7 +81,7 @@ fn test_multi_hop_respects_slippage_tolerance() {
// Try to execute with very tight slippage (should fail)
let tight_min_out = r.expected_output + 1000; // Unrealistic expectation
let result = client.try_execute_multi_hop_swap(&r, &500, &tight_min_out, &trader);
-
+
// Should fail due to slippage
assert!(result.is_err());
}
@@ -106,7 +106,7 @@ fn test_multi_hop_atomic_execution() {
// Try to find route (should not find 2-hop route)
let route = client.find_best_route(&xlm, &btc, &100);
-
+
// Route should be None since second pool doesn't exist
if route.is_none() {
// Test passes - route discovery prevents invalid execution
@@ -116,7 +116,7 @@ fn test_multi_hop_atomic_execution() {
// If route exists but pool is invalid, execution should fail
let r = route.unwrap();
let result = client.try_execute_multi_hop_swap(&r, &100, &0, &trader);
-
+
// Should fail due to missing pool
assert!(result.is_err());
}
@@ -178,7 +178,7 @@ fn test_single_hop_swap_via_route() {
// Execute via multi-hop function (should work for single hop too)
let min_out = ((r.expected_output as u128).saturating_mul(9500) / 10000) as i128;
let result = client.execute_multi_hop_swap(&r, &100, &min_out, &trader);
-
+
assert!(result > 0);
}
@@ -252,7 +252,7 @@ fn test_three_hop_route_execution() {
// Find route from XLM to ETH
let route = client.find_best_route(&xlm, ð, &100);
-
+
// Note: Current find_best_route only supports up to 2 hops
// This test validates that the execution function can handle it if route is provided
if route.is_some() {
diff --git a/swaptrade-contracts/counter/src/nft_lending.rs b/swaptrade-contracts/counter/src/nft_lending.rs
index 14ad03f..80d8710 100644
--- a/swaptrade-contracts/counter/src/nft_lending.rs
+++ b/swaptrade-contracts/counter/src/nft_lending.rs
@@ -211,7 +211,8 @@ pub fn fund_loan(env: &Env, lender: Address, loan_id: u64) -> Result<(), NFTErro
let current_time = env.ledger().timestamp();
// Calculate repayment amount using scaled arithmetic to prevent precision loss
- let scaled_principal = (loan.loan_amount as u128).checked_mul(INTEREST_PRECISION)
+ let scaled_principal = (loan.loan_amount as u128)
+ .checked_mul(INTEREST_PRECISION)
.ok_or(NFTError::InterestOverflow)?;
let daily_interest_rate = loan.interest_rate_bps as u128;
let daily_interest_scaled = scaled_principal
@@ -224,7 +225,8 @@ pub fn fund_loan(env: &Env, lender: Address, loan_id: u64) -> Result<(), NFTErro
.checked_mul(days)
.ok_or(NFTError::InterestOverflow)?;
let total_interest = (total_interest_scaled / INTEREST_PRECISION) as i128;
- loan.repayment_amount = loan.loan_amount
+ loan.repayment_amount = loan
+ .loan_amount
.checked_add(total_interest as i128)
.ok_or(NFTError::AmountOverflow)?;
diff --git a/swaptrade-contracts/counter/src/nft_types.rs b/swaptrade-contracts/counter/src/nft_types.rs
index 1294f93..04ce9ff 100644
--- a/swaptrade-contracts/counter/src/nft_types.rs
+++ b/swaptrade-contracts/counter/src/nft_types.rs
@@ -529,7 +529,8 @@ impl NFTLoan {
let days_elapsed = elapsed / 86400;
// Use saturating arithmetic to prevent overflow
// Scale to u128 for precision, then convert back
- let scaled_principal = (self.loan_amount as u128).saturating_mul(1_000_000_000_000_000_000u128);
+ let scaled_principal =
+ (self.loan_amount as u128).saturating_mul(1_000_000_000_000_000_000u128);
let daily_interest_rate = self.interest_rate_bps as u128;
let daily_interest_scaled = scaled_principal
.saturating_mul(daily_interest_rate)
diff --git a/swaptrade-contracts/counter/src/oracle_adapter.rs b/swaptrade-contracts/counter/src/oracle_adapter.rs
index f7db22d..9bc4bd4 100644
--- a/swaptrade-contracts/counter/src/oracle_adapter.rs
+++ b/swaptrade-contracts/counter/src/oracle_adapter.rs
@@ -91,8 +91,12 @@ impl OracleAdapter {
fallback_price: initial_price,
};
- env.storage().instance().set(&Self::config_key(&pair), &config);
- env.storage().instance().set(&Self::state_key(&pair), &state);
+ env.storage()
+ .instance()
+ .set(&Self::config_key(&pair), &config);
+ env.storage()
+ .instance()
+ .set(&Self::state_key(&pair), &state);
Ok(())
}
@@ -100,7 +104,7 @@ impl OracleAdapter {
/// Get price with TWAP validation and staleness checks
pub fn get_price(env: &Env, pair: (Symbol, Symbol)) -> Result {
let config = Self::get_config(env, &pair)?;
-
+
if !config.is_active {
return Err(ContractError::OracleNotActive);
}
@@ -153,15 +157,17 @@ impl OracleAdapter {
// Circuit breaker check: reject if deviation > threshold
if state.current_price > 0 {
let deviation_bps = Self::calculate_deviation_bps(state.current_price, new_price);
-
+
if deviation_bps > config.circuit_breaker_threshold_bps {
// Activate circuit breaker
state.circuit_breaker_active = true;
state.fallback_price = state.current_price;
-
+
// Store updated state
- env.storage().instance().set(&Self::state_key(&pair), &state);
-
+ env.storage()
+ .instance()
+ .set(&Self::state_key(&pair), &state);
+
return Err(ContractError::CircuitBreakerTriggered);
}
}
@@ -182,7 +188,7 @@ impl OracleAdapter {
// Update state
state.current_price = new_price;
state.last_update = current_time;
-
+
// Deactivate circuit breaker if price is stable
if state.circuit_breaker_active {
let deviation_bps = Self::calculate_deviation_bps(state.fallback_price, new_price);
@@ -192,7 +198,9 @@ impl OracleAdapter {
}
}
- env.storage().instance().set(&Self::state_key(&pair), &state);
+ env.storage()
+ .instance()
+ .set(&Self::state_key(&pair), &state);
Ok(())
}
@@ -285,15 +293,23 @@ impl OracleAdapter {
}
}
- env.storage().instance().set(&Self::config_key(&pair), &config);
+ env.storage()
+ .instance()
+ .set(&Self::config_key(&pair), &config);
Ok(())
}
/// Activate or deactivate oracle
- pub fn set_oracle_active(env: &Env, pair: (Symbol, Symbol), active: bool) -> Result<(), ContractError> {
+ pub fn set_oracle_active(
+ env: &Env,
+ pair: (Symbol, Symbol),
+ active: bool,
+ ) -> Result<(), ContractError> {
let mut config = Self::get_config(env, &pair)?;
config.is_active = active;
- env.storage().instance().set(&Self::config_key(&pair), &config);
+ env.storage()
+ .instance()
+ .set(&Self::config_key(&pair), &config);
Ok(())
}
@@ -301,12 +317,17 @@ impl OracleAdapter {
pub fn reset_circuit_breaker(env: &Env, pair: (Symbol, Symbol)) -> Result<(), ContractError> {
let mut state = Self::get_state(env, &pair)?;
state.circuit_breaker_active = false;
- env.storage().instance().set(&Self::state_key(&pair), &state);
+ env.storage()
+ .instance()
+ .set(&Self::state_key(&pair), &state);
Ok(())
}
/// Get oracle state information
- pub fn get_oracle_info(env: &Env, pair: (Symbol, Symbol)) -> Result<(OracleConfig, OracleState), ContractError> {
+ pub fn get_oracle_info(
+ env: &Env,
+ pair: (Symbol, Symbol),
+ ) -> Result<(OracleConfig, OracleState), ContractError> {
let config = Self::get_config(env, &pair)?;
let state = Self::get_state(env, &pair)?;
Ok((config, state))
diff --git a/swaptrade-contracts/counter/src/oracle_adapter_tests.rs b/swaptrade-contracts/counter/src/oracle_adapter_tests.rs
index 8599d86..ce358e5 100644
--- a/swaptrade-contracts/counter/src/oracle_adapter_tests.rs
+++ b/swaptrade-contracts/counter/src/oracle_adapter_tests.rs
@@ -23,7 +23,7 @@ fn test_initialize_oracle() {
setup_oracle(&env, pair.clone());
let (config, state) = OracleAdapter::get_oracle_info(&env, pair).unwrap();
-
+
assert!(config.is_active);
assert_eq!(config.staleness_threshold, 300);
assert_eq!(config.circuit_breaker_threshold_bps, 1000);
@@ -63,7 +63,7 @@ fn test_circuit_breaker_triggers_on_large_deviation() {
// Try to update price by 15% (exceeds 10% threshold)
let new_price = (PRECISION as u128).saturating_mul(11_500) / 10_000;
let result = OracleAdapter::update_price(&env, pair.clone(), new_price);
-
+
assert!(result.is_err());
assert_eq!(result.unwrap_err(), ContractError::CircuitBreakerTriggered);
@@ -135,7 +135,7 @@ fn test_twap_calculation() {
// Advance time for each observation
let mut ledger = env.ledger();
ledger.set_timestamp(env.ledger().timestamp() + 10);
-
+
if i == 0 {
// First update is already done in setup
continue;
@@ -145,11 +145,11 @@ fn test_twap_calculation() {
// Get price should return TWAP
let twap_price = OracleAdapter::get_price(&env, pair).unwrap();
-
+
// TWAP should be average of all observations
// Expected average is approximately 5.4% increase
let expected_avg = PRECISION.saturating_mul(10_540) / 10_000;
-
+
// Allow small rounding difference
assert!(twap_price >= expected_avg - 1_000_000);
assert!(twap_price <= expected_avg + 1_000_000);
@@ -168,13 +168,14 @@ fn test_update_config() {
OracleAdapter::update_config(
&env,
pair.clone(),
- Some(600), // 10 minutes staleness
- Some(500), // 5% circuit breaker
- Some(20), // TWAP window of 20
- ).unwrap();
+ Some(600), // 10 minutes staleness
+ Some(500), // 5% circuit breaker
+ Some(20), // TWAP window of 20
+ )
+ .unwrap();
let (config, _) = OracleAdapter::get_oracle_info(&env, pair).unwrap();
-
+
assert_eq!(config.staleness_threshold, 600);
assert_eq!(config.circuit_breaker_threshold_bps, 500);
assert_eq!(config.twap_window_size, 20);
@@ -190,14 +191,8 @@ fn test_invalid_config_rejected() {
setup_oracle(&env, pair.clone());
// Try to set invalid TWAP window size (0)
- let result = OracleAdapter::update_config(
- &env,
- pair.clone(),
- None,
- None,
- Some(0),
- );
-
+ let result = OracleAdapter::update_config(&env, pair.clone(), None, None, Some(0));
+
assert!(result.is_err());
assert_eq!(result.unwrap_err(), ContractError::InvalidConfig);
}
@@ -288,7 +283,7 @@ fn test_circuit_breaker_deactivates_on_stable_price() {
#[test]
fn test_deviation_calculation() {
let old_price = PRECISION;
-
+
// 10% deviation = 1000 bps
let new_price_10pct = (PRECISION as u128).saturating_mul(11_000) / 10_000;
let deviation = OracleAdapter::calculate_deviation_bps(old_price, new_price_10pct);
diff --git a/swaptrade-contracts/counter/src/orders.rs b/swaptrade-contracts/counter/src/orders.rs
index afe3d32..1605fee 100644
--- a/swaptrade-contracts/counter/src/orders.rs
+++ b/swaptrade-contracts/counter/src/orders.rs
@@ -6,21 +6,20 @@ use crate::errors::ContractError;
#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum OrderType {
- Market, // Execute immediately at best available price
- Limit, // Execute only at specified price or better
- StopLoss, // Execute when price reaches trigger (becomes market order)
- StopLimit, // Execute when price reaches trigger (becomes limit order)
- Recurring, // DCA / recurring order executed on a schedule
+ Market, // Execute immediately at best available price
+ Limit, // Execute only at specified price or better
+ StopLoss, // Execute when price reaches trigger (becomes market order)
+ StopLimit, // Execute when price reaches trigger (becomes limit order)
}
/// Order status
#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum OrderStatus {
- Pending, // Order is active and waiting to be filled
- Filled, // Order has been completely filled
- Cancelled, // Order was cancelled by user
- Expired, // Order expired without being filled
+ Pending, // Order is active and waiting to be filled
+ Filled, // Order has been completely filled
+ Cancelled, // Order was cancelled by user
+ Expired, // Order expired without being filled
PartiallyFilled, // Order is partially executed
Scheduled, // Recurring order is between executions, waiting for next run
}
@@ -36,11 +35,11 @@ pub struct Order {
pub token_out: Symbol,
pub amount_in: i128,
pub amount_filled: i128,
- pub limit_price: Option, // For limit orders: minimum acceptable price
- pub trigger_price: Option, // For stop orders: price that triggers execution
+ pub limit_price: Option, // For limit orders: minimum acceptable price
+ pub trigger_price: Option, // For stop orders: price that triggers execution
pub status: OrderStatus,
pub created_at: u64,
- pub expires_at: Option, // None means no expiry
+ pub expires_at: Option, // None means no expiry
pub filled_at: Option,
pub interval_secs: Option, // For recurring: seconds between executions
pub remaining_occurrences: Option, // For recurring: how many more times to execute
@@ -52,8 +51,8 @@ pub struct Order {
#[derive(Clone, Debug, PartialEq)]
pub struct OrderBook {
pub token_pair: (Symbol, Symbol),
- pub buy_orders: Vec, // Order IDs for buy orders
- pub sell_orders: Vec, // Order IDs for sell orders
+ pub buy_orders: Vec, // Order IDs for buy orders
+ pub sell_orders: Vec, // Order IDs for sell orders
}
/// Order manager - handles order lifecycle
@@ -164,10 +163,10 @@ impl OrderManager {
) -> Result, ContractError> {
let mut executed_orders = Vec::new(env);
let pair_key = Self::order_book_key(&(token_in.clone(), token_out.clone()));
-
+
// Get order book for this pair
let order_book: Option = env.storage().instance().get(&pair_key);
-
+
if order_book.is_none() {
return Ok(executed_orders);
}
@@ -189,7 +188,9 @@ impl OrderManager {
}
// Check if order can be executed
- if order.status == OrderStatus::Pending || order.status == OrderStatus::PartiallyFilled {
+ if order.status == OrderStatus::Pending
+ || order.status == OrderStatus::PartiallyFilled
+ {
let should_execute = match order.order_type {
OrderType::Limit => {
// Execute if current price is at or below limit
@@ -220,7 +221,12 @@ impl OrderManager {
// Emit execution event
env.events().publish(
(symbol_short!("ofill"), order_id),
- (order.owner, token_in.clone(), token_out.clone(), current_price),
+ (
+ order.owner,
+ token_in.clone(),
+ token_out.clone(),
+ current_price,
+ ),
);
}
}
@@ -243,13 +249,16 @@ impl OrderManager {
/// Get user's active orders
pub fn get_user_orders(env: &Env, user: Address) -> Vec {
let mut orders = Vec::new(env);
- let user_order_ids: Option> = env.storage().instance().get(&Self::user_orders_key(&user));
+ let user_order_ids: Option> =
+ env.storage().instance().get(&Self::user_orders_key(&user));
if let Some(order_ids) = user_order_ids {
for i in 0..order_ids.len() {
if let Some(order_id) = order_ids.get(i) {
if let Ok(order) = Self::get_order(env, order_id) {
- if order.status == OrderStatus::Pending || order.status == OrderStatus::PartiallyFilled || order.status == OrderStatus::Scheduled {
+ if order.status == OrderStatus::Pending
+ || order.status == OrderStatus::PartiallyFilled
+ {
orders.push_back(order);
}
}
@@ -273,8 +282,12 @@ impl OrderManager {
expires_at: Option,
) -> Result {
// Generate order ID
- let next_id: u64 = env.storage().instance().get(&symbol_short!("next_oid")).unwrap_or(1);
-
+ let next_id: u64 = env
+ .storage()
+ .instance()
+ .get(&symbol_short!("next_oid"))
+ .unwrap_or(1);
+
let order = Order {
order_id: next_id,
owner: owner.clone(),
@@ -298,23 +311,36 @@ impl OrderManager {
Self::save_order(env, &order);
// Add to user's order list
- let mut user_orders: Vec = env.storage()
+ let mut user_orders: Vec = env
+ .storage()
.instance()
.get(&Self::user_orders_key(&owner))
.unwrap_or_else(|| Vec::new(env));
user_orders.push_back(next_id);
- env.storage().instance().set(&Self::user_orders_key(&owner), &user_orders);
+ env.storage()
+ .instance()
+ .set(&Self::user_orders_key(&owner), &user_orders);
// Add to order book
Self::add_to_order_book(env, token_in.clone(), token_out.clone(), next_id);
// Increment next order ID
- env.storage().instance().set(&symbol_short!("next_oid"), &(next_id + 1));
+ env.storage()
+ .instance()
+ .set(&symbol_short!("next_oid"), &(next_id + 1));
// Emit order placement event
env.events().publish(
(symbol_short!("order_new"), next_id),
- (owner, order_type, token_in, token_out, amount_in, limit_price, trigger_price),
+ (
+ owner,
+ order_type,
+ token_in,
+ token_out,
+ amount_in,
+ limit_price,
+ trigger_price,
+ ),
);
Ok(next_id)
@@ -494,8 +520,9 @@ impl OrderManager {
fn add_to_order_book(env: &Env, token_in: Symbol, token_out: Symbol, order_id: u64) {
let pair = (token_in.clone(), token_out.clone());
let pair_key = Self::order_book_key(&pair);
-
- let mut book: OrderBook = env.storage()
+
+ let mut book: OrderBook = env
+ .storage()
.instance()
.get(&pair_key)
.unwrap_or(OrderBook {
diff --git a/swaptrade-contracts/counter/src/orders_tests.rs b/swaptrade-contracts/counter/src/orders_tests.rs
index e099914..95256bd 100644
--- a/swaptrade-contracts/counter/src/orders_tests.rs
+++ b/swaptrade-contracts/counter/src/orders_tests.rs
@@ -24,7 +24,8 @@ fn test_place_limit_order() {
1000,
PRECISION, // 1:1 price
None, // No expiry
- ).unwrap();
+ )
+ .unwrap();
assert_eq!(order_id, 1);
@@ -57,7 +58,8 @@ fn test_place_stop_loss() {
500,
trigger_price,
None,
- ).unwrap();
+ )
+ .unwrap();
assert_eq!(order_id, 1);
@@ -75,15 +77,9 @@ fn test_cancel_order() {
let usdc = symbol_short!("USDC");
// Place order
- let order_id = OrderManager::place_limit_order(
- &env,
- user.clone(),
- xlm,
- usdc,
- 1000,
- PRECISION,
- None,
- ).unwrap();
+ let order_id =
+ OrderManager::place_limit_order(&env, user.clone(), xlm, usdc, 1000, PRECISION, None)
+ .unwrap();
// Cancel order
OrderManager::cancel_order(&env, order_id, user.clone()).unwrap();
@@ -102,15 +98,9 @@ fn test_cancel_order_wrong_owner() {
let usdc = symbol_short!("USDC");
// Place order with user1
- let order_id = OrderManager::place_limit_order(
- &env,
- user1.clone(),
- xlm,
- usdc,
- 1000,
- PRECISION,
- None,
- ).unwrap();
+ let order_id =
+ OrderManager::place_limit_order(&env, user1.clone(), xlm, usdc, 1000, PRECISION, None)
+ .unwrap();
// Try to cancel with user2 (should fail)
let result = OrderManager::cancel_order(&env, order_id, user2);
@@ -125,8 +115,26 @@ fn test_get_user_orders() {
let usdc = symbol_short!("USDC");
// Place multiple orders
- OrderManager::place_limit_order(&env, user.clone(), xlm.clone(), usdc.clone(), 1000, PRECISION, None).unwrap();
- OrderManager::place_stop_loss(&env, user.clone(), xlm.clone(), usdc.clone(), 500, PRECISION, None).unwrap();
+ OrderManager::place_limit_order(
+ &env,
+ user.clone(),
+ xlm.clone(),
+ usdc.clone(),
+ 1000,
+ PRECISION,
+ None,
+ )
+ .unwrap();
+ OrderManager::place_stop_loss(
+ &env,
+ user.clone(),
+ xlm.clone(),
+ usdc.clone(),
+ 500,
+ PRECISION,
+ None,
+ )
+ .unwrap();
// Get user orders
let orders = OrderManager::get_user_orders(&env, user);
@@ -150,7 +158,8 @@ fn test_order_with_expiry() {
1000,
PRECISION,
Some(expiry),
- ).unwrap();
+ )
+ .unwrap();
let order = OrderManager::get_order(&env, order_id).unwrap();
assert_eq!(order.expires_at, Some(expiry));
@@ -176,15 +185,8 @@ fn test_invalid_order_amount() {
assert!(result.is_err());
// Try to place order with negative amount
- let result = OrderManager::place_stop_loss(
- &env,
- user.clone(),
- xlm,
- usdc,
- -100,
- PRECISION,
- None,
- );
+ let result =
+ OrderManager::place_stop_loss(&env, user.clone(), xlm, usdc, -100, PRECISION, None);
assert!(result.is_err());
}
@@ -208,15 +210,7 @@ fn test_invalid_order_price() {
assert!(result.is_err());
// Try to place stop-loss with zero trigger
- let result = OrderManager::place_stop_loss(
- &env,
- user,
- xlm,
- usdc,
- 500,
- 0,
- None,
- );
+ let result = OrderManager::place_stop_loss(&env, user, xlm, usdc, 500, 0, None);
assert!(result.is_err());
}
@@ -228,9 +222,36 @@ fn test_order_id_increment() {
let usdc = symbol_short!("USDC");
// Place multiple orders
- let id1 = OrderManager::place_limit_order(&env, user.clone(), xlm.clone(), usdc.clone(), 100, PRECISION, None).unwrap();
- let id2 = OrderManager::place_limit_order(&env, user.clone(), xlm.clone(), usdc.clone(), 200, PRECISION, None).unwrap();
- let id3 = OrderManager::place_stop_loss(&env, user.clone(), xlm.clone(), usdc.clone(), 300, PRECISION, None).unwrap();
+ let id1 = OrderManager::place_limit_order(
+ &env,
+ user.clone(),
+ xlm.clone(),
+ usdc.clone(),
+ 100,
+ PRECISION,
+ None,
+ )
+ .unwrap();
+ let id2 = OrderManager::place_limit_order(
+ &env,
+ user.clone(),
+ xlm.clone(),
+ usdc.clone(),
+ 200,
+ PRECISION,
+ None,
+ )
+ .unwrap();
+ let id3 = OrderManager::place_stop_loss(
+ &env,
+ user.clone(),
+ xlm.clone(),
+ usdc.clone(),
+ 300,
+ PRECISION,
+ None,
+ )
+ .unwrap();
assert_eq!(id1, 1);
assert_eq!(id2, 2);
@@ -253,15 +274,17 @@ fn test_match_pending_orders() {
1000,
PRECISION,
None,
- ).unwrap();
+ )
+ .unwrap();
// Match orders with current price at or below limit
let current_price = (PRECISION as u128).saturating_mul(9_900) / 10_000; // 1% below limit
- let executed = OrderManager::match_pending_orders(&env, xlm.clone(), usdc.clone(), current_price).unwrap();
+ let executed =
+ OrderManager::match_pending_orders(&env, xlm.clone(), usdc.clone(), current_price).unwrap();
// Order should be executed
assert!(executed.len() > 0);
-
+
let order = OrderManager::get_order(&env, order_id).unwrap();
assert_eq!(order.status, OrderStatus::Filled);
}
diff --git a/swaptrade-contracts/counter/src/referral_integration_test.rs b/swaptrade-contracts/counter/src/referral_integration_test.rs
index a43eded..939ad28 100644
--- a/swaptrade-contracts/counter/src/referral_integration_test.rs
+++ b/swaptrade-contracts/counter/src/referral_integration_test.rs
@@ -1,8 +1,9 @@
#[cfg(test)]
mod integration_tests {
-use soroban_sdk::{Env, Address};
- use soroban_sdk::testutils::{Address as _, Ledger as _};
+ use super::*;
use crate::CounterContract;
+ use soroban_sdk::testutils::{Address as _, Ledger as _};
+ use soroban_sdk::Address;
#[test]
fn test_referral_integration_with_swap() {
@@ -10,12 +11,15 @@ use soroban_sdk::{Env, Address};
let admin = Address::generate(&env);
let referrer = Address::generate(&env);
let trader = Address::generate(&env);
-
+
// Initialize contract
CounterContract::initialize(env.clone());
// Register referral
- assert!(CounterContract::register_referral(env.clone(), referrer.clone(), trader.clone()).is_ok());
+ assert!(
+ CounterContract::register_referral(env.clone(), referrer.clone(), trader.clone())
+ .is_ok()
+ );
// Check initial stats
let stats = CounterContract::get_referral_stats(env.clone(), referrer.clone());
@@ -24,9 +28,10 @@ use soroban_sdk::{Env, Address};
// Set up some basic liquidity for swap (this would normally be done through liquidity pools)
// For this test, we'll just verify the referral system integration points
-
+
// Verify commission balance starts at zero
- let commission_balance = CounterContract::get_commission_balance(env.clone(), referrer.clone());
+ let commission_balance =
+ CounterContract::get_commission_balance(env.clone(), referrer.clone());
assert_eq!(commission_balance, 0);
// The actual swap integration would be tested in the full integration test suite
@@ -38,7 +43,7 @@ use soroban_sdk::{Env, Address};
let env = Env::default();
let admin = Address::generate(&env);
let user = Address::generate(&env);
-
+
// Initialize contract
CounterContract::initialize(env.clone());
diff --git a/swaptrade-contracts/counter/src/referral_system.rs b/swaptrade-contracts/counter/src/referral_system.rs
index 57ea3e5..a2f53fd 100644
--- a/swaptrade-contracts/counter/src/referral_system.rs
+++ b/swaptrade-contracts/counter/src/referral_system.rs
@@ -1,6 +1,6 @@
-use soroban_sdk::{contracttype, Address, Env, Map, Vec, Symbol, symbol_short};
-use crate::storage::DataKey;
use crate::errors::SwapTradeError;
+use crate::storage::DataKey;
+use soroban_sdk::{contracttype, symbol_short, Address, Env, Map, Symbol, Vec};
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -30,7 +30,7 @@ pub struct ReferralStats {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TierConfig {
pub volume_threshold: i128,
- pub direct_commission_bps: u32, // basis points (1/100 of percent)
+ pub direct_commission_bps: u32, // basis points (1/100 of percent)
pub indirect_commission_bps: u32,
}
@@ -53,7 +53,11 @@ const DEFAULT_TIER_3: TierConfig = TierConfig {
indirect_commission_bps: 40, // 0.4%
};
-pub fn register_referral(env: &Env, referrer: Address, referred: Address) -> Result<(), SwapTradeError> {
+pub fn register_referral(
+ env: &Env,
+ referrer: Address,
+ referred: Address,
+) -> Result<(), SwapTradeError> {
// Authentication
referred.require_auth();
@@ -82,7 +86,7 @@ pub fn register_referral(env: &Env, referrer: Address, referred: Address) -> Res
level: ReferralLevel::Direct,
registration_timestamp: env.ledger().timestamp(),
};
-
+
let info_key = DataKey::ReferralInfo(referred.clone());
env.storage().instance().set(&info_key, &referral_info);
@@ -91,8 +95,8 @@ pub fn register_referral(env: &Env, referrer: Address, referred: Address) -> Res
// Emit event
env.events().publish(
- (Symbol::new(env, "referral_registered"),),
- (referrer, referred, ReferralLevel::Direct)
+ symbol_short!("referral_registered"),
+ (referrer, referred, ReferralLevel::Direct),
);
Ok(())
@@ -100,16 +104,24 @@ pub fn register_referral(env: &Env, referrer: Address, referred: Address) -> Res
fn is_circular_referral(env: &Env, referrer: &Address, referred: &Address) -> bool {
// Check if referrer is already referred by the referred user (direct circular)
- if let Some(existing_referrer) = env.storage().instance().get::<_, Address>(&DataKey::Referrer(referrer.clone())) {
+ if let Some(existing_referrer) = env
+ .storage()
+ .instance()
+ .get::<_, Address>(&DataKey::Referrer(referrer.clone()))
+ {
if existing_referrer == *referred {
return true;
}
}
-
+
// Check for indirect circular references up to 2 levels
let mut current_referrer = referrer.clone();
for _ in 0..2 {
- if let Some(next_referrer) = env.storage().instance().get::<_, Address>(&DataKey::Referrer(current_referrer.clone())) {
+ if let Some(next_referrer) = env
+ .storage()
+ .instance()
+ .get::<_, Address>(&DataKey::Referrer(current_referrer.clone()))
+ {
if next_referrer == *referred {
return true;
}
@@ -118,18 +130,18 @@ fn is_circular_referral(env: &Env, referrer: &Address, referred: &Address) -> bo
break;
}
}
-
+
false
}
fn update_referrer_stats(env: &Env, referrer: &Address, level: ReferralLevel) {
let mut stats = get_referral_stats(env, referrer.clone());
-
+
match level {
ReferralLevel::Direct => stats.direct_referrals += 1,
ReferralLevel::Indirect => stats.indirect_referrals += 1,
}
-
+
let stats_key = DataKey::ReferralStats(referrer.clone());
env.storage().instance().set(&stats_key, &stats);
}
@@ -170,33 +182,46 @@ pub fn calculate_and_distribute_commission(env: &Env, trader: Address, fee_amoun
update_user_trading_volume(env, trader.clone(), fee_amount);
// Get direct referrer
- if let Some(direct_referrer) = env.storage().instance().get::<_, Address>(&DataKey::Referrer(trader.clone())) {
+ if let Some(direct_referrer) = env
+ .storage()
+ .instance()
+ .get::<_, Address>(&DataKey::Referrer(trader.clone()))
+ {
// Calculate direct commission
let direct_commission = (fee_amount * tier_config.direct_commission_bps as i128) / 10000;
-
+
if direct_commission > 0 {
add_commission_balance(env, direct_referrer.clone(), direct_commission);
-
+
// Update stats
let mut stats = get_referral_stats(env, direct_referrer.clone());
stats.total_commission_earned += direct_commission;
stats.total_referee_volume += fee_amount;
- env.storage().instance().set(&DataKey::ReferralStats(direct_referrer.clone()), &stats);
+ env.storage()
+ .instance()
+ .set(&DataKey::ReferralStats(direct_referrer.clone()), &stats);
}
// Get indirect referrer (referrer's referrer)
- if let Some(indirect_referrer) = env.storage().instance().get::<_, Address>(&DataKey::Referrer(direct_referrer.clone())) {
+ if let Some(indirect_referrer) = env
+ .storage()
+ .instance()
+ .get::<_, Address>(&DataKey::Referrer(direct_referrer.clone()))
+ {
// Calculate indirect commission
- let indirect_commission = (fee_amount * tier_config.indirect_commission_bps as i128) / 10000;
-
+ let indirect_commission =
+ (fee_amount * tier_config.indirect_commission_bps as i128) / 10000;
+
if indirect_commission > 0 {
add_commission_balance(env, indirect_referrer.clone(), indirect_commission);
-
+
// Update stats
let mut stats = get_referral_stats(env, indirect_referrer.clone());
stats.total_commission_earned += indirect_commission;
stats.total_referee_volume += fee_amount;
- env.storage().instance().set(&DataKey::ReferralStats(indirect_referrer.clone()), &stats);
+ env.storage()
+ .instance()
+ .set(&DataKey::ReferralStats(indirect_referrer.clone()), &stats);
}
}
}
@@ -211,7 +236,10 @@ fn get_user_trading_volume(env: &Env, user: Address) -> i128 {
fn update_user_trading_volume(env: &Env, user: Address, additional_volume: i128) {
let current_volume = get_user_trading_volume(env, user.clone());
- env.storage().instance().set(&DataKey::TradingVolume(user), &(current_volume + additional_volume));
+ env.storage().instance().set(
+ &DataKey::TradingVolume(user),
+ &(current_volume + additional_volume),
+ );
}
fn add_commission_balance(env: &Env, user: Address, amount: i128) {
@@ -220,8 +248,11 @@ fn add_commission_balance(env: &Env, user: Address, amount: i128) {
.instance()
.get(&DataKey::CommissionBalance(user.clone()))
.unwrap_or(0);
-
- env.storage().instance().set(&DataKey::CommissionBalance(user), &(current_balance + amount));
+
+ env.storage().instance().set(
+ &DataKey::CommissionBalance(user),
+ &(current_balance + amount),
+ );
}
pub fn withdraw_commission(env: &Env, user: Address) -> i128 {
@@ -238,13 +269,13 @@ pub fn withdraw_commission(env: &Env, user: Address) -> i128 {
}
// Reset balance to zero before transfer (security best practice)
- env.storage().instance().set(&DataKey::CommissionBalance(user.clone()), &0);
+ env.storage()
+ .instance()
+ .set(&DataKey::CommissionBalance(user.clone()), &0);
// Emit event
- env.events().publish(
- (Symbol::new(env, "commission_withdrawn"),),
- (user, balance)
- );
+ env.events()
+ .publish(symbol_short!("commission_withdrawn"), (user, balance));
balance
}
@@ -255,3 +286,8 @@ pub fn get_commission_balance(env: &Env, user: Address) -> i128 {
.get(&DataKey::CommissionBalance(user))
.unwrap_or(0)
}
+
+#[cfg(test)]
+mod referral_integration_test;
+#[cfg(test)]
+mod referral_system_tests;
diff --git a/swaptrade-contracts/counter/src/referral_system_tests.rs b/swaptrade-contracts/counter/src/referral_system_tests.rs
index 97582eb..6a5aff9 100644
--- a/swaptrade-contracts/counter/src/referral_system_tests.rs
+++ b/swaptrade-contracts/counter/src/referral_system_tests.rs
@@ -25,7 +25,11 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
assert!(register_referral(&env, referrer.clone(), referred.clone()).is_ok());
// Verify referrer is set
- let stored_referrer = env.storage().instance().get::<_, Address>(&DataKey::Referrer(referred.clone())).unwrap();
+ let stored_referrer = env
+ .storage()
+ .instance()
+ .get::<_, Address>(&DataKey::Referrer(referred.clone()))
+ .unwrap();
assert_eq!(stored_referrer, referrer);
// Verify stats are updated
@@ -38,7 +42,10 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
fn test_self_referral_prevention() {
let (env, user, _, _) = setup_test_env();
- assert_eq!(register_referral(&env, user.clone(), user.clone()), Err(SwapTradeError::SelfReferral));
+ assert_eq!(
+ register_referral(&env, user.clone(), user.clone()),
+ Err(SwapTradeError::SelfReferral)
+ );
}
#[test]
@@ -49,7 +56,10 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
assert!(register_referral(&env, referrer1.clone(), referred.clone()).is_ok());
// Second referral should fail
- assert_eq!(register_referral(&env, referrer2.clone(), referred.clone()), Err(SwapTradeError::AlreadyReferred));
+ assert_eq!(
+ register_referral(&env, referrer2.clone(), referred.clone()),
+ Err(SwapTradeError::AlreadyReferred)
+ );
}
#[test]
@@ -60,7 +70,10 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
assert!(register_referral(&env, user_a.clone(), user_b.clone()).is_ok());
// Try circular referral: B refers A
- assert_eq!(register_referral(&env, user_b.clone(), user_a.clone()), Err(SwapTradeError::CircularReferral));
+ assert_eq!(
+ register_referral(&env, user_b.clone(), user_a.clone()),
+ Err(SwapTradeError::CircularReferral)
+ );
}
#[test]
@@ -74,7 +87,10 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
assert!(register_referral(&env, user_b.clone(), user_c.clone()).is_ok());
// Try C refers A (should fail - indirect circular)
- assert_eq!(register_referral(&env, user_c.clone(), user_a.clone()), Err(SwapTradeError::CircularReferral));
+ assert_eq!(
+ register_referral(&env, user_c.clone(), user_a.clone()),
+ Err(SwapTradeError::CircularReferral)
+ );
}
#[test]
@@ -103,7 +119,9 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
let (env, indirect_referrer, direct_referrer, trader) = setup_test_env();
// Set up two-level referral: indirect -> direct -> trader
- assert!(register_referral(&env, indirect_referrer.clone(), direct_referrer.clone()).is_ok());
+ assert!(
+ register_referral(&env, indirect_referrer.clone(), direct_referrer.clone()).is_ok()
+ );
assert!(register_referral(&env, direct_referrer.clone(), trader.clone()).is_ok());
// Simulate fee collection
@@ -131,17 +149,17 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
#[test]
fn test_tier_upgrade() {
let env = Env::default();
-
+
// Test tier 1 (default)
let tier_1 = get_tier_for_volume(&env, 1000);
assert_eq!(tier_1.direct_commission_bps, 50);
assert_eq!(tier_1.indirect_commission_bps, 20);
-
+
// Test tier 2
let tier_2 = get_tier_for_volume(&env, 15000);
assert_eq!(tier_2.direct_commission_bps, 75);
assert_eq!(tier_2.indirect_commission_bps, 30);
-
+
// Test tier 3
let tier_3 = get_tier_for_volume(&env, 60000);
assert_eq!(tier_3.direct_commission_bps, 100);
@@ -222,7 +240,11 @@ use soroban_sdk::{Env, Address, Symbol, symbol_short};
assert!(register_referral(&env, referrer.clone(), referred.clone()).is_ok());
// Check referral info
- let referral_info = env.storage().instance().get::<_, ReferralInfo>(&DataKey::ReferralInfo(referred.clone())).unwrap();
+ let referral_info = env
+ .storage()
+ .instance()
+ .get::<_, ReferralInfo>(&DataKey::ReferralInfo(referred.clone()))
+ .unwrap();
assert_eq!(referral_info.referrer, referrer);
assert_eq!(referral_info.level, ReferralLevel::Direct);
assert!(referral_info.registration_timestamp > 0);
diff --git a/swaptrade-contracts/counter/src/risk_management/alerts.rs b/swaptrade-contracts/counter/src/risk_management/alerts.rs
index 166e441..9d5a07d 100644
--- a/swaptrade-contracts/counter/src/risk_management/alerts.rs
+++ b/swaptrade-contracts/counter/src/risk_management/alerts.rs
@@ -1,8 +1,10 @@
use soroban_sdk::{Env, Symbol, Vec};
pub fn send_alert(env: &Env, user: Symbol, message: Symbol) {
- let key = (Symbol::new(env, "alerts"), user);
- let mut alerts: Vec = env.storage().temporary().get(&key).unwrap_or_else(|| Vec::new(env));
- alerts.push_back(message);
- env.storage().temporary().set(&key, &alerts);
-}
\ No newline at end of file
+ let mut alerts: Vec = env
+ .storage()
+ .get_unchecked(&format!("alerts_{}", user))
+ .unwrap_or_default();
+ alerts.push(message);
+ env.storage().set(&format!("alerts_{}", user), &alerts);
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/circuit_breaker.rs b/swaptrade-contracts/counter/src/risk_management/circuit_breaker.rs
index d6d1b5c..b53b465 100644
--- a/swaptrade-contracts/counter/src/risk_management/circuit_breaker.rs
+++ b/swaptrade-contracts/counter/src/risk_management/circuit_breaker.rs
@@ -1,6 +1,6 @@
-use soroban_sdk::{contracttype, Env, Map, Symbol, Vec, Address};
use crate::oracle::{get_stored_price, ContractError};
-use crate::risk_management::{RiskConfig, CircuitBreakerState};
+use crate::risk_management::{CircuitBreakerState, RiskConfig};
+use soroban_sdk::{contracttype, Address, Env, Map, Symbol, Vec};
/// Circuit breaker for extreme market moves
pub struct CircuitBreaker;
@@ -82,19 +82,24 @@ impl CircuitBreaker {
let mut prices = Vec::new(env);
// Try to get current price
- if let Some(current_data) = get_stored_price(env, (asset_symbol.clone(), Symbol::short("USD"))) {
+ if let Some(current_data) =
+ get_stored_price(env, (asset_symbol.clone(), Symbol::short("USD")))
+ {
if current_data.timestamp >= start_time && current_data.timestamp <= end_time {
prices.push_back((current_data.timestamp, current_data.price));
}
}
// Try inverse pair
- if let Some(current_data) = get_stored_price(env, (Symbol::short("USD"), asset_symbol.clone())) {
+ if let Some(current_data) =
+ get_stored_price(env, (Symbol::short("USD"), asset_symbol.clone()))
+ {
if current_data.timestamp >= start_time && current_data.timestamp <= end_time {
// Invert price
if current_data.price > 0 {
- let inverted = (1_000_000_000_000_000_000u128 * 1_000_000_000_000_000_000u128) / current_data.price;
- prices.push_back((current_data.timestamp, inverted));
+ let inverted = (1_000_000_000_000_000_000u128 * 1_000_000_000_000_000_000u128)
+ / current_data.price;
+ prices.push((current_data.timestamp, inverted));
}
}
}
@@ -150,4 +155,4 @@ impl CircuitBreaker {
.get(&Symbol::short("risk_cfg"))
.unwrap_or_default()
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/concentration_risk.rs b/swaptrade-contracts/counter/src/risk_management/concentration_risk.rs
index 0b79208..fd667a6 100644
--- a/swaptrade-contracts/counter/src/risk_management/concentration_risk.rs
+++ b/swaptrade-contracts/counter/src/risk_management/concentration_risk.rs
@@ -1,24 +1,21 @@
-use soroban_sdk::{contracttype, Env, Map, Symbol, Vec, Address};
-use crate::portfolio::{Portfolio, Asset};
+use crate::portfolio::{Asset, Portfolio};
use crate::risk_management::{RiskConfig, RiskMetrics};
+use soroban_sdk::{contracttype, Address, Env, Map, Symbol, Vec};
/// Portfolio concentration risk monitoring
pub struct ConcentrationRisk;
impl ConcentrationRisk {
/// Calculate portfolio concentration risk
- pub fn calculate_concentration_risk(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> u32 {
+ pub fn calculate_concentration_risk(env: &Env, portfolio: &Portfolio, user: &Address) -> u32 {
let total_value = Self::calculate_portfolio_value(env, portfolio, user);
if total_value == 0 {
return 0;
}
let xlm_value = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_value = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_value =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
// Calculate concentration as percentage of largest position
let max_position = xlm_value.max(usdc_value);
@@ -26,13 +23,17 @@ impl ConcentrationRisk {
// Convert to risk score (0-100)
// Risk increases exponentially as concentration approaches 100%
- if concentration_bps >= 8000 { // 80%
+ if concentration_bps >= 8000 {
+ // 80%
100
- } else if concentration_bps >= 6000 { // 60%
+ } else if concentration_bps >= 6000 {
+ // 60%
80 + ((concentration_bps - 6000) * 20) / 2000
- } else if concentration_bps >= 4000 { // 40%
+ } else if concentration_bps >= 4000 {
+ // 40%
60 + ((concentration_bps - 4000) * 20) / 2000
- } else if concentration_bps >= 2000 { // 20%
+ } else if concentration_bps >= 2000 {
+ // 20%
40 + ((concentration_bps - 2000) * 20) / 2000
} else {
(concentration_bps * 40) / 2000
@@ -40,11 +41,7 @@ impl ConcentrationRisk {
}
/// Check if concentration exceeds warning threshold
- pub fn check_concentration_warning(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> bool {
+ pub fn check_concentration_warning(env: &Env, portfolio: &Portfolio, user: &Address) -> bool {
let config = Self::get_risk_config(env);
let total_value = Self::calculate_portfolio_value(env, portfolio, user);
if total_value == 0 {
@@ -52,7 +49,8 @@ impl ConcentrationRisk {
}
let xlm_value = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_value = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_value =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
let max_position = xlm_value.max(usdc_value);
let concentration_bps = ((max_position * 10000) / total_value) as u32;
@@ -61,11 +59,7 @@ impl ConcentrationRisk {
}
/// Check if concentration exceeds limit threshold (should block trades)
- pub fn check_concentration_limit(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> bool {
+ pub fn check_concentration_limit(env: &Env, portfolio: &Portfolio, user: &Address) -> bool {
let config = Self::get_risk_config(env);
let total_value = Self::calculate_portfolio_value(env, portfolio, user);
if total_value == 0 {
@@ -73,7 +67,8 @@ impl ConcentrationRisk {
}
let xlm_value = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_value = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_value =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
let max_position = xlm_value.max(usdc_value);
let concentration_bps = ((max_position * 10000) / total_value) as u32;
@@ -93,21 +88,19 @@ impl ConcentrationRisk {
}
let xlm_value = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_value = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_value =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
let max_position = xlm_value.max(usdc_value);
((max_position * 10000) / total_value) as u32
}
/// Calculate total portfolio value
- fn calculate_portfolio_value(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> i128 {
+ fn calculate_portfolio_value(env: &Env, portfolio: &Portfolio, user: &Address) -> i128 {
// Simple sum for now - in production should use oracle prices
let xlm_balance = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_balance = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_balance =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
xlm_balance + usdc_balance
}
@@ -119,4 +112,4 @@ impl ConcentrationRisk {
.get(&Symbol::short("risk_cfg"))
.unwrap_or_default()
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/mod.rs b/swaptrade-contracts/counter/src/risk_management/mod.rs
index 018c9e2..822eeb9 100644
--- a/swaptrade-contracts/counter/src/risk_management/mod.rs
+++ b/swaptrade-contracts/counter/src/risk_management/mod.rs
@@ -1,17 +1,13 @@
-pub mod portfolio;
-pub mod position;
-pub mod volatility;
pub mod alerts;
pub mod circuit_breaker;
-pub mod volume_circuit_breaker;
-pub mod risk_metrics;
-pub mod position_limits;
pub mod concentration_risk;
+pub mod portfolio;
+pub mod position;
+pub mod position_limits;
+pub mod risk_metrics;
+pub mod volatility;
pub use circuit_breaker::*;
-pub use volume_circuit_breaker::*;
-pub use risk_metrics::*;
-pub use position_limits::*;
pub use concentration_risk::*;
-pub use portfolio::*;
-pub use position::*;
+pub use position_limits::*;
+pub use risk_metrics::*;
diff --git a/swaptrade-contracts/counter/src/risk_management/portfolio.rs b/swaptrade-contracts/counter/src/risk_management/portfolio.rs
index 996fb28..948da5c 100644
--- a/swaptrade-contracts/counter/src/risk_management/portfolio.rs
+++ b/swaptrade-contracts/counter/src/risk_management/portfolio.rs
@@ -1,34 +1,32 @@
-use soroban_sdk::{Env, Symbol, Map, Address};
use crate::portfolio::Portfolio;
-use crate::risk_management::{RiskMetrics, ConcentrationRisk, CircuitBreaker, RiskConfig};
+use crate::risk_management::{CircuitBreaker, ConcentrationRisk, RiskConfig, RiskMetrics};
+use soroban_sdk::{Address, Env, Map, Symbol};
/// Portfolio risk assessment
pub struct PortfolioRisk;
impl PortfolioRisk {
/// Calculate comprehensive risk metrics for a user
- pub fn calculate_risk_metrics(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> RiskMetrics {
+ pub fn calculate_risk_metrics(env: &Env, portfolio: &Portfolio, user: &Address) -> RiskMetrics {
let config = Self::get_risk_config(env);
// Calculate individual risk components
- let concentration_risk = ConcentrationRisk::calculate_concentration_risk(env, portfolio, user);
+ let concentration_risk =
+ ConcentrationRisk::calculate_concentration_risk(env, portfolio, user);
let position_size_risk = Self::calculate_position_size_risk(env, portfolio, user);
let volatility_risk = Self::calculate_volatility_risk(env);
// Calculate overall risk score using weighted average
- let overall_risk_score = (
- (concentration_risk as u32 * config.risk_weights.concentration_weight) +
- (position_size_risk as u32 * config.risk_weights.position_size_weight) +
- (volatility_risk as u32 * config.risk_weights.volatility_weight)
- ) / 100;
+ let overall_risk_score = ((concentration_risk as u32
+ * config.risk_weights.concentration_weight)
+ + (position_size_risk as u32 * config.risk_weights.position_size_weight)
+ + (volatility_risk as u32 * config.risk_weights.volatility_weight))
+ / 100;
// Calculate exposure and other metrics
let total_exposure_usd = Self::calculate_total_exposure(env, portfolio, user);
- let largest_position_pct = ConcentrationRisk::get_largest_position_percentage(env, portfolio, user);
+ let largest_position_pct =
+ ConcentrationRisk::get_largest_position_percentage(env, portfolio, user);
let positions_over_limit = Self::count_positions_over_limit(env, portfolio, user);
let circuit_breaker_active = CircuitBreaker::is_circuit_breaker_active(env);
@@ -46,11 +44,7 @@ impl PortfolioRisk {
}
/// Calculate position size risk (0-100)
- fn calculate_position_size_risk(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> u32 {
+ fn calculate_position_size_risk(env: &Env, portfolio: &Portfolio, user: &Address) -> u32 {
let config = Self::get_risk_config(env);
let user_tier = portfolio.get_user_tier(env, user.clone());
@@ -59,8 +53,10 @@ impl PortfolioRisk {
let mut position_count = 0u32;
for (_, size) in positions.iter() {
- let size_abs = if size < 0 { -size } else { size };
- let max_allowed = crate::risk_management::PositionLimits::get_tier_position_limit(&config, &user_tier);
+ let size_abs = if size < 0 { -size } else { *size };
+ let max_allowed = crate::risk_management::PositionLimits::get_tier_position_limit(
+ &config, &user_tier,
+ );
if max_allowed > 0 {
let utilization = (size_abs * 100) / max_allowed;
@@ -102,30 +98,28 @@ impl PortfolioRisk {
}
/// Calculate total exposure in USD
- fn calculate_total_exposure(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> i128 {
+ fn calculate_total_exposure(env: &Env, portfolio: &Portfolio, user: &Address) -> i128 {
// Simple calculation - in production should use oracle prices
let xlm_balance = portfolio.balance_of(env, crate::portfolio::Asset::XLM, user.clone());
- let usdc_balance = portfolio.balance_of(env, crate::portfolio::Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_balance = portfolio.balance_of(
+ env,
+ crate::portfolio::Asset::Custom(Symbol::short("USDCSIM")),
+ user.clone(),
+ );
// Assume 1 XLM = 1 USD for simulation
xlm_balance + usdc_balance
}
/// Count positions that exceed limits
- fn count_positions_over_limit(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> u32 {
+ fn count_positions_over_limit(env: &Env, portfolio: &Portfolio, user: &Address) -> u32 {
let positions = Self::get_user_positions(env, portfolio, user);
let mut over_limit = 0u32;
for (asset, _) in positions.iter() {
- if let Err(_) = crate::risk_management::PositionLimits::check_position_limits(env, portfolio, user, &asset, 0) {
+ if let Err(_) = crate::risk_management::PositionLimits::check_position_limits(
+ env, portfolio, user, &asset, 0,
+ ) {
over_limit += 1;
}
}
@@ -142,13 +136,20 @@ impl PortfolioRisk {
let mut positions = Map::new(env);
let xlm_balance = portfolio.balance_of(env, crate::portfolio::Asset::XLM, user.clone());
- let usdc_balance = portfolio.balance_of(env, crate::portfolio::Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_balance = portfolio.balance_of(
+ env,
+ crate::portfolio::Asset::Custom(Symbol::short("USDCSIM")),
+ user.clone(),
+ );
if xlm_balance != 0 {
positions.set(crate::portfolio::Asset::XLM, xlm_balance);
}
if usdc_balance != 0 {
- positions.set(crate::portfolio::Asset::Custom(Symbol::short("USDCSIM")), usdc_balance);
+ positions.set(
+ crate::portfolio::Asset::Custom(Symbol::short("USDCSIM")),
+ usdc_balance,
+ );
}
positions
@@ -161,4 +162,4 @@ impl PortfolioRisk {
.get(&Symbol::short("risk_cfg"))
.unwrap_or_default()
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/position.rs b/swaptrade-contracts/counter/src/risk_management/position.rs
index 4268a0a..751a858 100644
--- a/swaptrade-contracts/counter/src/risk_management/position.rs
+++ b/swaptrade-contracts/counter/src/risk_management/position.rs
@@ -1,6 +1,6 @@
-use soroban_sdk::{Env, Symbol, Address, Map};
-use crate::portfolio::{Portfolio, Asset};
-use crate::risk_management::{PositionLimits, PositionLimitError};
+use crate::portfolio::{Asset, Portfolio};
+use crate::risk_management::{PositionLimitError, PositionLimits};
+use soroban_sdk::{Address, Env, Map, Symbol};
/// Position management and limits
pub struct PositionManager;
@@ -37,7 +37,8 @@ impl PositionManager {
// For now, just XLM and USDC positions
let xlm_balance = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_balance = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_balance =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
if xlm_balance != 0 {
positions.set(Asset::XLM, xlm_balance);
@@ -50,11 +51,7 @@ impl PositionManager {
}
/// Check if user has exceeded position limits
- pub fn has_exceeded_limits(
- env: &Env,
- portfolio: &Portfolio,
- user: &Address,
- ) -> bool {
+ pub fn has_exceeded_limits(env: &Env, portfolio: &Portfolio, user: &Address) -> bool {
let positions = Self::get_user_positions(env, portfolio, user);
for (asset, size) in positions.iter() {
@@ -65,4 +62,4 @@ impl PositionManager {
false
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/position_limits.rs b/swaptrade-contracts/counter/src/risk_management/position_limits.rs
index e35ee55..4e519c0 100644
--- a/swaptrade-contracts/counter/src/risk_management/position_limits.rs
+++ b/swaptrade-contracts/counter/src/risk_management/position_limits.rs
@@ -1,7 +1,7 @@
-use soroban_sdk::{contracttype, Env, Map, Symbol, Vec, Address};
-use crate::portfolio::{Portfolio, Asset};
-use crate::tiers::{UserTier, calculate_user_tier};
+use crate::portfolio::{Asset, Portfolio};
use crate::risk_management::RiskConfig;
+use crate::tiers::{calculate_user_tier, UserTier};
+use soroban_sdk::{contracttype, Address, Env, Map, Symbol, Vec};
/// Position limit enforcement
pub struct PositionLimits;
@@ -49,20 +49,20 @@ impl PositionLimits {
/// Get position limit based on user tier
pub fn get_tier_position_limit(config: &RiskConfig, tier: &UserTier) -> i128 {
match tier {
- UserTier::Novice => config.max_position_per_asset / 10, // 10% of base
- UserTier::Trader => config.max_position_per_asset / 4, // 25% of base
- UserTier::Expert => config.max_position_per_asset / 2, // 50% of base
- UserTier::Whale => config.max_position_per_asset, // 100% of base
+ UserTier::Novice => config.max_position_per_asset / 10, // 10% of base
+ UserTier::Trader => config.max_position_per_asset / 4, // 25% of base
+ UserTier::Expert => config.max_position_per_asset / 2, // 50% of base
+ UserTier::Whale => config.max_position_per_asset, // 100% of base
}
}
/// Get portfolio limit based on user tier
pub fn get_tier_portfolio_limit(config: &RiskConfig, tier: &UserTier) -> i128 {
match tier {
- UserTier::Novice => config.max_position_per_user / 10, // 10% of base
- UserTier::Trader => config.max_position_per_user / 4, // 25% of base
- UserTier::Expert => config.max_position_per_user / 2, // 50% of base
- UserTier::Whale => config.max_position_per_user, // 100% of base
+ UserTier::Novice => config.max_position_per_user / 10, // 10% of base
+ UserTier::Trader => config.max_position_per_user / 4, // 25% of base
+ UserTier::Expert => config.max_position_per_user / 2, // 50% of base
+ UserTier::Whale => config.max_position_per_user, // 100% of base
}
}
@@ -75,7 +75,8 @@ impl PositionLimits {
// For simplicity, sum all balances (assuming same decimals)
// In production, this should convert to USD value using oracles
let xlm_balance = portfolio.balance_of(env, Asset::XLM, user.clone());
- let usdc_balance = portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
+ let usdc_balance =
+ portfolio.balance_of(env, Asset::Custom(Symbol::short("USDCSIM")), user.clone());
xlm_balance + usdc_balance
}
@@ -94,7 +95,9 @@ impl PositionLimits {
if !config.risk_weights.validate() {
panic!("Risk weights must sum to 100");
}
- env.storage().instance().set(&Symbol::short("risk_cfg"), config);
+ env.storage()
+ .instance()
+ .set(&Symbol::short("risk_cfg"), config);
}
}
@@ -110,4 +113,4 @@ pub enum PositionLimitError {
requested: i128,
limit: i128,
},
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/risk_metrics.rs b/swaptrade-contracts/counter/src/risk_management/risk_metrics.rs
index d4e27bf..0abaeac 100644
--- a/swaptrade-contracts/counter/src/risk_management/risk_metrics.rs
+++ b/swaptrade-contracts/counter/src/risk_management/risk_metrics.rs
@@ -1,5 +1,5 @@
-use soroban_sdk::{contracttype, Env, Map, Symbol, Vec, Address};
use crate::portfolio::Asset;
+use soroban_sdk::{contracttype, Address, Env, Map, Symbol, Vec};
/// Risk metrics returned by get_risk_metrics()
#[contracttype]
@@ -69,11 +69,11 @@ impl Default for RiskConfig {
fn default() -> Self {
Self {
max_position_per_user: 1000000000000, // 1M tokens (with 6 decimals)
- max_position_per_asset: 500000000000, // 500K tokens per asset
- concentration_warn_threshold: 3000, // 30%
- concentration_limit_threshold: 5000, // 50%
- circuit_breaker_threshold: 1500, // 15%
- circuit_breaker_window: 3600, // 1 hour
+ max_position_per_asset: 500000000000, // 500K tokens per asset
+ concentration_warning_threshold: 3000, // 30%
+ concentration_limit_threshold: 5000, // 50%
+ circuit_breaker_threshold: 1500, // 15%
+ circuit_breaker_window: 3600, // 1 hour
risk_weights: RiskWeights {
concentration_weight: 40,
position_size_weight: 35,
@@ -110,4 +110,4 @@ impl Default for CircuitBreakerState {
recovery_price: None,
}
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/risk_management/volatility.rs b/swaptrade-contracts/counter/src/risk_management/volatility.rs
index f43c9e0..4458808 100644
--- a/swaptrade-contracts/counter/src/risk_management/volatility.rs
+++ b/swaptrade-contracts/counter/src/risk_management/volatility.rs
@@ -2,7 +2,13 @@ use soroban_sdk::{Env, Symbol};
pub fn check_circuit_breaker(env: &Env, asset: Symbol) -> bool {
// Example: trigger breaker if volatility exceeds threshold
- let key = (Symbol::new(env, "vol"), asset);
- let volatility: i32 = env.storage().temporary().get(&key).unwrap_or(0);
- volatility > 50
-}
\ No newline at end of file
+ let volatility: i32 = env
+ .storage()
+ .get_unchecked(&format!("vol_{}", asset))
+ .unwrap_or(0);
+ if volatility > 50 {
+ true // Circuit breaker triggered
+ } else {
+ false
+ }
+}
diff --git a/swaptrade-contracts/counter/src/risk_management_tests.rs b/swaptrade-contracts/counter/src/risk_management_tests.rs
index 1cb51cf..3422060 100644
--- a/swaptrade-contracts/counter/src/risk_management_tests.rs
+++ b/swaptrade-contracts/counter/src/risk_management_tests.rs
@@ -1,15 +1,13 @@
#[cfg(test)]
mod risk_management_tests {
use super::*;
- use crate::CounterContract;
- use soroban_sdk::{testutils::Address as _, testutils::Ledger as _, Address, Env, Symbol, symbol_short};
- use crate::portfolio::{Portfolio, Asset};
- use crate::tiers::UserTier;
+ use crate::portfolio::{Asset, Portfolio};
use crate::risk_management::{
- RiskConfig, RiskMetrics, CircuitBreakerState,
- PositionLimits, ConcentrationRisk, CircuitBreaker, PortfolioRisk,
- PositionLimitError,
+ CircuitBreaker, CircuitBreakerState, ConcentrationRisk, PortfolioRisk, PositionLimitError,
+ PositionLimits, RiskConfig, RiskMetrics,
};
+ use crate::tiers::UserTier;
+ use soroban_sdk::{symbol_short, testutils::Address as _, Address, Env, Symbol};
// ===== POSITION LIMITS TESTS =====
@@ -79,7 +77,12 @@ mod risk_management_tests {
// Balanced portfolio: 50% XLM, 50% USDC
portfolio.credit(&env, Asset::XLM, user.clone(), 500);
- portfolio.credit(&env, Asset::Custom(symbol_short!("USDCSIM")), user.clone(), 500);
+ portfolio.credit(
+ &env,
+ Asset::Custom(symbol_short!("USDCSIM")),
+ user.clone(),
+ 500,
+ );
let risk = ConcentrationRisk::calculate_concentration_risk(&env, &portfolio, &user);
assert!(risk < 30); // Should be low risk
@@ -93,7 +96,12 @@ mod risk_management_tests {
// Highly concentrated: 95% XLM, 5% USDC
portfolio.credit(&env, Asset::XLM, user.clone(), 950);
- portfolio.credit(&env, Asset::Custom(symbol_short!("USDCSIM")), user.clone(), 50);
+ portfolio.credit(
+ &env,
+ Asset::Custom(symbol_short!("USDCSIM")),
+ user.clone(),
+ 50,
+ );
let risk = ConcentrationRisk::calculate_concentration_risk(&env, &portfolio, &user);
assert!(risk > 70); // Should be high risk
@@ -107,7 +115,12 @@ mod risk_management_tests {
// 35% concentration (above 30% threshold)
portfolio.credit(&env, Asset::XLM, user.clone(), 700);
- portfolio.credit(&env, Asset::Custom(symbol_short!("USDCSIM")), user.clone(), 300);
+ portfolio.credit(
+ &env,
+ Asset::Custom(symbol_short!("USDCSIM")),
+ user.clone(),
+ 300,
+ );
let warning = ConcentrationRisk::check_concentration_warning(&env, &portfolio, &user);
assert!(warning);
@@ -161,7 +174,12 @@ mod risk_management_tests {
// Set up a test portfolio
portfolio.credit(&env, Asset::XLM, user.clone(), 600);
- portfolio.credit(&env, Asset::Custom(symbol_short!("USDCSIM")), user.clone(), 400);
+ portfolio.credit(
+ &env,
+ Asset::Custom(symbol_short!("USDCSIM")),
+ user.clone(),
+ 400,
+ );
let metrics = PortfolioRisk::calculate_risk_metrics(&env, &portfolio, &user);
@@ -202,12 +220,8 @@ mod risk_management_tests {
let to = symbol_short!("USDCSIM");
let amount = 100; // Small amount should be OK
- let would_exceed = CounterContract::check_risk_limits(
- env.clone(),
- user.clone(),
- to,
- amount,
- );
+ let would_exceed =
+ CounterContract::check_risk_limits(env.clone(), user.clone(), to, amount);
assert!(!would_exceed);
}
@@ -220,7 +234,12 @@ mod risk_management_tests {
// Create highly concentrated portfolio
portfolio.credit(&env, Asset::XLM, user.clone(), 900);
- portfolio.credit(&env, Asset::Custom(symbol_short!("USDCSIM")), user.clone(), 100);
+ portfolio.credit(
+ &env,
+ Asset::Custom(symbol_short!("USDCSIM")),
+ user.clone(),
+ 100,
+ );
// Save portfolio to storage for contract functions
env.storage().instance().set(&(), &portfolio);
@@ -304,4 +323,4 @@ mod risk_management_tests {
let retrieved_config = PositionLimits::get_risk_config(&env);
assert_eq!(retrieved_config.max_position_per_user, 2000000000000);
}
-}
\ No newline at end of file
+}
diff --git a/swaptrade-contracts/counter/src/staking_bonus.rs b/swaptrade-contracts/counter/src/staking_bonus.rs
index 8aad2b7..215cb38 100644
--- a/swaptrade-contracts/counter/src/staking_bonus.rs
+++ b/swaptrade-contracts/counter/src/staking_bonus.rs
@@ -1,3 +1,4 @@
+use crate::errors::SwapTradeError;
/// Staking Bonus System
///
/// Provides long-term staking bonuses with:
@@ -12,7 +13,6 @@
/// - 90 days: 20% bonus
/// - 365 days: 50% bonus
use soroban_sdk::{contracttype, symbol_short, Address, Env, Vec};
-use crate::errors::SwapTradeError;
// ────────────────────────────────────────────────────────────────────────────
// Constants
@@ -254,7 +254,11 @@ impl StakingBonusManager {
///
/// # Returns
/// Result with (principal_returned, penalty) or error
- pub fn unstake_early(env: &Env, user: Address, stake_id: u32) -> Result<(i128, i128), SwapTradeError> {
+ pub fn unstake_early(
+ env: &Env,
+ user: Address,
+ stake_id: u32,
+ ) -> Result<(i128, i128), SwapTradeError> {
let mut stakes: Vec = env
.storage()
.persistent()
diff --git a/swaptrade-contracts/counter/src/state_snapshot.rs b/swaptrade-contracts/counter/src/state_snapshot.rs
index 6a56e8e..d580c38 100644
--- a/swaptrade-contracts/counter/src/state_snapshot.rs
+++ b/swaptrade-contracts/counter/src/state_snapshot.rs
@@ -10,7 +10,7 @@
//! - Use local variables for state reads
//! - Validate before commit
-use soroban_sdk::{contracttype, Env, Address, Symbol};
+use soroban_sdk::{contracttype, Address, Env, Symbol};
/// Snapshot of critical state for validation
#[contracttype]
@@ -47,7 +47,10 @@ impl StateSnapshotManager {
/// Get next snapshot ID
fn get_next_snapshot_id(env: &Env) -> u64 {
- let key = (soroban_sdk::symbol_short!("snap"), soroban_sdk::symbol_short!("id"));
+ let key = (
+ soroban_sdk::symbol_short!("snap"),
+ soroban_sdk::symbol_short!("id"),
+ );
let current_id: u64 = env.storage().temporary().get(&key).unwrap_or(0);
let next_id = current_id + 1;
env.storage().temporary().set(&key, &next_id);
@@ -60,7 +63,7 @@ pub struct AtomicOperation;
impl AtomicOperation {
/// Execute an atomic operation with state validation
- ///
+ ///
/// This function ensures that:
/// 1. All state is read before any mutations
/// 2. State is validated before committing changes
@@ -123,9 +126,9 @@ impl StateConsistencyChecker {
new_state: &T,
allowed_transitions: &[(T, T)],
) -> bool {
- allowed_transitions.iter().any(|(from, to)| {
- from == old_state && to == new_state
- })
+ allowed_transitions
+ .iter()
+ .any(|(from, to)| from == old_state && to == new_state)
}
/// Validate that all required preconditions are met before state mutation
@@ -137,10 +140,7 @@ impl StateConsistencyChecker {
}
/// Execute state mutation with pre and post validation
- pub fn execute_with_validation(
- operation: F,
- validator: V,
- ) -> Result
+ pub fn execute_with_validation(operation: F, validator: V) -> Result
where
F: FnOnce() -> R,
V: FnOnce(&R) -> bool,
diff --git a/swaptrade-contracts/counter/src/state_snapshot_tests.rs b/swaptrade-contracts/counter/src/state_snapshot_tests.rs
index 6806271..d1fd876 100644
--- a/swaptrade-contracts/counter/src/state_snapshot_tests.rs
+++ b/swaptrade-contracts/counter/src/state_snapshot_tests.rs
@@ -115,8 +115,7 @@ mod tests {
#[test]
fn test_execute_with_validation_failure() {
- let result =
- StateConsistencyChecker::execute_with_validation(|| 42, |value| *value == 100);
+ let result = StateConsistencyChecker::execute_with_validation(|| 42, |value| *value == 100);
assert!(result.is_err());
}
diff --git a/swaptrade-contracts/counter/src/storage.rs b/swaptrade-contracts/counter/src/storage.rs
index 00c41e9..87db3e6 100644
--- a/swaptrade-contracts/counter/src/storage.rs
+++ b/swaptrade-contracts/counter/src/storage.rs
@@ -1,8 +1,9 @@
-use soroban_sdk::{contracttype, symbol_short, Symbol, Address};
+use soroban_sdk::{contracttype, symbol_short, Address, Symbol};
pub const ADMIN_KEY: Symbol = symbol_short!("admin");
pub const PAUSED_KEY: Symbol = symbol_short!("paused");
pub const POOL_REGISTRY_KEY: Symbol = symbol_short!("pools");
+pub const DEFAULT_TREASURY_KEY: Symbol = symbol_short!("treasury");
#[contracttype]
#[derive(Clone)]
@@ -11,7 +12,8 @@ pub enum DataKey {
Admin,
Paused,
PoolRegistry,
-
+ DefaultTreasury,
+
// Referral system keys
Referrer(Address),
ReferralInfo(Address),