econia-labs / econia

Hyper-parallelized on-chain order book for the Aptos blockchain
https://econia.dev
Other
141 stars 53 forks source link

Add event emissions #23

Closed alnoki closed 1 year ago

alnoki commented 2 years ago

For ease of indexing, the following events are proposed:

Order book events:


/// Emitted when a maker order is added to or cancelled from the book.
/// Combined with `TakerEvent`, contains all fields necessary to
/// reconstruct the order book from an event stream.
struct MakerEvent has drop, store {
    /// `CANCEL` or `PLACE`
    type: bool,
    /// `ASK` or `BID`
    side: bool,
    /// Order ID of corresponding order, containing encoded price
    order_id: u128,
    /// Size, in lots, of the order at time of placement or cancellation
    size: u64,
    /// Address of corresponding user
    user: address,
    /// For given user, the ID of the custodian required to approve
    /// orders and coin withdrawals
    general_custodian_id: u64
}

/// Emitted when a taker order fills against the book. If a taker order
/// fills against multiple orders, an event is emitted for each one.
struct TakerEvent has drop, store {
    /// `ASK` or `BID`, the side of the maker order filled against
    side: bool,
    /// Order ID of maker order filled against, containing encoded price
    order_id: u128,
    /// Fill size, in lots
    size: u64
}

A corresponding MakerEventHandle and TakerEventHandle would then be fields within each OrderBook

Market registration events

/// Emitted when a market is registered.
struct MarketRegistrationEvent has drop, store {
    /// Market ID of the market just registered    
    market_id: u64,
    /// Base asset type info.
    base_type_info: type_info::TypeInfo,
    /// Quote asset type info.
    quote_type_info: type_info::TypeInfo,
    /// Number of base units exchanged per lot
    lot_size: u64,
    /// Number of quote units exchanged per tick
    tick_size: u64,
    /// ID of custodian capability required to verify deposits,
    /// swaps, and withdrawals of assets that are not coins.
    generic_asset_transfer_custodian_id: u64,
    /// `PURE_COIN_PAIR` when base and quote types are both coins,
    /// otherwise the serial ID of the corresponding market. 
    agnostic_disambiguator: u64
}

Account considerations

Since event handles rely on an account-specific GUID, it may be necessary to enforce that the number of registered markets is U64_MAX / 2 - 1 if all order books and the registry are kept in a single resource account, since each OrderBook will require two event handles and the registry will require one.

alnoki commented 2 years ago

Per the most recent Move Mondays, events are not intended to communicate all information executed on-chain, but rather, relevant deltas to state. It is additionally advised that events be designed such that indexers can make sense of them, which in the case of Econia likely involves emitting the actual price, rather than an Order ID with an encoded price. Moreover, the premier task of simply indexing an order book into orders with a size and price does not require user addresses or general custodian IDs, hence the following simpler event structure may be more appropriate for order book events:

/// Emitted when a maker order is added to or cancelled from the book.
struct MakerEvent has drop, store {
    /// `CANCEL` or `PLACE`
    type: bool,
    /// `ASK` or `BID`
    side: bool,
    /// Size, in lots, of the order at time of placement or cancellation
    size: u64,
    /// Price, in ticks per lot, of the order
    price: u64
}

/// Emitted when a taker order fills against the book. If a taker order
/// fills against multiple orders, an event is emitted for each one.
struct TakerEvent has drop, store {
    /// `ASK` or `BID`, the side of the maker order filled against
    side: bool,
    /// Fill size, in lots
    size: u64
    /// Price, in ticks per lot, of the order filled against
    price: u64
}

Here, only the price and size are included, since the chronology of price-time priority is encoded in the event stream itself: if an RPC endpoint is maintaining a real-time snapshot of the order book by monitoring events, it simply needs to insert/delete events as they come in.

alnoki commented 2 years ago

Market account registration

Optionally, an event may be emitted when a user registers a MarketAccount, which will allow indexers to determine how many users are trading on a given market:


/// Event emitted when user registers a `MarketAccount`.
struct MarketAccountRegistrationEvent has drop, store {
    /// Market account ID of `MarketAccount` just registered.
    market_account_id: u128
}

/// Market account map for all of a user's `MarketAccount`s.
struct MarketAccounts has key {
    /// Map from market account ID to `MarketAccount`. Separated
    /// into different table entries to reduce transaction
    /// collisions across markets, with iterated indexing support.
    map: iterable_table::IterableTable<u128, MarketAccount>,
    /// Event handle for registration events.
    registration_events: EventHandle<MarketAccountRegistrationEvent>
}
alnoki commented 2 years ago

For notifying users that their limit orders have cleared, it may be appropriate to include the address and general custodian ID of any taker orders that have filled.