diff --git a/packages/hyperbet-solana/anchor/programs/gold_perps_market/src/lib.rs b/packages/hyperbet-solana/anchor/programs/gold_perps_market/src/lib.rs index fd79fd39..c6007ee6 100644 --- a/packages/hyperbet-solana/anchor/programs/gold_perps_market/src/lib.rs +++ b/packages/hyperbet-solana/anchor/programs/gold_perps_market/src/lib.rs @@ -7,6 +7,8 @@ use std::cmp; declare_id!("BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS"); const FUNDING_RATE_PRECISION: i128 = 1_000_000_000; +// Funding velocity is expressed in Solana 1e9 precision units; EVM-scale 1e12 literals are invalid here. +const MAX_SOLANA_FUNDING_VELOCITY: u64 = 1_000_000_000; const BPS_DENOMINATOR: u64 = 10_000; const MARKET_STATUS_ACTIVE: u8 = 0; const MARKET_STATUS_CLOSE_ONLY: u8 = 1; @@ -54,6 +56,7 @@ pub mod gold_perps_market { ) -> Result<()> { validate_config_inputs( default_skew_scale, + default_funding_velocity, max_oracle_staleness_seconds, min_oracle_spot_index, max_oracle_spot_index, @@ -147,6 +150,7 @@ pub mod gold_perps_market { ); validate_config_inputs( default_skew_scale, + default_funding_velocity, max_oracle_staleness_seconds, min_oracle_spot_index, max_oracle_spot_index, @@ -905,6 +909,7 @@ fn require_market(market: &MarketState, market_id: u64) -> Result<()> { fn validate_config_inputs( default_skew_scale: u64, + default_funding_velocity: u64, max_oracle_staleness_seconds: i64, min_oracle_spot_index: u64, max_oracle_spot_index: u64, @@ -919,6 +924,10 @@ fn validate_config_inputs( trade_market_maker_fee_bps: u16, ) -> Result<()> { require!(default_skew_scale > 0, PerpsError::InvalidRiskConfig); + require!( + default_funding_velocity > 0 && default_funding_velocity <= MAX_SOLANA_FUNDING_VELOCITY, + PerpsError::InvalidRiskConfig + ); require!( max_oracle_staleness_seconds > 0, PerpsError::InvalidRiskConfig @@ -1748,6 +1757,33 @@ pub enum PerpsError { mod tests { use super::*; + fn validate_default_config_with_funding_velocity(default_funding_velocity: u64) -> Result<()> { + validate_config_inputs( + 1_000_000, + default_funding_velocity, + 60, + 1, + 1_000_000_000, + 1_000, + 10, + 1, + 1_000_000_000, + 1, + 500, + 50, + 10, + 10, + ) + } + + #[test] + fn config_rejects_evm_scale_funding_velocity() { + assert!(validate_default_config_with_funding_velocity(1_000).is_ok()); + assert!(validate_default_config_with_funding_velocity(MAX_SOLANA_FUNDING_VELOCITY).is_ok()); + assert!(validate_default_config_with_funding_velocity(0).is_err()); + assert!(validate_default_config_with_funding_velocity(1_000_000_000_000).is_err()); + } + #[test] fn realized_pnl_on_partial_reduction_matches_direction() { let pnl = calculate_realized_trade_pnl(10_000, 100, 120, -4_000).unwrap(); diff --git a/packages/hyperbet-solana/anchor/tests/gold_perps_market.ts b/packages/hyperbet-solana/anchor/tests/gold_perps_market.ts index ba4a4587..9ee2dcf4 100644 --- a/packages/hyperbet-solana/anchor/tests/gold_perps_market.ts +++ b/packages/hyperbet-solana/anchor/tests/gold_perps_market.ts @@ -190,6 +190,43 @@ describe("gold_perps_market", () => { ); }); + it("rejects EVM-scale funding velocity on Solana config", async () => { + try { + await program.methods + .updateConfig( + authority.publicKey, + authority.publicKey, + authority.publicKey, + toBn(DEFAULT_SKEW_SCALE), + new anchor.BN("1000000000000"), + new anchor.BN(DEFAULT_MAX_ORACLE_STALENESS_SECONDS), + toBn(DEFAULT_MIN_ORACLE_SPOT_INDEX), + toBn(DEFAULT_MAX_ORACLE_SPOT_INDEX), + DEFAULT_MAX_ORACLE_PRICE_DELTA_BPS, + toBn(DEFAULT_MAX_LEVERAGE), + toBn(DEFAULT_MIN_MARGIN), + toBn(DEFAULT_MAX_MARKET_OPEN_INTEREST), + toBn(DEFAULT_MIN_MARKET_INSURANCE), + DEFAULT_MAINTENANCE_MARGIN_BPS, + DEFAULT_LIQUIDATION_FEE_BPS, + DEFAULT_TRADE_TREASURY_FEE_BPS, + DEFAULT_TRADE_MARKET_MAKER_FEE_BPS, + ) + .accountsPartial({ + config: configPda(program.programId), + authority: authority.publicKey, + }) + .signers([authority]) + .rpc(); + assert.fail("EVM-scale funding velocity succeeded"); + } catch (error: unknown) { + assert.ok( + hasProgramError(error, "InvalidRiskConfig"), + `expected InvalidRiskConfig, got ${String(error)}`, + ); + } + }); + it("rejects oracle updates outside configured bounds and oversized jumps", async () => { await program.methods .updateConfig(