Skip to main content

Storage & Events

Storage Layout

All storage keys are defined in TradingStorageKey. Storage is split into three TTL tiers.

Instance Storage (30-day TTL)

Global state that is accessed frequently and shared across all calls.

KeyTypeDescription
Statusu32Contract status enum value
VaultAddressVault contract address
TokenAddressCollateral token address
PriceVerifierAddressPyth Lazer verifier address
TreasuryAddressProtocol fee recipient
ConfigTradingConfigGlobal trading parameters
TotalNotionali128Sum of all position notionals across all markets
PositionCounteru32Monotonically incrementing position ID allocator
LastFundingUpdateu64Timestamp of last apply_funding call

Persistent Storage: Market Tier (45/52-day TTL)

Per-market data and the global market list.

KeyTypeDescription
MarketsVec<u32>List of registered market IDs (max MAX_ENTRIES)
MarketConfig(u32)MarketConfigPer-market parameters
MarketData(u32)MarketDataPer-market mutable state

Persistent Storage: Position Tier (14/21-day TTL)

Per-position and per-user data. Shorter TTL because perp positions are short-lived (most close within days).

KeyTypeDescription
Position(u32)PositionIndividual position data
UserPositions(Address)Vec<u32>Position IDs owned by an address

The PositionCounter is never decremented. Position IDs are permanent. Closing a position does not free its ID for reuse. This simplifies event indexing and prevents ID collisions.

TTL Strategy

TierThresholdBumpRationale
Instance30 days31 daysAccessed on every call; minimal expiry risk
Market Persistent45 days52 daysMarket config/data and market list; moderate access frequency
Position Persistent14 days21 daysPositions and user position lists; short-lived data

All TTLs are bumped on read or write. If a user does not interact for 14+ days, their UserPositions entry and Position records could expire. Positions are short-lived (most close within days), so the shorter TTL avoids paying rent for abandoned positions.

Events

All events use Soroban's #[contractevent] derive macro. Fields marked with #[topic] are indexed for efficient filtering.

Admin Events

EventTopicsData
SetConfigNone(no data)
SetMarketmarket_idNone
SetStatusNonestatus: u32

Position Events

EventTopicsData
PlaceLimitmarket_id, user, position_id(no data)
OpenMarketmarket_id, user, position_idbase_fee, impact_fee
FillLimitmarket_id, user, position_idbase_fee, impact_fee
ClosePositionmarket_id, user, position_idprice, pnl, base_fee, impact_fee, funding, borrowing_fee
TakeProfitmarket_id, user, position_idprice, pnl, base_fee, impact_fee, funding, borrowing_fee
StopLossmarket_id, user, position_idprice, pnl, base_fee, impact_fee, funding, borrowing_fee
Liquidationmarket_id, user, position_idprice, base_fee, impact_fee, funding, borrowing_fee, liq_fee
RefundPositionmarket_id, user, position_idamount
ModifyCollateralmarket_id, user, position_idamount (positive = deposit, negative = withdraw)
SetTriggersmarket_id, user, position_idtake_profit, stop_loss

Market Events

EventTopicsData
DelMarketmarket_id(no data)

System Events

EventTopicsData
ApplyFundingNone(no data)
ADLTriggeredNonereduction_pct, deficit

Close events include borrowing_fee as a separate field alongside base_fee, impact_fee, and funding. The emitted pnl is the net PnL (after all fees, clamped to -col).

Error Codes

All errors use panic_with_error!(e, TradingError::Variant). Errors are hard panics that abort the entire transaction, including keeper batch execution via execute (which returns ()).

CodeNameDescription
1UnauthorizedNon-owner tried owner-only action
700InvalidConfigConfig parameter out of valid range
701MarketNotFoundNo market registered for the given market_id
702MarketDisabledMarket is disabled or deleted
703MaxMarketsReachedMAX_ENTRIES markets already registered
710InvalidPricePrice verification failed, market_id mismatch, or missing feed
711StalePricePrice data predates position open time
720PositionNotFoundPosition ID not found in storage
721PositionNotPendingPosition is filled; expected pending
722MaxPositionsReachedUser has MAX_ENTRIES positions
723NegativeValueNotAllowedA parameter is zero or negative
724NotionalBelowMinimumBelow min_notional
725NotionalAboveMaximumAbove max_notional
726LeverageAboveMaximumExceeds 1/margin
727CollateralUnchangedModify to same value
728WithdrawalBreaksMarginWithdrawal would breach initial margin
731NotActionableNo valid action for this position in execute batch
732PositionTooNewMIN_OPEN_TIME not elapsed
733ActionNotAllowedForStatusAction not allowed for position status
740InvalidStatusInvalid or disallowed contract status value
741ContractOnIceNew positions blocked (OnIce, AdminOnIce, or Frozen)
742ContractFrozenAll position management blocked (Frozen)
750ThresholdNotMetNet PnL below ADL threshold
751UtilizationExceededPosition would exceed notional/vault cap
752FundingTooEarlyapply_funding called < 1 hour since last call