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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/waterx_perp/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Position has key, store {
- Borrow fee: continuous, `collateral_amount * rate_delta / 1e9` (u64 sufficient)
- Funding rate: periodic (1h intervals), OI imbalance, Double precision (u256/1e18)
- Protocol fee share: configurable % split to protocol address
- Liquidator fee + insurance fee on liquidation
- Liquidation: insurance fee = configurable bps of liquidation notional; phase 1 keeper fee defaults to 0; remaining collateral returns to WLP

### 10. Funding Rate
- `funding_rate = basic_rate * (long_oi - short_oi) / total_oi`
Expand Down
6 changes: 3 additions & 3 deletions contracts/waterx_perp/sources/global_config.move
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public struct GlobalConfig has key {
protocol_fee_share_bps: u64,
/// Insurance fund recipient.
insurance_address: address,
/// Liquidator reward fee in bps.
/// Optional keeper reward fee in bps of position notional.
liquidator_fee_bps: u64,
/// Insurance fund fee in bps (of remaining collateral after liquidator fee).
/// Insurance fund fee in bps of position notional.
insurance_fee_bps: u64,
/// Maximum orders per price level.
max_orders_per_price: u64,
Expand All @@ -57,7 +57,7 @@ fun init(ctx: &mut TxContext) {
fee_address: ctx.sender(),
protocol_fee_share_bps: 3000, // 30% default
insurance_address: ctx.sender(),
liquidator_fee_bps: 100,
liquidator_fee_bps: 0,
insurance_fee_bps: 100,
max_orders_per_price: 100,
oi_cap_bps: 0, // no cap by default
Expand Down
71 changes: 71 additions & 0 deletions contracts/waterx_perp/sources/market_config.move
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ use waterx_perp::error;
use waterx_perp::math;
use waterx_perp::events;

// === Constants ===

/// Typus-compatible default share of LP TVL allocated to net exposure before impact hits max.
const DEFAULT_ALLOCATED_LP_EXPOSURE_BPS: u64 = 2_000; // 20%
/// Typus-compatible default curvature for the impact fee curve.
const DEFAULT_IMPACT_FEE_CURVATURE: u64 = 2;
/// Typus-compatible default scale divisor applied before curvature.
const DEFAULT_IMPACT_FEE_SCALE: u64 = 1;

// === Structs ===

/// Configuration and state for a single trading symbol.
Expand All @@ -32,6 +41,14 @@ public struct MarketConfig<phantom BASE_TOKEN> has key, store {
lot_size: u64,
/// Base trading fee in bps.
trading_fee_bps: u64,
/// Maximum additional impact fee in bps.
max_impact_fee_bps: u64,
/// Share of LP TVL allocated to net exposure before impact hits max, in bps.
allocated_lp_exposure_bps: u64,
/// Curvature exponent for the impact fee curve.
impact_fee_curvature: u64,
/// Scale divisor applied to the exposure ratio before curvature.
impact_fee_scale: u64,
/// Size decimal (matches base token).
size_decimal: u8,
/// Maintenance margin rate in bps (default 150).
Expand Down Expand Up @@ -94,6 +111,16 @@ public(package) fun new_market_config<BASE_TOKEN>(
): MarketConfig<BASE_TOKEN> {
let now = clock.timestamp_ms();
let last_funding = now / funding_interval_ms * funding_interval_ms;
let max_impact_fee_bps = trading_fee_bps;
let allocated_lp_exposure_bps = DEFAULT_ALLOCATED_LP_EXPOSURE_BPS;
let impact_fee_curvature = DEFAULT_IMPACT_FEE_CURVATURE;
let impact_fee_scale = DEFAULT_IMPACT_FEE_SCALE;
assert_valid_impact_fee_config(
max_impact_fee_bps,
allocated_lp_exposure_bps,
impact_fee_curvature,
impact_fee_scale,
);

MarketConfig {
id: object::new(ctx),
Expand All @@ -102,6 +129,10 @@ public(package) fun new_market_config<BASE_TOKEN>(
min_size,
lot_size,
trading_fee_bps,
max_impact_fee_bps,
allocated_lp_exposure_bps,
impact_fee_curvature,
impact_fee_scale,
size_decimal,
maintenance_margin_bps,
max_long_oi,
Expand Down Expand Up @@ -130,6 +161,10 @@ public fun update_market_config<BASE_TOKEN>(
min_size: Option<u64>,
lot_size: Option<u64>,
trading_fee_bps: Option<u64>,
max_impact_fee_bps: Option<u64>,
allocated_lp_exposure_bps: Option<u64>,
impact_fee_curvature: Option<u64>,
impact_fee_scale: Option<u64>,
maintenance_margin_bps: Option<u64>,
max_long_oi: Option<u64>,
max_short_oi: Option<u64>,
Expand All @@ -141,6 +176,16 @@ public fun update_market_config<BASE_TOKEN>(
if (min_size.is_some()) { market_config.min_size = min_size.destroy_some(); };
if (lot_size.is_some()) { market_config.lot_size = lot_size.destroy_some(); };
if (trading_fee_bps.is_some()) { market_config.trading_fee_bps = trading_fee_bps.destroy_some(); };
if (max_impact_fee_bps.is_some()) { market_config.max_impact_fee_bps = max_impact_fee_bps.destroy_some(); };
if (allocated_lp_exposure_bps.is_some()) { market_config.allocated_lp_exposure_bps = allocated_lp_exposure_bps.destroy_some(); };
if (impact_fee_curvature.is_some()) { market_config.impact_fee_curvature = impact_fee_curvature.destroy_some(); };
if (impact_fee_scale.is_some()) { market_config.impact_fee_scale = impact_fee_scale.destroy_some(); };
assert_valid_impact_fee_config(
market_config.max_impact_fee_bps,
market_config.allocated_lp_exposure_bps,
market_config.impact_fee_curvature,
market_config.impact_fee_scale,
);
if (maintenance_margin_bps.is_some()) { market_config.maintenance_margin_bps = maintenance_margin_bps.destroy_some(); };
if (max_long_oi.is_some()) { market_config.max_long_oi = max_long_oi.destroy_some(); };
if (max_short_oi.is_some()) { market_config.max_short_oi = max_short_oi.destroy_some(); };
Expand Down Expand Up @@ -248,6 +293,18 @@ public fun market_config_lot_size<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u64
public use fun market_config_trading_fee_bps as MarketConfig.trading_fee_bps;
public fun market_config_trading_fee_bps<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u64 { m.trading_fee_bps }

public use fun market_config_max_impact_fee_bps as MarketConfig.max_impact_fee_bps;
public fun market_config_max_impact_fee_bps<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u64 { m.max_impact_fee_bps }

public use fun market_config_allocated_lp_exposure_bps as MarketConfig.allocated_lp_exposure_bps;
public fun market_config_allocated_lp_exposure_bps<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u64 { m.allocated_lp_exposure_bps }

public use fun market_config_impact_fee_curvature as MarketConfig.impact_fee_curvature;
public fun market_config_impact_fee_curvature<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u64 { m.impact_fee_curvature }

public use fun market_config_impact_fee_scale as MarketConfig.impact_fee_scale;
public fun market_config_impact_fee_scale<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u64 { m.impact_fee_scale }

public use fun market_config_size_decimal as MarketConfig.size_decimal;
public fun market_config_size_decimal<BASE_TOKEN>(m: &MarketConfig<BASE_TOKEN>): u8 { m.size_decimal }

Expand Down Expand Up @@ -396,6 +453,20 @@ public fun calculate_funding_rate(
}
}

fun assert_valid_impact_fee_config(
max_impact_fee_bps: u64,
allocated_lp_exposure_bps: u64,
impact_fee_curvature: u64,
impact_fee_scale: u64,
) {
if (
max_impact_fee_bps > math::bp_scale()
|| allocated_lp_exposure_bps > math::bp_scale()
|| impact_fee_curvature == 0
|| impact_fee_scale == 0
) error::err_invalid_config();
}

// === Tests ===

#[test]
Expand Down
3 changes: 1 addition & 2 deletions contracts/waterx_perp/sources/position.move
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ public fun is_liquidatable(
position: &Position,
current_price: Float,
collateral_price: Float,
trading_fee_bps: u64,
closing_fee: Float,
maintenance_margin_bps: u64,
cumulative_borrow_rate: u64,
cumulative_funding_sign: bool,
Expand All @@ -441,7 +441,6 @@ public fun is_liquidatable(
let borrow_fee_delta = calculate_borrow_fee(position, cumulative_borrow_rate);
let total_borrow_fee = position.unrealized_borrow_fee + borrow_fee_delta;
let total_trading_fee = position.unrealized_trading_fee;
let closing_fee = math::fee_from_bps(notional, trading_fee_bps);

let total_fees_usd = math::amount_to_usd(
total_borrow_fee + total_trading_fee, position.collateral_decimal, collateral_price,
Expand Down
Loading
Loading