diff --git a/examples/orderbook/Move.toml b/examples/orderbook/Move.toml new file mode 100644 index 0000000000..43f2bc8778 --- /dev/null +++ b/examples/orderbook/Move.toml @@ -0,0 +1,17 @@ +[package] +name = "orderbook" +version = "0.0.1" + +[dependencies] +MoveStdlib = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/move-stdlib", rev = "main" } +MoveosStdlib = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/moveos-stdlib", rev = "main" } +RoochFramework = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/rooch-framework", rev = "main" } + +[addresses] +orderbook = "_" +std = "0x1" +moveos_std = "0x2" +rooch_framework = "0x3" + +[dev-addresses] +orderbook = "0x42" \ No newline at end of file diff --git a/examples/orderbook/sources/market.move b/examples/orderbook/sources/market.move new file mode 100644 index 0000000000..3d93940bb4 --- /dev/null +++ b/examples/orderbook/sources/market.move @@ -0,0 +1,617 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 +module orderbook::market { + use std::option; + use std::option::Option; + use std::string; + use std::string::String; + use moveos_std::object; + use rooch_framework::coin::{Self, Coin}; + use std::vector; + use moveos_std::event; + use rooch_framework::account_coin_store; + use orderbook::linked_table; + use orderbook::linked_table::LinkedTable; + use rooch_framework::coin_store; + use moveos_std::tx_context::sender; + use moveos_std::type_info::type_name; + use moveos_std::object::{Object, ObjectID, to_shared, new_named_object, transfer}; + use rooch_framework::coin_store::{CoinStore, create_coin_store}; + use rooch_framework::timestamp::now_milliseconds; + use orderbook::critbit::{CritbitTree, find_leaf, borrow_leaf_by_index, borrow_mut_leaf_by_index, + remove_leaf_by_index + }; + use orderbook::critbit; + use moveos_std::table; + use moveos_std::table::Table; + + const DEPLOYER: address = @orderbook; + + + const VERSION: u64 = 4; + + + const BASE_MARKET_FEE: u256 = 20; + const TRADE_FEE_BASE_RATIO: u256 = 1000; + + const MIN_BID_ORDER_ID: u64 = 1; + const MIN_ASK_ORDER_ID: u64 = 1 << 63; + + const ErrorWrongVersion: u64 = 0; + const ErrorWrongPaused: u64 = 1; + const ErrorInputCoin: u64 = 2; + const ErrorWrongMarket: u64 = 3; + const ErrorPriceTooLow: u64 = 4; + const ErrorWrongCreateBid: u64 = 5; + const ErrorFeeTooHigh: u64 = 6; + const ErrorInvalidOrderId: u64 = 7; + const ErrorUnauthorizedCancel: u64 = 8; + + + /// listing info in the market + struct Order has key, store, drop { + /// The order id of the order + order_id: u64, + /// The unit_price of the order + unit_price: u64, + /// the quantity of order + quantity: u256, + /// The owner of order + owner: address, + /// is bid order or listing order + is_bid: bool, + } + + struct TickLevel has store { + price: u64, + // The key is order order id. + open_orders: Object>, + // other price level info + } + + + ///Record some important information of the market + struct Marketplace has key { + /// is paused of market + is_paused: bool, + /// version of market + version: u64, + /// All open bid orders. + bids: CritbitTree, + /// All open ask orders. + asks: CritbitTree, + /// Order id of the next bid order, starting from 0. + next_bid_order_id: u64, + /// Order id of the next ask order, starting from 1<<63. + next_ask_order_id: u64, + /// Marketplace fee of the marketplace + fee: u256, + /// User order info + user_order_info: Table>>, + base_asset: Object>, + quote_asset: Object>, + /// Stores the trading fees paid in `BaseAsset`. + base_asset_trading_fees: Object>, + /// Stores the trading fees paid in `QuoteAsset`. + quote_asset_trading_fees: Object>, + trade_info: TradeInfo + } + + struct TradeInfo has store { + timestamp: u64, + yesterday_volume: u256, + today_volume: u256, + total_volume: u256, + txs: u64 + } + + struct AdminCap has key, store {} + + struct MarketplaceHouse has key { + market_info: Object>, + } + + + + public entry fun create_market( + market_house_obj: &mut Object, + ) { + let market_obj = new_named_object(Marketplace { + is_paused: false, + version: VERSION, + bids: critbit::new(), + asks: critbit::new(), + // Order id of the next bid order, starting from 0. + next_bid_order_id: MIN_BID_ORDER_ID, + // Order id of the next ask order, starting from 1<<63. + next_ask_order_id: MIN_ASK_ORDER_ID, + fee: BASE_MARKET_FEE, + user_order_info: table::new(), + base_asset: create_coin_store(), + quote_asset: create_coin_store(), + base_asset_trading_fees: create_coin_store(), + quote_asset_trading_fees: create_coin_store(), + trade_info: TradeInfo{ + timestamp: now_milliseconds(), + yesterday_volume: 0, + today_volume: 0, + total_volume: 0, + txs: 0 + } + }); + let object_id = object::id(&market_obj); + let market_house = object::borrow_mut(market_house_obj); + let type_name = type_name(); + string::append(&mut type_name, type_name()); + linked_table::push_back(&mut market_house.market_info, type_name, object_id); + to_shared(market_obj); + } + + fun init() { + let market_house = MarketplaceHouse { + market_info: linked_table::new(), + }; + + //TODO market create event + transfer(new_named_object(AdminCap{}), sender()); + to_shared(new_named_object(market_house)) + } + + ///Listing NFT in the collection + public fun list( + market_obj: &mut Object>, + coin: Coin, + unit_price: u64, + ) { + let market = object::borrow_mut(market_obj); + assert!(market.version == VERSION, ErrorWrongVersion); + assert!(market.is_paused == false, ErrorWrongPaused); + let quantity = coin::value(&coin); + let order_id = market.next_ask_order_id; + market.next_ask_order_id = market.next_ask_order_id + 1; + // TODO here maybe wrap to u512? + // let price = (unit_price as u256) * quantity; + assert!(unit_price > 0, ErrorPriceTooLow); + let asks = Order { + order_id, + unit_price, + quantity, + owner: sender(), + is_bid: false, + }; + coin_store::deposit(&mut market.base_asset, coin); + let (find_price, index) = critbit::find_leaf(&market.asks, unit_price); + if (find_price) { + critbit::insert_leaf(&mut market.asks, unit_price, TickLevel{ + price: unit_price, + open_orders: linked_table::new() + }); + }; + let tick_level = critbit::borrow_mut_leaf_by_index(&mut market.asks, index); + linked_table::push_back(&mut tick_level.open_orders, order_id, asks); + + if (!table::contains(&market.user_order_info, sender())) { + table::add(&mut market.user_order_info, sender(), linked_table::new()); + }; + linked_table::push_back(table::borrow_mut(&mut market.user_order_info, sender()), order_id, unit_price); + + } + + + public fun create_bid( + market_obj: &mut Object>, + paid: &mut Coin, + unit_price: u64, + quantity: u256, + ) { + let market = object::borrow_mut(market_obj); + assert!(market.version == VERSION, ErrorWrongVersion); + assert!(market.is_paused == false, ErrorWrongPaused); + assert!(quantity > 0, ErrorWrongCreateBid); + assert!(unit_price > 0, ErrorWrongCreateBid); + // TODO here maybe wrap to u512? + let price = (unit_price as u256) * quantity; + assert!(price <= coin::value(paid), ErrorInputCoin); + let order_id = market.next_bid_order_id; + market.next_bid_order_id = market.next_bid_order_id + 1; + let bid = Order { + order_id, + unit_price, + quantity, + owner: sender(), + is_bid: true, + }; + coin_store::deposit(&mut market.quote_asset, coin::extract(paid, price)); + + let (find_price, index) = critbit::find_leaf(&market.bids, unit_price); + if (!find_price) { + critbit::insert_leaf(&mut market.bids, unit_price, TickLevel { + price: unit_price, + open_orders: linked_table::new() + }); + }; + let tick_level = critbit::borrow_mut_leaf_by_index(&mut market.bids, index); + linked_table::push_back(&mut tick_level.open_orders, order_id, bid); + } + + ///Cancel the listing of inscription + public entry fun cancel_order( + market_obj: &mut Object>, + order_id: u64, + ) { + //Get the list from the collection + let market = object::borrow_mut(market_obj); + assert!(market.version == VERSION, ErrorWrongVersion); + + let usr_open_orders = table::borrow_mut(&mut market.user_order_info, sender()); + let tick_price = *linked_table::borrow(usr_open_orders, order_id); + let is_bid = order_is_bid(order_id); + let (tick_exists, tick_index) = find_leaf(if (is_bid) { &market.bids } else { &market.asks }, tick_price); + assert!(tick_exists, ErrorInvalidOrderId); + let order = remove_order( + if (is_bid) { &mut market.bids } else { &mut market.asks }, + usr_open_orders, + tick_index, + order_id, + sender() + ); + if (is_bid) { + // TODO here maybe wrap to u512? + let total_balance = (order.unit_price as u256) * order.quantity; + account_coin_store::deposit(sender(), coin_store::withdraw(&mut market.quote_asset, total_balance)) + }else { + account_coin_store::deposit(sender(), coin_store::withdraw(&mut market.base_asset, order.quantity)) + } + } + + + ///purchase + public fun buy( + market_obj: &mut Object>, + order_id: u64, + assert_order_exist: bool, + paid: &mut Coin, + ): Option> { + let market = object::borrow_mut(market_obj); + assert!(market.is_paused == false, ErrorWrongPaused); + assert!(market.version == VERSION, ErrorWrongVersion); + let usr_open_orders = table::borrow_mut(&mut market.user_order_info, sender()); + let tick_price = *linked_table::borrow(usr_open_orders, order_id); + let (tick_exists, tick_index) = find_leaf(&market.asks, tick_price); + // Return non-existent orders to none instead of panic during bulk buying + if (!assert_order_exist && !tick_exists) { + return option::none() + }; + assert!(tick_exists, ErrorInvalidOrderId); + let order = remove_order(&mut market.asks, usr_open_orders, tick_index, order_id, sender()); + // TODO here maybe wrap to u512? + let total_price = order.quantity * (order.unit_price as u256); + let trade_coin = coin::extract(paid, total_price); + assert!(coin::value(paid) >= total_price, ErrorInputCoin); + let trade_info = &mut market.trade_info; + trade_info.total_volume = trade_info.total_volume + total_price; + trade_info.txs = trade_info.txs + 1; + if (now_milliseconds() - trade_info.timestamp > 86400000) { + trade_info.yesterday_volume = trade_info.today_volume; + trade_info.today_volume = total_price; + trade_info.timestamp = now_milliseconds(); + }else { + trade_info.today_volume = trade_info.today_volume + total_price; + }; + + // TODO here maybe wrap to u512? + // Here is trade fee is BaseAsset + let trade_fee = total_price * market.fee / TRADE_FEE_BASE_RATIO; + coin_store::deposit(&mut market.base_asset_trading_fees, coin::extract(&mut trade_coin, trade_fee)); + coin_store::deposit(&mut market.base_asset, trade_coin); + option::some(coin_store::withdraw(&mut market.quote_asset, order.quantity)) + } + + public fun accept_bid( + market_obj: &mut Object>, + order_id: u64, + assert_order_exist: bool, + paid: &mut Coin + ): Option> + { + let market = object::borrow_mut(market_obj); + assert!(market.is_paused == false, ErrorWrongPaused); + assert!(market.version == VERSION, ErrorWrongVersion); + let usr_open_orders = table::borrow_mut(&mut market.user_order_info, sender()); + let tick_price = *linked_table::borrow(usr_open_orders, order_id); + let (tick_exists, tick_index) = find_leaf(&market.bids, tick_price); + // Return non-existent orders to none instead of panic during bulk buying + if (!assert_order_exist && !tick_exists) { + return option::none() + }; + assert!(tick_exists, ErrorInvalidOrderId); + + let order = remove_order(&mut market.bids, usr_open_orders, tick_index, order_id, sender()); + assert!(coin::value(paid) >= order.quantity, ErrorInputCoin); + let trade_coin = coin::extract(paid, order.quantity); + + // TODO here maybe wrap to u512? + let total_price = (order.unit_price as u256) * order.quantity; + let trade_info = &mut market.trade_info; + + trade_info.total_volume = trade_info.total_volume + total_price; + if (now_milliseconds() - trade_info.timestamp > 86400000) { + trade_info.yesterday_volume = trade_info.today_volume; + trade_info.today_volume = total_price; + trade_info.timestamp = now_milliseconds(); + }else { + trade_info.today_volume = trade_info.today_volume + total_price; + }; + + // Here trade fee is QuoteAsset + let trade_fee = order.quantity * market.fee / TRADE_FEE_BASE_RATIO; + coin_store::deposit(&mut market.quote_asset_trading_fees, coin::extract(&mut trade_coin, trade_fee)); + coin_store::deposit(&mut market.quote_asset_trading_fees, trade_coin); + + option::some(coin_store::withdraw(&mut market.base_asset, total_price)) + } + + + + public entry fun withdraw_profits( + _admin: &mut Object, + market_obj: &mut Object>, + receiver: address, + ) { + let market = object::borrow_mut(market_obj); + assert!(market.version == VERSION, ErrorWrongVersion); + let quote_amount = coin_store::balance(&market.quote_asset_trading_fees); + account_coin_store::deposit(receiver, coin_store::withdraw(&mut market.quote_asset_trading_fees, quote_amount)); + let base_amount = coin_store::balance(&market.base_asset_trading_fees); + account_coin_store::deposit(receiver, coin_store::withdraw(&mut market.base_asset_trading_fees, base_amount)); + } + + + public entry fun update_market_fee( + _admin: &mut Object, + market_obj: &mut Object>, + fee: u256, + ) { + let market = object::borrow_mut(market_obj); + assert!(market.version == VERSION, ErrorWrongVersion); + assert!(fee < TRADE_FEE_BASE_RATIO, ErrorFeeTooHigh); + market.fee = fee + } + + public entry fun migrate_marketplace( + market_obj: &mut Object>, + ) { + let market = object::borrow_mut(market_obj); + assert!(market.version <= VERSION, ErrorWrongVersion); + market.version = VERSION; + } + + fun remove_order( + open_orders: &mut CritbitTree, + user_order_info: &mut Object>, + tick_index: u64, + order_id: u64, + user: address, + ): Order { + linked_table::remove(user_order_info, order_id); + let tick_level = borrow_leaf_by_index(open_orders, tick_index); + assert!(linked_table::contains(&tick_level.open_orders, order_id), ErrorInvalidOrderId); + let mut_tick_level = borrow_mut_leaf_by_index(open_orders, tick_index); + let order = linked_table::remove(&mut mut_tick_level.open_orders, order_id); + assert!(order.owner == user, ErrorUnauthorizedCancel); + if (linked_table::is_empty(&mut_tick_level.open_orders)) { + destroy_empty_level(remove_leaf_by_index(open_orders, tick_index)); + }; + order + } + + fun destroy_empty_level(level: TickLevel) { + let TickLevel { + price: _, + open_orders: orders, + } = level; + + linked_table::destroy_empty(orders); + } + + struct QueryOrderEvent has copy, drop { + order_ids: vector, + unit_prices: vector, + quantitys: vector, + owners: vector
, + is_bids: vector + } + + public fun query_order( + market_obj: &Object>, + query_bid: bool, + from_order: Option, + start: u64 + ): vector { + let market = object::borrow(market_obj); + let order_ids = vector[]; + let unit_prices = vector[]; + let quantitys = vector[]; + let owners = vector
[]; + let is_bids = vector[]; + + if (query_bid) { + let i = 0; + let from = if (option::is_none(&from_order)) { + let (key, _) = critbit::max_leaf(&market.bids); + key + }else { + *option::borrow(&from_order) + }; + let count = start; + while (i < 50) { + let tick_level = critbit::borrow_leaf_by_key(&market.bids, from); + let order_count = linked_table::length(&tick_level.open_orders); + + while (order_count > count) { + let order = linked_table::borrow(&tick_level.open_orders, count); + vector::push_back(&mut order_ids, order.order_id); + vector::push_back(&mut unit_prices, order.unit_price); + vector::push_back(&mut quantitys, order.quantity); + vector::push_back(&mut owners, order.owner); + vector::push_back(&mut is_bids, order.is_bid); + + count = count + 1; + i = i + 1; + if (i >= 50) { + event::emit( + QueryOrderEvent{ + order_ids, + unit_prices, + quantitys, + owners, + is_bids + } + ); + return order_ids + } + }; + count = 0; + let (key, index) = critbit::previous_leaf(&market.bids, from); + if (index != 0x8000000000000000) { + from = key; + }else { + event::emit( + QueryOrderEvent{ + order_ids, + unit_prices, + quantitys, + owners, + is_bids + } + ); + return order_ids + } + }; + }else { + let i = 0; + let from = if (option::is_none(&from_order)) { + let (key, _) = critbit::min_leaf(&market.asks); + key + }else { + *option::borrow(&from_order) + }; + let count = start; + while (i < 50) { + let tick_level = critbit::borrow_leaf_by_key(&market.asks, from); + let order_count = linked_table::length(&tick_level.open_orders); + + while (order_count > count) { + let order = linked_table::borrow(&tick_level.open_orders, count); + vector::push_back(&mut order_ids, order.order_id); + vector::push_back(&mut unit_prices, order.unit_price); + vector::push_back(&mut quantitys, order.quantity); + vector::push_back(&mut owners, order.owner); + vector::push_back(&mut is_bids, order.is_bid); + + count = count + 1; + i = i + 1; + if (i >= 50) { + event::emit( + QueryOrderEvent{ + order_ids, + unit_prices, + quantitys, + owners, + is_bids + } + ); + return order_ids + } + }; + count = 0; + let (key, index) = critbit::next_leaf(&market.asks, from); + if (index != 0x8000000000000000) { + from = key; + }else { + event::emit( + QueryOrderEvent{ + order_ids, + unit_prices, + quantitys, + owners, + is_bids + } + ); + return order_ids + } + }; + }; + event::emit( + QueryOrderEvent{ + order_ids, + unit_prices, + quantitys, + owners, + is_bids + } + ); + return order_ids + } + + public fun query_user_order( + market_obj: &Object>, + user: address, + from_order: Option, + count: u64 + ): vector{ + let market = object::borrow(market_obj); + let user_table = table::borrow(&market.user_order_info, user); + let order_ids = vector[]; + let unit_prices = vector[]; + let quantitys = vector[]; + let owners = vector
[]; + let is_bids = vector[]; + let from = if (option::is_none(&from_order)) { + *option::borrow(linked_table::front(user_table)) + }else { + *option::borrow(&from_order) + }; + + let i = 0; + while (i < count) { + let tick_price = *linked_table::borrow(user_table, from); + + let is_bid = order_is_bid(from); + let open_orders = if (is_bid) { &market.bids } else { &market.asks }; + let (tick_exists, tick_index) = find_leaf(open_orders, tick_price); + if (tick_exists) { + let tick_level = borrow_leaf_by_index(open_orders, tick_index); + let order = linked_table::borrow(&tick_level.open_orders, from); + vector::push_back(&mut order_ids, order.order_id); + vector::push_back(&mut unit_prices, order.unit_price); + vector::push_back(&mut quantitys, order.quantity); + vector::push_back(&mut owners, order.owner); + vector::push_back(&mut is_bids, order.is_bid); + i = i + 1; + }else { + break + }; + if (option::is_some(linked_table::next(user_table, from))){ + from = *option::borrow(linked_table::next(user_table, from)); + }else { + break + } + }; + event::emit( + QueryOrderEvent{ + order_ids, + unit_prices, + quantitys, + owners, + is_bids + } + ); + return order_ids + } + + + fun order_is_bid(order_id: u64): bool { + return order_id < MIN_ASK_ORDER_ID + } +} diff --git a/examples/orderbook/sources/utils/critbit.move b/examples/orderbook/sources/utils/critbit.move new file mode 100644 index 0000000000..6d079f601e --- /dev/null +++ b/examples/orderbook/sources/utils/critbit.move @@ -0,0 +1,504 @@ +/// forked by deepbook + +module orderbook::critbit { + + use moveos_std::table::{Self, Table}; + + // <<<<<<<<<<<<<<<<<<<<<<<< Error codes <<<<<<<<<<<<<<<<<<<<<<<< + const ErrorExceedCapacity: u64 = 2; + const ErrorTreeNotEmpty: u64 = 3; + const ErrorKeyAlreadyExist: u64 = 4; + const ErrorLeafNotExist: u64 = 5; + const ErrorIndexOutOfRange: u64 = 7; + const ErrorNullParent: u64 = 8; + // <<<<<<<<<<<<<<<<<<<<<<<< Error codes <<<<<<<<<<<<<<<<<<<<<<<< + + + // <<<<<<<<<<<<<<<<<<<<<<<< Constants <<<<<<<<<<<<<<<<<<<<<<<< + const PARTITION_INDEX: u64 = 0x8000000000000000; + // 9223372036854775808 + const MAX_U64: u64 = 0xFFFFFFFFFFFFFFFF; + // 18446744073709551615 + const MAX_CAPACITY: u64 = 0x7FFFFFFFFFFFFFFF; + // <<<<<<<<<<<<<<<<<<<<<<<< Constants <<<<<<<<<<<<<<<<<<<<<<<< + + struct Leaf has store, drop { + key: u64, + value: V, + parent: u64, + } + + struct InternalNode has store, drop { + mask: u64, + left_child: u64, + right_child: u64, + // We keep track of the parent node to make it easier to traverse the tree to retrieve the previous or the next leaf. + parent: u64, + } + + // Leaves of the Critbit Tree are sorted in ascending order. + struct CritbitTree has store { + root: u64, + internal_nodes: Table, + // A leaf contains orders at that price level. + leaves: Table>, + min_leaf: u64, + max_leaf: u64, + next_internal_node_index: u64, + next_leaf_index: u64 + } + + public fun new(): CritbitTree { + CritbitTree { + root: PARTITION_INDEX, + internal_nodes: table::new(), + leaves: table::new(), + min_leaf: PARTITION_INDEX, + max_leaf: PARTITION_INDEX, + next_internal_node_index: 0, + next_leaf_index: 0 + } + } + + public fun size(tree: &CritbitTree): u64 { + table::length(&tree.leaves) + } + + public fun is_empty(tree: &CritbitTree): bool { + table::is_empty(&tree.leaves) + } + + // Return (key, index) the leaf with minimum value. + // A market buy order will start consuming liquidty from the min leaf. + public fun min_leaf(tree: &CritbitTree): (u64, u64) { + assert!(!is_empty(tree), ErrorLeafNotExist); + let min_leaf = table::borrow(&tree.leaves, tree.min_leaf); + return (min_leaf.key, tree.min_leaf) + } + + // Return (key, index) the leaf with maximum value. + // A market sell order will start consuming liquidity from the max leaf. + public fun max_leaf(tree: &CritbitTree): (u64, u64) { + assert!(!is_empty(tree), ErrorLeafNotExist); + let max_leaf = table::borrow(&tree.leaves, tree.max_leaf); + return (max_leaf.key, tree.max_leaf) + } + + // Return the previous leaf (key, index) of the input leaf. + // Market sell orders consume liquidities by iterating through the leaves in descending order starting from the max leaf of the asks Critbit Tree. + // This function provides the iterator for this procedure. + public fun previous_leaf(tree: &CritbitTree, key: u64): (u64, u64) { + let (_, index) = find_leaf(tree, key); + assert!(index != PARTITION_INDEX, ErrorLeafNotExist); + let ptr = MAX_U64 - index; + let parent = table::borrow(&tree.leaves, index).parent; + while (parent != PARTITION_INDEX && is_left_child(tree, parent, ptr)) { + ptr = parent; + parent = table::borrow(&tree.internal_nodes, ptr).parent; + }; + if (parent == PARTITION_INDEX) { + return (0, PARTITION_INDEX) + }; + index = MAX_U64 - right_most_leaf(tree, table::borrow(&tree.internal_nodes, parent).left_child); + let key = table::borrow(&tree.leaves, index).key; + return (key, index) + } + + // Return the next leaf (key, index) of the input leaf. + // Market buy orders consume liquidities by iterating through the leaves in ascending order starting from the min leaf of the asks Critbit Tree. + // This function provides the iterator for this procedure. + public fun next_leaf(tree: &CritbitTree, key: u64): (u64, u64) { + let (_, index) = find_leaf(tree, key); + assert!(index != PARTITION_INDEX, ErrorLeafNotExist); + let ptr = MAX_U64 - index; + let parent = table::borrow(&tree.leaves, index).parent; + while (parent != PARTITION_INDEX && !is_left_child(tree, parent, ptr)) { + ptr = parent; + parent = table::borrow(&tree.internal_nodes, ptr).parent; + }; + if (parent == PARTITION_INDEX) { + return (0, PARTITION_INDEX) + }; + index = MAX_U64 - left_most_leaf(tree, table::borrow(&tree.internal_nodes, parent).right_child); + let key = table::borrow(&tree.leaves, index).key; + return (key, index) + } + + + fun left_most_leaf(tree: &CritbitTree, root: u64): u64 { + let ptr = root; + while (ptr < PARTITION_INDEX) { + ptr = table::borrow(&tree.internal_nodes, ptr).left_child; + }; + ptr + } + + fun right_most_leaf(tree: &CritbitTree, root: u64): u64 { + let ptr = root; + while (ptr < PARTITION_INDEX) { + ptr = table::borrow(&tree.internal_nodes, ptr).right_child; + }; + ptr + } + + // Insert new leaf to the tree. + // Returns the index of the leaf. + // Called when a new order is being injected to the order book. + public fun insert_leaf(tree: &mut CritbitTree, key: u64, value: V): u64 { + let new_leaf = Leaf { + key, + value, + parent: PARTITION_INDEX, + }; + let new_leaf_index = tree.next_leaf_index; + tree.next_leaf_index = tree.next_leaf_index + 1; + assert!(new_leaf_index < MAX_CAPACITY - 1, ErrorExceedCapacity); + table::add(&mut tree.leaves, new_leaf_index, new_leaf); + + let closest_leaf_index = get_closest_leaf_index_by_key(tree, key); + + // Handle the first insertion + if (closest_leaf_index == PARTITION_INDEX) { + assert!(new_leaf_index == 0, ErrorTreeNotEmpty); + tree.root = MAX_U64 - new_leaf_index; + tree.min_leaf = new_leaf_index; + tree.max_leaf = new_leaf_index; + return 0 + }; + + let closest_key = table::borrow(&tree.leaves, closest_leaf_index).key; + assert!(closest_key != key, ErrorKeyAlreadyExist); + + // Note that we reserve count_leading_zeros of form u128 for future use + let critbit = 64 - (count_leading_zeros(((closest_key ^ key) as u128)) - 64); + let new_mask = 1u64 << (critbit - 1); + + let new_internal_node = InternalNode { + mask: new_mask, + left_child: PARTITION_INDEX, + right_child: PARTITION_INDEX, + parent: PARTITION_INDEX, + }; + let new_internal_node_index = tree.next_internal_node_index; + tree.next_internal_node_index = tree.next_internal_node_index + 1; + table::add(&mut tree.internal_nodes, new_internal_node_index, new_internal_node); + + let ptr = tree.root; + let new_internal_node_parent_index = PARTITION_INDEX; + // Search position of the new internal node + while (ptr < PARTITION_INDEX) { + let internal_node = table::borrow(&tree.internal_nodes, ptr); + if (new_mask > internal_node.mask) { + break + }; + new_internal_node_parent_index = ptr; + if (key & internal_node.mask == 0) { + ptr = internal_node.left_child; + } else { + ptr = internal_node.right_child; + }; + }; + + // Update the child info of new internal node's parent + if (new_internal_node_parent_index == PARTITION_INDEX) { + // if the new internal node is root + tree.root = new_internal_node_index; + } else { + // In another case, we update the child field of the new internal node's parent + // And the parent field of the new internal node + let is_left_child = is_left_child(tree, new_internal_node_parent_index, ptr); + update_child(tree, new_internal_node_parent_index, new_internal_node_index, is_left_child); + }; + + // Finally, update the child field of the new internal node + let is_left_child = new_mask & key == 0; + update_child(tree, new_internal_node_index, MAX_U64 - new_leaf_index, is_left_child); + update_child(tree, new_internal_node_index, ptr, !is_left_child); + + if (table::borrow(&tree.leaves, tree.min_leaf).key > key) { + tree.min_leaf = new_leaf_index; + }; + if (table::borrow(&tree.leaves, tree.max_leaf).key < key) { + tree.max_leaf = new_leaf_index; + }; + new_leaf_index + } + + // Find the leaf from the tree. + // Returns true and the index of the leaf if exists. + public fun find_leaf(tree: &CritbitTree, key: u64): (bool, u64) { + if (is_empty(tree)) { + return (false, PARTITION_INDEX) + }; + let closest_leaf_index = get_closest_leaf_index_by_key(tree, key); + let closeset_leaf = table::borrow(&tree.leaves, closest_leaf_index); + if (closeset_leaf.key != key) { + return (false, PARTITION_INDEX) + } else { + return (true, closest_leaf_index) + } + } + + public fun find_closest_key(tree: & CritbitTree, key: u64): u64 { + if (is_empty(tree)) { + return 0 + }; + let closest_leaf_index = get_closest_leaf_index_by_key(tree, key); + let closeset_leaf = table::borrow(&tree.leaves, closest_leaf_index); + closeset_leaf.key + } + + public fun remove_leaf_by_index(tree: &mut CritbitTree, index: u64): V { + let key = table::borrow(&tree.leaves, index).key; + if (tree.min_leaf == index) { + let (_, index) = next_leaf(tree, key); + tree.min_leaf = index; + }; + if (tree.max_leaf == index) { + let (_, index) = previous_leaf(tree, key); + tree.max_leaf = index; + }; + + let is_left_child_; + let Leaf { key: _, value, parent: removed_leaf_parent_index } = table::remove(&mut tree.leaves, index); + + if (size(tree) == 0) { + tree.root = PARTITION_INDEX; + tree.min_leaf = PARTITION_INDEX; + tree.max_leaf = PARTITION_INDEX; + tree.next_internal_node_index = 0; + tree.next_leaf_index = 0; + } else { + assert!(removed_leaf_parent_index != PARTITION_INDEX, ErrorIndexOutOfRange); + let removed_leaf_parent = table::borrow(&tree.internal_nodes, removed_leaf_parent_index); + let removed_leaf_grand_parent_index = removed_leaf_parent.parent; + + // Note that sibling of the removed leaf can be a leaf or an internal node + is_left_child_ = is_left_child(tree, removed_leaf_parent_index, MAX_U64 - index); + let sibling_index = if (is_left_child_) { removed_leaf_parent.right_child } + else { removed_leaf_parent.left_child }; + + if (removed_leaf_grand_parent_index == PARTITION_INDEX) { + // Parent of the removed leaf is the tree root + // Update the parent of the sibling node and and set sibling as the tree root + if (sibling_index < PARTITION_INDEX) { + // sibling is an internal node + table::borrow_mut(&mut tree.internal_nodes, sibling_index).parent = PARTITION_INDEX; + } else { + // sibling is a leaf + table::borrow_mut(&mut tree.leaves, MAX_U64 - sibling_index).parent = PARTITION_INDEX; + }; + tree.root = sibling_index; + } else { + // grand parent of the removed leaf is a internal node + // set sibling as the child of the grand parent of the removed leaf + is_left_child_ = is_left_child(tree, removed_leaf_grand_parent_index, removed_leaf_parent_index); + update_child(tree, removed_leaf_grand_parent_index, sibling_index, is_left_child_); + }; + table::remove(&mut tree.internal_nodes, removed_leaf_parent_index); + }; + value + } + + public fun borrow_mut_leaf_by_index(tree: &mut CritbitTree, index: u64): &mut V { + let entry = table::borrow_mut(&mut tree.leaves, index); + &mut entry.value + } + + public fun borrow_leaf_by_index(tree: & CritbitTree, index: u64): &V { + let entry = table::borrow(&tree.leaves, index); + &entry.value + } + + public fun borrow_leaf_by_key(tree: & CritbitTree, key: u64): &V { + let (is_exist, index) = find_leaf(tree, key); + assert!(is_exist, ErrorLeafNotExist); + borrow_leaf_by_index(tree, index) + } + + public fun borrow_mut_leaf_by_key(tree: &mut CritbitTree, key: u64): &mut V { + let (is_exist, index) = find_leaf(tree, key); + assert!(is_exist, ErrorLeafNotExist); + borrow_mut_leaf_by_index(tree, index) + } + + public fun drop(tree: CritbitTree) { + let CritbitTree { + root: _, + internal_nodes, + leaves, + min_leaf: _, + max_leaf: _, + next_internal_node_index: _, + next_leaf_index: _, + } = tree; + table::drop(internal_nodes); + table::drop(leaves); + } + + public fun destroy_empty(tree: CritbitTree) { + assert!(table::length(&tree.leaves) == 0, 0); + + let CritbitTree { + root: _, + leaves, + internal_nodes, + min_leaf: _, + max_leaf: _, + next_internal_node_index: _, + next_leaf_index: _ + } = tree; + + table::destroy_empty(leaves); + table::destroy_empty(internal_nodes); + } + + // function for internal usage + fun get_closest_leaf_index_by_key(tree: &CritbitTree, key: u64): u64 { + let ptr = tree.root; + // if tree is empty, return the patrition index + if (ptr == PARTITION_INDEX) return PARTITION_INDEX; + while (ptr < PARTITION_INDEX) { + let node = table::borrow(&tree.internal_nodes, ptr); + if (key & node.mask == 0) { + ptr = node.left_child; + } else { + ptr = node.right_child; + } + }; + return (MAX_U64 - ptr) + } + + // new_child can be either internal node or leaf + fun update_child(tree: &mut CritbitTree, parent_index: u64, new_child: u64, is_left_child: bool) { + assert!(parent_index != PARTITION_INDEX, ErrorNullParent); + if (is_left_child) { + table::borrow_mut(&mut tree.internal_nodes, parent_index).left_child = new_child; + } else { + table::borrow_mut(&mut tree.internal_nodes, parent_index).right_child = new_child; + }; + if (new_child > PARTITION_INDEX) { + table::borrow_mut(&mut tree.leaves, MAX_U64 - new_child).parent = parent_index; + } else { + table::borrow_mut(&mut tree.internal_nodes, new_child).parent = parent_index; + } + } + + fun is_left_child(tree: &CritbitTree, parent_index: u64, index: u64): bool { + table::borrow(&tree.internal_nodes, parent_index).left_child == index + } + + fun count_leading_zeros(x: u128): u8 { + if (x == 0) { + 128 + } else { + let n: u8 = 0; + if (x & 0xFFFFFFFFFFFFFFFF0000000000000000 == 0) { + // x's higher 64 is all zero, shift the lower part over + x = x << 64; + n = n + 64; + }; + if (x & 0xFFFFFFFF000000000000000000000000 == 0) { + // x's higher 32 is all zero, shift the lower part over + x = x << 32; + n = n + 32; + }; + if (x & 0xFFFF0000000000000000000000000000 == 0) { + // x's higher 16 is all zero, shift the lower part over + x = x << 16; + n = n + 16; + }; + if (x & 0xFF000000000000000000000000000000 == 0) { + // x's higher 8 is all zero, shift the lower part over + x = x << 8; + n = n + 8; + }; + if (x & 0xF0000000000000000000000000000000 == 0) { + // x's higher 4 is all zero, shift the lower part over + x = x << 4; + n = n + 4; + }; + if (x & 0xC0000000000000000000000000000000 == 0) { + // x's higher 2 is all zero, shift the lower part over + x = x << 2; + n = n + 2; + }; + if (x & 0x80000000000000000000000000000000 == 0) { + n = n + 1; + }; + + n + } + } + + #[test_only] + use std::vector; + + #[test_only] + public fun new_leaf_for_test(key: u64, value: V, parent: u64): Leaf { + Leaf { + key, + value, + parent, + } + } + + #[test_only] + public fun new_internal_node_for_test(mask: u64, parent: u64, left_child: u64, right_child: u64): InternalNode { + InternalNode { + mask, + left_child, + right_child, + parent, + } + } + + #[test_only] + public fun check_tree_struct( + tree: &CritbitTree, + internal_node_keys: &vector, + internal_node: &vector, + leaves_keys: &vector, + leaves: &vector>, + root: u64, + min_leaf: u64, + max_leaf: u64 + ): bool { + assert!(vector::length(internal_node_keys) == vector::length(internal_node), 0); + assert!(vector::length(leaves_keys) == vector::length(leaves), 0); + if (tree.root != root || tree.min_leaf != min_leaf || tree.max_leaf != max_leaf) { + return false + }; + let internal_node_from_tree = &tree.internal_nodes; + let leaves_from_tree = &tree.leaves; + let i = 0; + let is_equal = true; + while (i < vector::length(internal_node_keys)) { + let key = *vector::borrow(internal_node_keys, i); + if (table::borrow(internal_node_from_tree, key) != vector::borrow(internal_node, i)) { + is_equal = false; + break + }; + i = i + 1; + }; + i = 0; + while (i < vector::length(leaves_keys)) { + let key = *vector::borrow(leaves_keys, i); + if (table::borrow(leaves_from_tree, key) != vector::borrow(leaves, i)) { + is_equal = false; + break + }; + i = i + 1; + }; + is_equal + } + + #[test_only] + public fun check_empty_tree(tree: &CritbitTree) { + assert!(table::is_empty(&tree.leaves), 0); + assert!(table::is_empty(&tree.internal_nodes), 0); + assert!(tree.root == PARTITION_INDEX, 0); + assert!(tree.min_leaf == PARTITION_INDEX, 0); + assert!(tree.max_leaf == PARTITION_INDEX, 0); + } +} \ No newline at end of file diff --git a/examples/orderbook/sources/utils/linked_table.move b/examples/orderbook/sources/utils/linked_table.move new file mode 100644 index 0000000000..97dfdd9296 --- /dev/null +++ b/examples/orderbook/sources/utils/linked_table.move @@ -0,0 +1,210 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +/// Similar to `sui::table` but the values are linked together, allowing for ordered insertion and +/// removal +module orderbook::linked_table { + use std::option::{Self, Option}; + use moveos_std::object::Object; + use moveos_std::object; + + + // Attempted to destroy a non-empty table + const ErrorTableNotEmpty: u64 = 0; + // Attempted to remove the front or back of an empty table + const ErrorTableIsEmpty: u64 = 1; + + struct LinkedTable has key, store { + /// the number of key-value pairs in the table + size: u64, + /// the front of the table, i.e. the key of the first entry + head: Option, + /// the back of the table, i.e. the key of the last entry + tail: Option, + } + + struct Node has store { + /// the previous key + prev: Option, + /// the next key + next: Option, + /// the value being stored + value: V + } + + /// Creates a new, empty table + public fun new(): Object> { + object::new_named_object( + LinkedTable { + size: 0, + head: option::none(), + tail: option::none(), + } + ) + + } + + /// Returns the key for the first element in the table, or None if the table is empty + public fun front(table_obj: &Object>): &Option { + let table = object::borrow(table_obj); + &table.head + } + + /// Returns the key for the last element in the table, or None if the table is empty + public fun back(table_obj: &Object>): &Option { + let table = object::borrow(table_obj); + &table.tail + } + + /// Inserts a key-value pair at the front of the table, i.e. the newly inserted pair will be + /// the first element in the table + /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with + /// that key `k: K`. + public fun push_front( + table_obj: &mut Object>, + k: K, + value: V, + ) { + let table = object::borrow_mut(table_obj); + table.size = table.size + 1; + let old_head = option::swap_or_fill(&mut table.head, k); + if (option::is_none(&table.tail)) option::fill(&mut table.tail, k); + let prev = option::none(); + let next = if (option::is_some(&old_head)) { + let old_head_k = option::destroy_some(old_head); + let node = object::borrow_mut_field, K, Node>(table_obj, old_head_k); + node.prev = option::some(k); + option::some(old_head_k) + } else { + option::none() + }; + object::add_field(table_obj, k, Node { prev, next, value }); + } + + /// Inserts a key-value pair at the back of the table, i.e. the newly inserted pair will be + /// the last element in the table + /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with + /// that key `k: K`. + public fun push_back( + table_obj: &mut Object>, + k: K, + value: V, + ) { + let table = object::borrow_mut(table_obj); + table.size = table.size + 1; + if (option::is_none(&table.head)) option::fill(&mut table.head, k); + let old_tail = option::swap_or_fill(&mut table.tail, k); + let prev = if (option::is_some(&old_tail)) { + let old_tail_k = option::destroy_some(old_tail); + object::borrow_mut_field, K, Node>(table_obj, old_tail_k).next = option::some(k); + option::some(old_tail_k) + } else { + option::none() + }; + let next = option::none(); + object::add_field(table_obj, k, Node { prev, next, value }); + } + + /// Immutable borrows the value associated with the key in the table `table: &LinkedTable`. + /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with + /// that key `k: K`. + public fun borrow(table_obj: &Object>, k: K): &V { + &object::borrow_field, K, Node>(table_obj, k).value + } + + /// Mutably borrows the value associated with the key in the table `table: &mut LinkedTable`. + /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with + /// that key `k: K`. + public fun borrow_mut( + table_obj: &mut Object>, + k: K, + ): &mut V { + &mut object::borrow_mut_field, K, Node>(table_obj, k).value + } + + /// Borrows the key for the previous entry of the specified key `k: K` in the table + /// `table: &LinkedTable`. Returns None if the entry does not have a predecessor. + /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with + /// that key `k: K` + public fun prev(table_obj: &Object>, k: K): &Option { + &object::borrow_field, K, Node>(table_obj, k).prev + } + + /// Borrows the key for the next entry of the specified key `k: K` in the table + /// `table: &LinkedTable`. Returns None if the entry does not have a predecessor. + /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with + /// that key `k: K` + public fun next(table_obj: &Object>, k: K): &Option { + &object::borrow_field, K, Node>(table_obj, k).next + } + + /// Removes the key-value pair in the table `table: &mut LinkedTable` and returns the value. + /// This splices the element out of the ordering. + /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with + /// that key `k: K`. Note: this is also what happens when the table is empty. + public fun remove(table_obj: &mut Object>, k: K): V { + let Node { prev, next, value } = object::remove_field, K, Node>(table_obj, k); + if (option::is_some(&prev)) { + object::borrow_mut_field, K, Node>(table_obj, *option::borrow(&prev)).next = next + }; + if (option::is_some(&next)) { + object::borrow_mut_field, K, Node>(table_obj, *option::borrow(&next)).prev = prev + }; + let table = object::borrow_mut(table_obj); + table.size = table.size - 1; + if (option::borrow(&table.head) == &k) table.head = next; + if (option::borrow(&table.tail) == &k) table.tail = prev; + value + } + + /// Removes the front of the table `table: &mut LinkedTable` and returns the value. + /// Aborts with `ETableIsEmpty` if the table is empty + public fun pop_front(table_obj: &mut Object>): (K, V) { + let table = object::borrow_mut(table_obj); + assert!(option::is_some(&table.head), ErrorTableIsEmpty); + let head = *option::borrow(&table.head); + (head, remove(table_obj, head)) + } + + /// Removes the back of the table `table: &mut LinkedTable` and returns the value. + /// Aborts with `ETableIsEmpty` if the table is empty + public fun pop_back(table_obj: &mut Object>): (K, V) { + let table = object::borrow_mut(table_obj); + assert!(option::is_some(&table.tail), ErrorTableIsEmpty); + let tail = *option::borrow(&table.tail); + (tail, remove(table_obj, tail)) + } + + /// Returns true iff there is a value associated with the key `k: K` in table + /// `table: &LinkedTable` + public fun contains(table_obj: &Object>, k: K): bool { + object::contains_field_with_type, K, Node>(table_obj, k) + } + + /// Returns the size of the table, the number of key-value pairs + public fun length(table_obj: &Object>): u64 { + let table = object::borrow(table_obj); + table.size + } + + /// Returns true iff the table is empty (if `length` returns `0`) + public fun is_empty(table_obj: &Object>): bool { + let table = object::borrow(table_obj); + table.size == 0 + } + + /// Destroys an empty table + /// Aborts with `ETableNotEmpty` if the table still contains values + public fun destroy_empty(table_obj: Object>) { + let table = object::remove(table_obj); + let LinkedTable { size, head: _, tail: _ } = table; + assert!(size == 0, ErrorTableNotEmpty); + } + + /// Drop a possibly non-empty table. + /// Usable only if the value type `V` has the `drop` ability + public fun drop(table_obj: Object>) { + let table = object::remove(table_obj); + let LinkedTable { size: _, head: _, tail: _ } = table; + } +}