//! Perpetual Futures Trading Contract //! //! This contract implements perpetual futures trading with: //! - Long and Short positions //! - Leverage from 2x to 100x //! - Funding rate mechanism (keeps price anchored to spot) //! - Mark price for liquidations (manipulation resistant) //! - Index price from oracles //! - Liquidation engine with insurance fund //! - Cross-margin and isolated margin modes //! //! # Architecture (Similar to dYdX/GMX) //! //! ```text //! ┌─────────────────────────────────────────────────────────────┐ //! │ PERPETUAL FUTURES │ //! ├─────────────────────────────────────────────────────────────┤ //! │ PRICE FEEDS: │ //! │ ┌─────────┐ ┌───────────┐ ┌─────────────┐ │ //! │ │ Oracle │────►│Index Price│────►│ Mark Price │ │ //! │ │ (Pyth) │ │ (Spot) │ │ (EMA-based) │ │ //! │ └─────────┘ └───────────┘ └─────────────┘ │ //! │ │ │ //! │ POSITIONS: ▼ │ //! │ ┌─────────────────────────────────────────────────────┐ │ //! │ │ Position { size, collateral, entry_price, leverage }│ │ //! │ │ PnL = size * (mark_price - entry_price) │ │ //! │ │ Liquidation when: margin_ratio < maintenance_margin │ │ //! │ └─────────────────────────────────────────────────────┘ │ //! │ │ //! │ FUNDING: │ //! │ ┌─────────────────────────────────────────────────────┐ │ //! │ │ funding_rate = (mark_price - index_price) / 8h │ │ //! │ │ if mark > index: longs pay shorts │ │ //! │ │ if mark < index: shorts pay longs │ │ //! │ └─────────────────────────────────────────────────────┘ │ //! └─────────────────────────────────────────────────────────────┘ //! ``` #![no_std] extern crate alloc; use alloc::string::String; use alloc::vec::Vec; use borsh::{BorshDeserialize, BorshSerialize}; use synor_sdk::prelude::*; use synor_sdk::{require, require_auth}; // ============================================================================= // CONSTANTS // ============================================================================= /// Minimum leverage (2x) pub const MIN_LEVERAGE: u64 = 2; /// Maximum leverage (100x) pub const MAX_LEVERAGE: u64 = 100; /// Basis points denominator (10000 = 100%) pub const BPS_DENOMINATOR: u64 = 10000; /// Initial margin requirement (1% at 100x leverage = 100 bps) pub const INITIAL_MARGIN_BPS: u64 = 100; /// Maintenance margin (0.5% = 50 bps) - below this, liquidation pub const MAINTENANCE_MARGIN_BPS: u64 = 50; /// Liquidation fee (5% = 500 bps) - goes to liquidator + insurance pub const LIQUIDATION_FEE_BPS: u64 = 500; /// Insurance fund share of liquidation fee (50%) pub const INSURANCE_SHARE_BPS: u64 = 5000; /// Maximum funding rate (0.1% per 8h = 10 bps) pub const MAX_FUNDING_RATE_BPS: u64 = 10; /// Funding interval (8 hours in seconds) pub const FUNDING_INTERVAL: u64 = 8 * 60 * 60; /// Price precision (6 decimals, like USDC) pub const PRICE_PRECISION: u64 = 1_000_000; /// Size precision (8 decimals) pub const SIZE_PRECISION: u64 = 100_000_000; // ============================================================================= // EVENT TOPICS // ============================================================================= fn event_topic(name: &[u8]) -> [u8; 32] { use synor_sdk::crypto::blake3_hash; blake3_hash(name).0 } // ============================================================================= // STORAGE KEYS // ============================================================================= mod keys { pub const OWNER: &[u8] = b"perps:owner"; pub const INITIALIZED: &[u8] = b"perps:initialized"; pub const PAUSED: &[u8] = b"perps:paused"; // Market configuration pub const MARKETS: &[u8] = b"perps:markets"; pub const MARKET_COUNT: &[u8] = b"perps:market_count"; // Positions pub const POSITIONS: &[u8] = b"perps:positions"; pub const POSITION_COUNT: &[u8] = b"perps:position_count"; pub const USER_POSITIONS: &[u8] = b"perps:user_positions"; // Funding pub const LAST_FUNDING_TIME: &[u8] = b"perps:last_funding"; pub const CUMULATIVE_FUNDING: &[u8] = b"perps:cum_funding"; // Insurance fund pub const INSURANCE_FUND: &[u8] = b"perps:insurance"; // Oracle pub const ORACLE: &[u8] = b"perps:oracle"; pub const PRICES: &[u8] = b"perps:prices"; } // ============================================================================= // DATA STRUCTURES // ============================================================================= /// Position direction #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum Direction { Long, Short, } /// Margin mode #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum MarginMode { /// Each position has isolated margin Isolated, /// All positions share margin Cross, } /// Market status #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum MarketStatus { Active, Paused, SettleOnly, Delisted, } /// Market configuration #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct Market { /// Market ID pub id: u32, /// Base asset symbol (e.g., "BTC") pub base_asset: String, /// Quote asset symbol (e.g., "USD") pub quote_asset: String, /// Market status pub status: MarketStatus, /// Maximum leverage allowed pub max_leverage: u64, /// Maintenance margin requirement (bps) pub maintenance_margin_bps: u64, /// Taker fee (bps) pub taker_fee_bps: u64, /// Maker fee (bps, can be negative for rebates) pub maker_fee_bps: i64, /// Minimum position size pub min_size: u64, /// Maximum position size per user pub max_size: u64, /// Total open interest (long) pub open_interest_long: u64, /// Total open interest (short) pub open_interest_short: u64, /// Maximum open interest pub max_open_interest: u64, } /// A trading position #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct Position { /// Position ID pub id: u64, /// Owner address pub owner: Address, /// Market ID pub market_id: u32, /// Position direction pub direction: Direction, /// Position size (in SIZE_PRECISION) pub size: u64, /// Collateral amount (in PRICE_PRECISION) pub collateral: u64, /// Entry price (in PRICE_PRECISION) pub entry_price: u64, /// Leverage (2x to 100x) pub leverage: u64, /// Margin mode pub margin_mode: MarginMode, /// Cumulative funding at entry pub entry_funding: i64, /// Timestamp when opened pub opened_at: u64, /// Last update timestamp pub updated_at: u64, /// Whether position is active pub is_open: bool, } /// Price data from oracle #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct PriceData { /// Index price (spot price from oracles) pub index_price: u64, /// Mark price (EMA-smoothed, used for liquidations) pub mark_price: u64, /// Last update timestamp pub timestamp: u64, /// Confidence interval pub confidence: u64, } /// Funding rate data #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct FundingData { /// Current funding rate (can be negative, in bps) pub rate_bps: i64, /// Cumulative funding for longs pub cumulative_long: i64, /// Cumulative funding for shorts pub cumulative_short: i64, /// Last funding timestamp pub last_update: u64, } // ============================================================================= // EVENTS // ============================================================================= #[derive(BorshSerialize)] pub struct MarketCreated { pub market_id: u32, pub base_asset: String, pub quote_asset: String, pub max_leverage: u64, } #[derive(BorshSerialize)] pub struct PositionOpened { pub position_id: u64, pub owner: Address, pub market_id: u32, pub direction: Direction, pub size: u64, pub collateral: u64, pub entry_price: u64, pub leverage: u64, } #[derive(BorshSerialize)] pub struct PositionClosed { pub position_id: u64, pub owner: Address, pub exit_price: u64, pub realized_pnl: i64, pub fee_paid: u64, } #[derive(BorshSerialize)] pub struct PositionLiquidated { pub position_id: u64, pub owner: Address, pub liquidator: Address, pub size: u64, pub collateral_seized: u64, pub liquidation_price: u64, pub insurance_fund_share: u64, pub liquidator_reward: u64, } #[derive(BorshSerialize)] pub struct FundingPaid { pub market_id: u32, pub funding_rate_bps: i64, pub total_long_payment: i64, pub total_short_payment: i64, pub timestamp: u64, } #[derive(BorshSerialize)] pub struct CollateralModified { pub position_id: u64, pub old_collateral: u64, pub new_collateral: u64, pub new_leverage: u64, } // ============================================================================= // STORAGE HELPERS // ============================================================================= fn get_owner() -> Option
{ storage::get::
(keys::OWNER) } fn is_owner(addr: &Address) -> bool { get_owner().map(|o| o == *addr).unwrap_or(false) } fn is_initialized() -> bool { storage::get::(keys::INITIALIZED).unwrap_or(false) } fn is_paused() -> bool { storage::get::(keys::PAUSED).unwrap_or(false) } fn get_market_count() -> u32 { storage::get::(keys::MARKET_COUNT).unwrap_or(0) } fn set_market_count(count: u32) { storage::set(keys::MARKET_COUNT, &count); } fn get_market(id: u32) -> Option { storage::get_with_suffix::(keys::MARKETS, &id.to_le_bytes()) } fn set_market(id: u32, market: &Market) { storage::set_with_suffix(keys::MARKETS, &id.to_le_bytes(), market); } fn get_position_count() -> u64 { storage::get::(keys::POSITION_COUNT).unwrap_or(0) } fn set_position_count(count: u64) { storage::set(keys::POSITION_COUNT, &count); } fn get_position(id: u64) -> Option { storage::get_with_suffix::(keys::POSITIONS, &id.to_le_bytes()) } fn set_position(id: u64, position: &Position) { storage::set_with_suffix(keys::POSITIONS, &id.to_le_bytes(), position); } fn get_price(market_id: u32) -> Option { storage::get_with_suffix::(keys::PRICES, &market_id.to_le_bytes()) } fn set_price(market_id: u32, price: &PriceData) { storage::set_with_suffix(keys::PRICES, &market_id.to_le_bytes(), price); } fn get_funding(market_id: u32) -> Option { storage::get_with_suffix::(keys::CUMULATIVE_FUNDING, &market_id.to_le_bytes()) } fn set_funding(market_id: u32, funding: &FundingData) { storage::set_with_suffix(keys::CUMULATIVE_FUNDING, &market_id.to_le_bytes(), funding); } fn get_insurance_fund() -> u64 { storage::get::(keys::INSURANCE_FUND).unwrap_or(0) } fn set_insurance_fund(amount: u64) { storage::set(keys::INSURANCE_FUND, &amount); } fn get_oracle() -> Option
{ storage::get::
(keys::ORACLE) } // ============================================================================= // CALCULATION HELPERS // ============================================================================= /// Calculate unrealized PnL for a position fn calculate_pnl(position: &Position, mark_price: u64) -> i64 { let size_value = (position.size as u128 * mark_price as u128) / SIZE_PRECISION as u128; let entry_value = (position.size as u128 * position.entry_price as u128) / SIZE_PRECISION as u128; match position.direction { Direction::Long => (size_value as i64) - (entry_value as i64), Direction::Short => (entry_value as i64) - (size_value as i64), } } /// Calculate margin ratio (collateral + pnl) / position_value fn calculate_margin_ratio(position: &Position, mark_price: u64) -> u64 { let pnl = calculate_pnl(position, mark_price); let effective_collateral = if pnl >= 0 { position.collateral + (pnl as u64) } else { position.collateral.saturating_sub((-pnl) as u64) }; let position_value = (position.size as u128 * mark_price as u128) / SIZE_PRECISION as u128; if position_value == 0 { return BPS_DENOMINATOR; } ((effective_collateral as u128 * BPS_DENOMINATOR as u128) / position_value) as u64 } /// Check if position should be liquidated fn is_liquidatable(position: &Position, mark_price: u64) -> bool { let margin_ratio = calculate_margin_ratio(position, mark_price); margin_ratio < MAINTENANCE_MARGIN_BPS } /// Calculate liquidation price fn calculate_liquidation_price(position: &Position) -> u64 { // For long: liq_price = entry_price * (1 - maintenance_margin / leverage) // For short: liq_price = entry_price * (1 + maintenance_margin / leverage) let margin_factor = (MAINTENANCE_MARGIN_BPS as u128 * PRICE_PRECISION as u128) / (position.leverage as u128 * 100); match position.direction { Direction::Long => { position.entry_price.saturating_sub(margin_factor as u64) } Direction::Short => { position.entry_price + margin_factor as u64 } } } /// Calculate funding payment for a position fn calculate_funding_payment(position: &Position, current_funding: &FundingData) -> i64 { let funding_delta = match position.direction { Direction::Long => current_funding.cumulative_long - position.entry_funding, Direction::Short => current_funding.cumulative_short - position.entry_funding, }; // funding_payment = position_size * funding_delta / SIZE_PRECISION ((position.size as i128 * funding_delta as i128) / SIZE_PRECISION as i128) as i64 } /// Calculate new funding rate based on mark vs index price fn calculate_funding_rate(mark_price: u64, index_price: u64) -> i64 { if index_price == 0 { return 0; } // funding_rate = (mark_price - index_price) / index_price // Scaled to bps let price_diff = mark_price as i64 - index_price as i64; let rate = (price_diff as i128 * BPS_DENOMINATOR as i128) / index_price as i128; // Clamp to max funding rate rate.max(-(MAX_FUNDING_RATE_BPS as i128)).min(MAX_FUNDING_RATE_BPS as i128) as i64 } /// Calculate trading fee fn calculate_fee(size: u64, price: u64, fee_bps: u64) -> u64 { let notional = (size as u128 * price as u128) / SIZE_PRECISION as u128; ((notional * fee_bps as u128) / BPS_DENOMINATOR as u128) as u64 } // ============================================================================= // ENTRY POINTS // ============================================================================= synor_sdk::entry_point!(init, call); fn init(params: &[u8]) -> Result<()> { require!(!is_initialized(), Error::invalid_args("Already initialized")); #[derive(BorshDeserialize)] struct InitParams { oracle: Address, } let params = InitParams::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid init params"))?; storage::set(keys::OWNER, &caller()); storage::set(keys::INITIALIZED, &true); storage::set(keys::PAUSED, &false); storage::set(keys::ORACLE, ¶ms.oracle); set_market_count(0); set_position_count(0); set_insurance_fund(0); Ok(()) } fn call(selector: &[u8], params: &[u8]) -> Result> { // Method selectors let create_market_sel = synor_sdk::method_selector("create_market"); let open_position_sel = synor_sdk::method_selector("open_position"); let close_position_sel = synor_sdk::method_selector("close_position"); let add_collateral_sel = synor_sdk::method_selector("add_collateral"); let remove_collateral_sel = synor_sdk::method_selector("remove_collateral"); let liquidate_sel = synor_sdk::method_selector("liquidate"); let update_price_sel = synor_sdk::method_selector("update_price"); let settle_funding_sel = synor_sdk::method_selector("settle_funding"); let get_position_sel = synor_sdk::method_selector("get_position"); let get_market_sel = synor_sdk::method_selector("get_market"); let get_price_sel = synor_sdk::method_selector("get_price"); let get_pnl_sel = synor_sdk::method_selector("get_pnl"); let pause_sel = synor_sdk::method_selector("pause"); let unpause_sel = synor_sdk::method_selector("unpause"); match selector { // ===== Admin Methods ===== s if s == create_market_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); #[derive(BorshDeserialize)] struct Args { base_asset: String, quote_asset: String, max_leverage: u64, taker_fee_bps: u64, maker_fee_bps: i64, min_size: u64, max_size: u64, max_open_interest: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid create_market params"))?; require!(args.max_leverage >= MIN_LEVERAGE && args.max_leverage <= MAX_LEVERAGE, Error::invalid_args("Leverage must be 2-100x")); let market_id = get_market_count(); let market = Market { id: market_id, base_asset: args.base_asset.clone(), quote_asset: args.quote_asset.clone(), status: MarketStatus::Active, max_leverage: args.max_leverage, maintenance_margin_bps: MAINTENANCE_MARGIN_BPS, taker_fee_bps: args.taker_fee_bps, maker_fee_bps: args.maker_fee_bps, min_size: args.min_size, max_size: args.max_size, open_interest_long: 0, open_interest_short: 0, max_open_interest: args.max_open_interest, }; set_market(market_id, &market); set_market_count(market_id + 1); // Initialize funding data let funding = FundingData { rate_bps: 0, cumulative_long: 0, cumulative_short: 0, last_update: timestamp(), }; set_funding(market_id, &funding); emit_raw( &[event_topic(b"MarketCreated")], &borsh::to_vec(&MarketCreated { market_id, base_asset: args.base_asset, quote_asset: args.quote_asset, max_leverage: args.max_leverage, }).unwrap(), ); Ok(borsh::to_vec(&market_id).unwrap()) } s if s == pause_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); storage::set(keys::PAUSED, &true); Ok(borsh::to_vec(&true).unwrap()) } s if s == unpause_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); storage::set(keys::PAUSED, &false); Ok(borsh::to_vec(&true).unwrap()) } // ===== Oracle Methods ===== s if s == update_price_sel => { // Only oracle can update prices let oracle = get_oracle().ok_or(Error::Unauthorized)?; require_auth!(oracle); #[derive(BorshDeserialize)] struct Args { market_id: u32, index_price: u64, confidence: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid update_price params"))?; let _market = get_market(args.market_id) .ok_or_else(|| Error::invalid_args("Market not found"))?; // Get previous mark price for EMA calculation let prev_price = get_price(args.market_id); let prev_mark = prev_price.map(|p| p.mark_price).unwrap_or(args.index_price); // Mark price is EMA of index price (smooths out manipulation) // mark_price = 0.9 * prev_mark + 0.1 * index_price let mark_price = (prev_mark * 9 + args.index_price) / 10; let price_data = PriceData { index_price: args.index_price, mark_price, timestamp: timestamp(), confidence: args.confidence, }; set_price(args.market_id, &price_data); Ok(borsh::to_vec(&price_data).unwrap()) } // ===== Trading Methods ===== s if s == open_position_sel => { require!(!is_paused(), Error::invalid_args("Contract paused")); #[derive(BorshDeserialize)] struct Args { market_id: u32, direction: Direction, size: u64, collateral: u64, leverage: u64, margin_mode: MarginMode, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid open_position params"))?; let mut market = get_market(args.market_id) .ok_or_else(|| Error::invalid_args("Market not found"))?; require!(market.status == MarketStatus::Active, Error::invalid_args("Market not active")); let price = get_price(args.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; // Validate leverage require!(args.leverage >= MIN_LEVERAGE && args.leverage <= market.max_leverage, Error::invalid_args("Invalid leverage")); // Validate size require!(args.size >= market.min_size && args.size <= market.max_size, Error::invalid_args("Invalid position size")); // Validate collateral matches leverage requirement let required_collateral = (args.size as u128 * price.mark_price as u128) / (SIZE_PRECISION as u128 * args.leverage as u128); require!(args.collateral >= required_collateral as u64, Error::invalid_args("Insufficient collateral for leverage")); // Check open interest limits let new_oi = match args.direction { Direction::Long => market.open_interest_long + args.size, Direction::Short => market.open_interest_short + args.size, }; require!(new_oi <= market.max_open_interest, Error::invalid_args("Exceeds max open interest")); // Get current funding let funding = get_funding(args.market_id).unwrap_or(FundingData { rate_bps: 0, cumulative_long: 0, cumulative_short: 0, last_update: timestamp(), }); let entry_funding = match args.direction { Direction::Long => funding.cumulative_long, Direction::Short => funding.cumulative_short, }; // Calculate and deduct fee let fee = calculate_fee(args.size, price.mark_price, market.taker_fee_bps); let net_collateral = args.collateral.saturating_sub(fee); // Create position let position_id = get_position_count(); let position = Position { id: position_id, owner: caller(), market_id: args.market_id, direction: args.direction, size: args.size, collateral: net_collateral, entry_price: price.mark_price, leverage: args.leverage, margin_mode: args.margin_mode, entry_funding, opened_at: timestamp(), updated_at: timestamp(), is_open: true, }; // Update open interest match args.direction { Direction::Long => market.open_interest_long += args.size, Direction::Short => market.open_interest_short += args.size, } set_position(position_id, &position); set_position_count(position_id + 1); set_market(args.market_id, &market); emit_raw( &[event_topic(b"PositionOpened")], &borsh::to_vec(&PositionOpened { position_id, owner: caller(), market_id: args.market_id, direction: args.direction, size: args.size, collateral: net_collateral, entry_price: price.mark_price, leverage: args.leverage, }).unwrap(), ); Ok(borsh::to_vec(&position_id).unwrap()) } s if s == close_position_sel => { #[derive(BorshDeserialize)] struct Args { position_id: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid close_position params"))?; let mut position = get_position(args.position_id) .ok_or_else(|| Error::invalid_args("Position not found"))?; require!(position.is_open, Error::invalid_args("Position already closed")); require!(position.owner == caller(), Error::Unauthorized); let mut market = get_market(position.market_id) .ok_or_else(|| Error::invalid_args("Market not found"))?; let price = get_price(position.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; // Calculate PnL let pnl = calculate_pnl(&position, price.mark_price); // Calculate funding owed let funding = get_funding(position.market_id).unwrap_or(FundingData { rate_bps: 0, cumulative_long: 0, cumulative_short: 0, last_update: timestamp(), }); let funding_payment = calculate_funding_payment(&position, &funding); // Calculate fee let fee = calculate_fee(position.size, price.mark_price, market.taker_fee_bps); // Calculate final payout let total_pnl = pnl - funding_payment - fee as i64; let payout = if total_pnl >= 0 { position.collateral + total_pnl as u64 } else { position.collateral.saturating_sub((-total_pnl) as u64) }; // Update open interest match position.direction { Direction::Long => market.open_interest_long = market.open_interest_long.saturating_sub(position.size), Direction::Short => market.open_interest_short = market.open_interest_short.saturating_sub(position.size), } // Close position position.is_open = false; position.updated_at = timestamp(); set_position(args.position_id, &position); set_market(position.market_id, &market); emit_raw( &[event_topic(b"PositionClosed")], &borsh::to_vec(&PositionClosed { position_id: args.position_id, owner: position.owner, exit_price: price.mark_price, realized_pnl: total_pnl, fee_paid: fee, }).unwrap(), ); Ok(borsh::to_vec(&payout).unwrap()) } s if s == add_collateral_sel => { #[derive(BorshDeserialize)] struct Args { position_id: u64, amount: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid add_collateral params"))?; let mut position = get_position(args.position_id) .ok_or_else(|| Error::invalid_args("Position not found"))?; require!(position.is_open, Error::invalid_args("Position closed")); require!(position.owner == caller(), Error::Unauthorized); let old_collateral = position.collateral; position.collateral += args.amount; position.updated_at = timestamp(); // Recalculate effective leverage let price = get_price(position.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; let notional = (position.size as u128 * price.mark_price as u128) / SIZE_PRECISION as u128; let new_leverage = if position.collateral > 0 { (notional / position.collateral as u128) as u64 } else { MAX_LEVERAGE }; position.leverage = new_leverage.max(MIN_LEVERAGE); set_position(args.position_id, &position); emit_raw( &[event_topic(b"CollateralModified")], &borsh::to_vec(&CollateralModified { position_id: args.position_id, old_collateral, new_collateral: position.collateral, new_leverage: position.leverage, }).unwrap(), ); Ok(borsh::to_vec(&position.collateral).unwrap()) } s if s == remove_collateral_sel => { #[derive(BorshDeserialize)] struct Args { position_id: u64, amount: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid remove_collateral params"))?; let mut position = get_position(args.position_id) .ok_or_else(|| Error::invalid_args("Position not found"))?; require!(position.is_open, Error::invalid_args("Position closed")); require!(position.owner == caller(), Error::Unauthorized); let market = get_market(position.market_id) .ok_or_else(|| Error::invalid_args("Market not found"))?; let price = get_price(position.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; let old_collateral = position.collateral; let new_collateral = position.collateral.saturating_sub(args.amount); // Check that new collateral maintains margin requirement let notional = (position.size as u128 * price.mark_price as u128) / SIZE_PRECISION as u128; let new_leverage = if new_collateral > 0 { (notional / new_collateral as u128) as u64 } else { MAX_LEVERAGE + 1 }; require!(new_leverage <= market.max_leverage, Error::invalid_args("Would exceed max leverage")); // Check margin ratio after removal position.collateral = new_collateral; require!(!is_liquidatable(&position, price.mark_price), Error::invalid_args("Would become liquidatable")); position.leverage = new_leverage; position.updated_at = timestamp(); set_position(args.position_id, &position); emit_raw( &[event_topic(b"CollateralModified")], &borsh::to_vec(&CollateralModified { position_id: args.position_id, old_collateral, new_collateral, new_leverage, }).unwrap(), ); Ok(borsh::to_vec(&args.amount).unwrap()) } s if s == liquidate_sel => { #[derive(BorshDeserialize)] struct Args { position_id: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid liquidate params"))?; let mut position = get_position(args.position_id) .ok_or_else(|| Error::invalid_args("Position not found"))?; require!(position.is_open, Error::invalid_args("Position closed")); let mut market = get_market(position.market_id) .ok_or_else(|| Error::invalid_args("Market not found"))?; let price = get_price(position.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; // Verify position is liquidatable require!(is_liquidatable(&position, price.mark_price), Error::invalid_args("Position not liquidatable")); // Calculate liquidation amounts let collateral_seized = position.collateral; let liquidation_fee = (collateral_seized as u128 * LIQUIDATION_FEE_BPS as u128 / BPS_DENOMINATOR as u128) as u64; let insurance_share = (liquidation_fee as u128 * INSURANCE_SHARE_BPS as u128 / BPS_DENOMINATOR as u128) as u64; let liquidator_reward = liquidation_fee - insurance_share; // Update insurance fund let insurance = get_insurance_fund(); set_insurance_fund(insurance + insurance_share); // Update open interest match position.direction { Direction::Long => market.open_interest_long = market.open_interest_long.saturating_sub(position.size), Direction::Short => market.open_interest_short = market.open_interest_short.saturating_sub(position.size), } // Close position position.is_open = false; position.collateral = 0; position.updated_at = timestamp(); set_position(args.position_id, &position); set_market(position.market_id, &market); emit_raw( &[event_topic(b"PositionLiquidated")], &borsh::to_vec(&PositionLiquidated { position_id: args.position_id, owner: position.owner, liquidator: caller(), size: position.size, collateral_seized, liquidation_price: price.mark_price, insurance_fund_share: insurance_share, liquidator_reward, }).unwrap(), ); Ok(borsh::to_vec(&liquidator_reward).unwrap()) } s if s == settle_funding_sel => { #[derive(BorshDeserialize)] struct Args { market_id: u32, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid settle_funding params"))?; let market = get_market(args.market_id) .ok_or_else(|| Error::invalid_args("Market not found"))?; let price = get_price(args.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; let mut funding = get_funding(args.market_id).unwrap_or(FundingData { rate_bps: 0, cumulative_long: 0, cumulative_short: 0, last_update: timestamp(), }); let now = timestamp(); let time_elapsed = now - funding.last_update; // Only settle if enough time has passed require!(time_elapsed >= FUNDING_INTERVAL, Error::invalid_args("Funding interval not elapsed")); // Calculate new funding rate let new_rate = calculate_funding_rate(price.mark_price, price.index_price); // Calculate funding payments // Positive rate = longs pay shorts // Negative rate = shorts pay longs let long_payment = (market.open_interest_long as i128 * new_rate as i128 / BPS_DENOMINATOR as i128) as i64; let short_payment = -long_payment; // Update cumulative funding funding.rate_bps = new_rate; funding.cumulative_long += long_payment; funding.cumulative_short += short_payment; funding.last_update = now; set_funding(args.market_id, &funding); emit_raw( &[event_topic(b"FundingPaid")], &borsh::to_vec(&FundingPaid { market_id: args.market_id, funding_rate_bps: new_rate, total_long_payment: long_payment, total_short_payment: short_payment, timestamp: now, }).unwrap(), ); Ok(borsh::to_vec(&new_rate).unwrap()) } // ===== Query Methods ===== s if s == get_position_sel => { #[derive(BorshDeserialize)] struct Args { position_id: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (position_id: u64)"))?; let position = get_position(args.position_id); Ok(borsh::to_vec(&position).unwrap()) } s if s == get_market_sel => { #[derive(BorshDeserialize)] struct Args { market_id: u32, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (market_id: u32)"))?; let market = get_market(args.market_id); Ok(borsh::to_vec(&market).unwrap()) } s if s == get_price_sel => { #[derive(BorshDeserialize)] struct Args { market_id: u32, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (market_id: u32)"))?; let price = get_price(args.market_id); Ok(borsh::to_vec(&price).unwrap()) } s if s == get_pnl_sel => { #[derive(BorshDeserialize)] struct Args { position_id: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (position_id: u64)"))?; let position = get_position(args.position_id) .ok_or_else(|| Error::invalid_args("Position not found"))?; let price = get_price(position.market_id) .ok_or_else(|| Error::invalid_args("Price not available"))?; let pnl = calculate_pnl(&position, price.mark_price); let liq_price = calculate_liquidation_price(&position); let margin_ratio = calculate_margin_ratio(&position, price.mark_price); #[derive(BorshSerialize)] struct PnlResult { unrealized_pnl: i64, liquidation_price: u64, margin_ratio_bps: u64, is_liquidatable: bool, } let result = PnlResult { unrealized_pnl: pnl, liquidation_price: liq_price, margin_ratio_bps: margin_ratio, is_liquidatable: is_liquidatable(&position, price.mark_price), }; Ok(borsh::to_vec(&result).unwrap()) } _ => Err(Error::InvalidMethod), } }