Closed alnoki closed 2 years ago
OrderBook
does not need to include custodian_id
as no collateral transfer happens book-side
In the case of asset-agnostic markets, the above implementation still requires that calling functions submit unique type arguments for each asset-agnostic market. But this approach requires integrators to declare unique types for each asset-agnostic market they wish to trade on, which is unfeasible. Instead, generic agnostic type flags are proposed as follows, for the following registry.move
data structures:
/// When both base and quote assets are coins
const PURE_COIN_PAIR: u64 = 0;
/// Type flag for generic asset
struct GenericAsset{}
/// Unique identifier for a market
struct MarketInfo has copy, drop, store {
/// Account hosting corresponding `OrderBook`
host: address,
/// Trading pair parameters
trading_pair_info: TradingPairInfo
/// Serial ID for the corresponding market
market_id: u64
}
/// Container for core registration information
struct Registry has key {
/// Map from trading pair to order book host address
hosts: table::Table<TradingPairInfo, address>,
/// List of all available markets
markets: vector<MarketInfo>,
/// Number of registered custodians
n_custodians: u64,
/// Number of registered markets
n_markets: u64,
}
/// Information about a trading pair
struct TradingPairInfo has copy, drop, store {
/// Base asset type info. When trading an
/// `aptos_framework::coin::Coin`, corresponds to the phantom
/// `CoinType`, for instance `MyCoin` rather than
/// `Coin<MyCoin>`. Corresponds to `GenericAsset` otherwise.
base_type_info: type_info::TypeInfo,
/// Quote asset type info. When trading an
/// `aptos_framework::coin::Coin`, corresponds to the phantom
/// `CoinType`, for instance `MyCoin` rather than
/// `Coin<MyCoin>`. Corresponds to `GenericAsset` otheriwse.
quote_type_info: type_info::TypeInfo,
/// Number of base units exchanged per lot
lot_size: u64,
/// Number of quote units exchanged per lot
tick_size: u64,
/// ID of custodian capability required to withdraw/deposit
/// collateral for an asset that is not a coin. A "market-wide"
/// collateral transfer custodian ID, required to verify deposit
/// and withdraw amounts for asset-agnostic markets. Marked as
/// `PURE_COIN_PAIR` when base and quote types are both coins.
custodian_id: u64,
/// Serial ID for a given base/quote combination in the case of
/// a generic asset on either side. Allows third-parties to
/// register multiple asset-agnostic order books. Marked as
/// `PURE_COIN_PAIR` when base and quote types are both coins.
agnostic_id: u64
}
Here, a user's MarketAccount
need only store the corresponding market_id
:
/// Unique ID describing a market and a user-specific custodian
struct MarketAccountInfo has copy, drop, store {
/// Serial ID of the market that a user is trading on
market_id: u64,
/// Serial ID of registered account custodian, set to 0 when
/// given account does not have an authorized custodian
user_level_custodian_id: u64
/// ID of custodian capability required to withdraw/deposit
/// collateral for an asset that is not a coin. A "market-wide"
/// collateral transfer custodian ID, required to verify deposit
/// and withdraw amounts for asset-agnostic markets. Marked as
/// `PURE_COIN_PAIR` when base and quote types are both coins.
market_level_custodian_id: u64
}
/// Market account map for all of a user's `MarketAccount`s
struct MarketAccounts has key {
/// Map from `MarketAccountInfo` to `MarketAccount`. Separated
/// into different table entries to reduce transaction
/// collisions across markets
map: open_table::OpenTable<MarketAccountInfo, MarketAccount>
}
/// Represents a user's open orders and available assets for a given
///`MarketAccountInfo`
struct MarketAccount has store {
/// Base asset type info. When trading an
/// `aptos_framework::coin::Coin`, corresponds to the phantom
/// `CoinType`, for instance `MyCoin` rather than
/// `Coin<MyCoin>`. Corresponds to `GenericAsset` otherwise.
base_type_info: type_info::TypeInfo,
/// Quote asset type info. When trading an
/// `aptos_framework::coin::Coin`, corresponds to the phantom
/// `CoinType`, for instance `MyCoin` rather than
/// `Coin<MyCoin>`. Corresponds to `GenericAsset` otheriwse.
quote_type_info: type_info::TypeInfo,
/// Map from order ID to size of outstanding order, measured in
/// lots lefts to fill
asks: CritBitTree<u64>,
/// Map from order ID to size of outstanding order, measured in
/// lots lefts to fill
bids: CritBitTree<u64>,
/// Total base asset units held as collateral
base_total: u64,
/// Base asset units available for withdraw
base_available: u64,
/// Total quote asset units held as collateral
quote_total: u64,
/// Quote asset units available for withdraw
quote_available: u64
}
Base types, quote types, and market-level custodian IDs are included as a parallelism optimization - such that user-side operations do not conflict with market-wide registration operations.
On the book side, type arguments can then be eliminated per a map approach, whereby a host stores multiple order books, indexed by market ID:
/// An order book for the given market
struct OrderBook has store {
/// Base asset type info. When trading an
/// `aptos_framework::coin::Coin`, corresponds to the phantom
/// `CoinType`, for instance `MyCoin` rather than
/// `Coin<MyCoin>`. Corresponds to `GenericAsset` otherwise.
base_type_info: type_info::TypeInfo,
/// Quote asset type info. When trading an
/// `aptos_framework::coin::Coin`, corresponds to the phantom
/// `CoinType`, for instance `MyCoin` rather than
/// `Coin<MyCoin>`. Corresponds to `GenericAsset` otheriwse.
quote_type_info: type_info::TypeInfo,
/// Number of base units exchanged per lot
lot_size: u64,
/// Number of quote units exchanged per lot
tick_size: u64,
/// Asks tree
asks: CritBitTree<Order>,
/// Bids tree
bids: CritBitTree<Order>,
/// Order ID of minimum ask, per price-time priority. The ask
/// side "spread maker".
min_ask: u128,
/// Order ID of maximum bid, per price-time priority. The bid
/// side "spread maker".
max_bid: u128,
/// Number of limit orders placed on book
counter: u64
}
/// Order book map for all of a host's `OrderBook`s
struct OrderBooks has key {
/// Map from market ID to the corresponding order book
map: open_table::OpenTable<u64, OrderBook>
}
Here, lot size and tick side are included as lookup optimizations for internal integer arithmetic, and base/quote types are included to reduce queries on the registry when filling swaps directly against the book
Motivations
Presently, Econia supports spot trading for assets of type
aptos_framework::coin::Coin
, but this approach is insufficient for certain applications, for example perpetual futures and other contract exchanges. In the interest of abstraction and modularity, an asset-agnostic approach is proposed, whereby Econia's bookkeeping functionality is only optionally combined with collateral management.Data structures
Here, a custodian capability is required for collateral withdraw/deposit on an asset agnostic order book (one having either
base_is_coin
orquote_is_coin
set tofalse
), because the corresponding custodian is required to validate that withdraws and deposits are accounted for as the correct size. This is in contrast to an order book where both assets areCoin
types, because the underlyingCoin.value
field can be used to verify withdraw and deposit sizes.When either
base_is_coin
orquote_is_coin
istrue
, then a corresponding collateral container (Collateral<CoinType>
) will be set up and managed per the current implementation. Otherwise collateral will be ignored for the givenMarketAccount
Considerations
Market registration will require verifying that
base_is_coin
andquote_is_coin
are accounted for correctly. In the case of an agnostic order book, thecustodian_id
field of anOrder
will become irrelevant - this could potentially be used as a general level metadata field. Pending future suggestions, asset-agnostic order books trades can be placed by users, but collateral management will only be available to the designated custodian for the given order book, who can validate deposit and withdraw amounts. Pending the implementation of a fee model, it may be necessary to enforce that thequote_is_coin
is always true.