//! v2 order payload: an order may be sized as a fraction of another order's //! already-produced output, and may opt in to recording its own output so a //! later order can draw against it. use alloc::{format, string::String}; use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; use frame_support::{BoundedVec, traits::ConstU32}; use scale_info::TypeInfo; use sp_core::{H256, hexdisplay::HexDisplay}; use sp_runtime::Perbill; use subtensor_macros::freeze_struct; use subtensor_runtime_common::NetUid; use crate::{Order, OrderType}; /// What a provider produced — and therefore what a linked order may spend. #[derive( Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen, Clone, PartialEq, Eq, Debug, )] pub enum LinkedAsset { /// Free TAO, produced by a sell. Tao, /// Alpha staked to `hotkey` on `netuid`, produced by a buy. Alpha { netuid: NetUid, hotkey: AccountId }, } /// How much an order trades. Part of the signed payload. #[derive( Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen, Clone, Copy, PartialEq, Eq, Debug, )] pub enum OrderAmount { /// Absolute raw amount: TAO for buys, alpha for sells. v1 semantics. Fixed(u64), /// `pct` of the output recorded for `provider`. Drawing removes the record. LinkedPercentage { provider: H256, pct: Perbill }, } impl OrderAmount { pub fn is_linked(&self) -> bool { matches!(self, OrderAmount::LinkedPercentage { .. }) } pub fn fixed(&self) -> Option { match self { OrderAmount::Fixed(amount) => Some(*amount), OrderAmount::LinkedPercentage { .. } => None, } } pub fn linked(&self) -> Option<(H256, Perbill)> { match self { OrderAmount::LinkedPercentage { provider, pct } => Some((*provider, *pct)), OrderAmount::Fixed(_) => None, } } /// Clear-signing rendering. `Fixed(n)` is bare digits (v1-identical); /// linked amounts always carry the ` ppb of order 0x… output` suffix. pub fn render(&self) -> String { match self { OrderAmount::Fixed(amount) => format!("{amount}"), OrderAmount::LinkedPercentage { provider, pct } => format!( "{} ppb of order 0x{} output", pct.deconstruct(), HexDisplay::from(&provider.0), ), } } } /// v2 signed payload. Field-for-field v1 plus [`OrderAmount`] and `has_linked_order`. #[allow(clippy::multiple_bound_locations)] #[freeze_struct("71aea78239e92aa7")] #[derive( Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen, Clone, PartialEq, Eq, Debug, )] pub struct OrderV2 { pub signer: AccountId, pub hotkey: AccountId, pub netuid: NetUid, pub order_type: OrderType, pub amount: OrderAmount, pub limit_price: u64, pub expiry: u64, pub fee_rate: Perbill, pub fee_recipient: AccountId, pub relayer: Option>>, pub max_slippage: Option, pub chain_id: u64, pub partial_fills_enabled: bool, /// When true, this order's post-fee output is recorded so a later linked /// order can draw against it. Mutually exclusive with partial fills. pub has_linked_order: bool, } /// Single-use record of a provider's post-fee output. #[allow(clippy::multiple_bound_locations)] #[freeze_struct("8e774441a7370a3f")] #[derive( Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen, Clone, PartialEq, Eq, Debug, )] pub struct LinkedOutput { pub signer: AccountId, pub asset: LinkedAsset, pub total: u64, pub expires_at: u64, } /// Version-agnostic projection used by every validation/execution path. #[derive(Clone, PartialEq, Eq, Debug)] pub struct OrderView { pub signer: AccountId, pub hotkey: AccountId, pub netuid: NetUid, pub order_type: OrderType, pub amount: OrderAmount, pub limit_price: u64, pub expiry: u64, pub fee_rate: Perbill, pub fee_recipient: AccountId, pub relayer: Option>>, pub max_slippage: Option, pub chain_id: u64, pub partial_fills_enabled: bool, pub has_linked_order: bool, } impl OrderView { pub fn from_v1(order: &Order) -> Self { Self { signer: order.signer.clone(), hotkey: order.hotkey.clone(), netuid: order.netuid, order_type: order.order_type.clone(), amount: OrderAmount::Fixed(order.amount), limit_price: order.limit_price, expiry: order.expiry, fee_rate: order.fee_rate, fee_recipient: order.fee_recipient.clone(), relayer: order.relayer.clone(), max_slippage: order.max_slippage, chain_id: order.chain_id, partial_fills_enabled: order.partial_fills_enabled, has_linked_order: false, } } pub fn from_v2(order: &OrderV2) -> Self { Self { signer: order.signer.clone(), hotkey: order.hotkey.clone(), netuid: order.netuid, order_type: order.order_type.clone(), amount: order.amount, limit_price: order.limit_price, expiry: order.expiry, fee_rate: order.fee_rate, fee_recipient: order.fee_recipient.clone(), relayer: order.relayer.clone(), max_slippage: order.max_slippage, chain_id: order.chain_id, partial_fills_enabled: order.partial_fills_enabled, has_linked_order: order.has_linked_order, } } pub fn input_asset(&self) -> LinkedAsset { if self.order_type.is_buy() { LinkedAsset::Tao } else { LinkedAsset::Alpha { netuid: self.netuid, hotkey: self.hotkey.clone(), } } } pub fn output_asset(&self) -> LinkedAsset { if self.order_type.is_buy() { LinkedAsset::Alpha { netuid: self.netuid, hotkey: self.hotkey.clone(), } } else { LinkedAsset::Tao } } }