diff --git a/Move.toml b/Move.toml index d9e4779..bbd5f0b 100644 --- a/Move.toml +++ b/Move.toml @@ -1,7 +1,6 @@ [package] name = "LiquidityLayer" version = "1.0.0" -published-at = "0x4e0629fa51a62b0c1d7c7b9fc89237ec5b6f630d7798ad3f06d820afb93a995a" [dependencies.Sui] git = "https://github.com/MystenLabs/sui.git" @@ -9,10 +8,9 @@ subdir = "crates/sui-framework/packages/sui-framework" # mainnet-1.0.0 rev = "ae1212baf8f0837e25926d941db3d26a61c1bea2" -[dependencies.Originmate] -git = "https://github.com/Origin-Byte/originmate.git" -# mainnet-1.0.0 -rev = "36e02283fa00451e8476a1bbc201af9a248396de" +[dependencies.Critbit] +git = "https://github.com/Origin-Byte/critbit.git" +rev = "v1.0.1-mainnet" [dependencies.Permissions] git = "https://github.com/Origin-Byte/nft-protocol.git" @@ -33,4 +31,4 @@ subdir = "contracts/kiosk" rev = "2a435a0194a3b62988f38de519c212c224162be2" [addresses] -liquidity_layer = "0x4e0629fa51a62b0c1d7c7b9fc89237ec5b6f630d7798ad3f06d820afb93a995a" \ No newline at end of file +liquidity_layer = "0x0" \ No newline at end of file diff --git a/sources/trading/orderbook.move b/sources/trading/orderbook.move index b253175..447da14 100644 --- a/sources/trading/orderbook.move +++ b/sources/trading/orderbook.move @@ -31,14 +31,15 @@ module liquidity_layer::orderbook { use sui::coin::{Self, Coin}; use sui::kiosk::{Self, Kiosk}; use sui::object::{Self, ID, UID}; - use sui::transfer::share_object; + use sui::transfer; use sui::tx_context::{Self, TxContext}; use sui::dynamic_field as df; - use ob_permissions::witness::Witness as DelegatedWitness; + use ob_permissions::witness::{Self, Witness as DelegatedWitness}; use ob_kiosk::ob_kiosk; use ob_request::transfer_request::{Self, TransferRequest}; - use originmate::crit_bit_u64::{Self as crit_bit, CB as CBTree}; + + use critbit::critbit_u64::{Self as critbit, CritbitTree}; use liquidity_layer::trading; use liquidity_layer::liquidity_layer::LIQUIDITY_LAYER; @@ -102,7 +103,7 @@ module liquidity_layer::orderbook { /// A critbit order book implementation. Contains two ordered trees: /// 1. bids ASC /// 2. asks DESC - struct Orderbook has key { + struct Orderbook has key, store { id: UID, version: u64, tick_size: u64, @@ -113,12 +114,12 @@ module liquidity_layer::orderbook { /// such an order is saying: /// /// > for this NFT, I want to receive at least this amount of FT. - asks: CBTree>, + asks: CritbitTree>, /// A bid order stores amount of tokens of type "B"(id) to trade. A bid /// order is saying: /// /// > for any NFT in this collection, I will spare this many tokens - bids: CBTree>>, + bids: CritbitTree>>, } /// The contract which creates the orderbook can restrict specific actions @@ -279,6 +280,210 @@ module liquidity_layer::orderbook { ft_type: String, } + /// Create a new `Orderbook` + /// + /// To implement specific logic in your smart contract, you can toggle the + /// protection on specific actions. That will make them only accessible via + /// witness protected methods. + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is not an OriginByte policy. + public fun new( + _witness: DelegatedWitness, + transfer_policy: &TransferPolicy, + buy_nft: bool, + create_ask: bool, + create_bid: bool, + ctx: &mut TxContext, + ): Orderbook { + assert!( + transfer_request::is_originbyte(transfer_policy), + ENotOriginBytePolicy, + ); + + new_(WitnessProtectedActions { buy_nft, create_ask, create_bid }, ctx) + } + + /// Create an unprotected new `Orderbook` + /// + /// To implement specific logic in your smart contract, you can toggle the + /// protection on specific actions. That will make them only accessible via + /// witness protected methods. + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is not an OriginByte policy. + public fun new_unprotected( + witness: DelegatedWitness, + transfer_policy: &TransferPolicy, + ctx: &mut TxContext + ): Orderbook { + new(witness, transfer_policy, false, false, false, ctx) + } + + /// Create a new `Orderbook` and immediately share it, returning + /// it's ID + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is not an OriginByte policy. + public fun create( + witness: DelegatedWitness, + transfer_policy: &TransferPolicy, + buy_nft: bool, + create_ask: bool, + create_bid: bool, + ctx: &mut TxContext, + ): ID { + let orderbook = new( + witness, transfer_policy, buy_nft, create_ask, create_bid, ctx, + ); + let orderbook_id = object::id(&orderbook); + transfer::share_object(orderbook); + orderbook_id + } + + /// Create a new unprotected `Orderbook` and immediately share it + /// returning it's ID + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is not an OriginByte policy. + public fun create_unprotected( + witness: DelegatedWitness, + transfer_policy: &TransferPolicy, + ctx: &mut TxContext + ): ID { + create(witness, transfer_policy, false, false, false, ctx) + } + + /// Create a new `Orderbook` and immediately share it + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is not an OriginByte policy. + public entry fun init_orderbook( + publisher: &Publisher, + transfer_policy: &TransferPolicy, + buy_nft: bool, + create_ask: bool, + create_bid: bool, + ctx: &mut TxContext, + ) { + create( + witness::from_publisher(publisher), + transfer_policy, + buy_nft, + create_ask, + create_bid, + ctx, + ); + } + + /// Create a new unprotected `Orderbook` and immediately share it + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is not an OriginByte policy. + public entry fun init_unprotected_orderbook( + publisher: &Publisher, + transfer_policy: &TransferPolicy, + ctx: &mut TxContext + ) { + create_unprotected( + witness::from_publisher(publisher), transfer_policy, ctx, + ); + } + + /// Create a new `Orderbook` for external `TransferPolicy` + /// + /// To implement specific logic in your smart contract, you can toggle the + /// protection on specific actions. That will make them only accessible via + /// witness protected methods. + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is an OriginByte policy. + public fun new_external( + transfer_policy: &TransferPolicy, + ctx: &mut TxContext + ): Orderbook { + assert!( + !transfer_request::is_originbyte(transfer_policy), + ENotExternalPolicy, + ); + + new_( + WitnessProtectedActions { + buy_nft: false, + create_ask: false, + create_bid: false, + }, + ctx, + ) + } + + /// Create a new `Orderbook` for external `TransferPolicy` and + /// immediately share it returning its ID + /// + /// To implement specific logic in your smart contract, you can toggle the + /// protection on specific actions. That will make them only accessible via + /// witness protected methods. + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is an OriginByte policy. + public fun create_external( + transfer_policy: &TransferPolicy, + ctx: &mut TxContext + ): ID { + let orderbook = new_external(transfer_policy, ctx); + let orderbook_id = object::id(&orderbook); + transfer::share_object(orderbook); + orderbook_id + } + + /// Create a new `Orderbook` for external `TransferPolicy` and + /// immediately share it + /// + /// To implement specific logic in your smart contract, you can toggle the + /// protection on specific actions. That will make them only accessible via + /// witness protected methods. + /// + /// #### Panics + /// + /// Panics if `TransferPolicy` is an OriginByte policy. + public entry fun init_external( + transfer_policy: &TransferPolicy, + ctx: &mut TxContext + ) { + create_external(transfer_policy, ctx); + } + + /// Create a new `Orderbook` + fun new_( + protected_actions: WitnessProtectedActions, + ctx: &mut TxContext, + ): Orderbook { + let id = object::new(ctx); + + event::emit(OrderbookCreatedEvent { + orderbook: object::uid_to_inner(&id), + nft_type: type_name::into_string(type_name::get()), + ft_type: type_name::into_string(type_name::get()), + }); + + Orderbook { + id, + version: VERSION, + tick_size: DEFAULT_TICK_SIZE, + protected_actions, + asks: critbit::new(ctx), + bids: critbit::new(ctx), + } + } + // === Create bid === /// How many (`price`) fungible tokens should be taken from sender's wallet @@ -390,13 +595,12 @@ module liquidity_layer::orderbook { ctx: &mut TxContext, ): TradeInfo { let is_matched_with_price = create_bid( - book, - buyer_kiosk, - max_price, - wallet, - ctx, + book, buyer_kiosk, max_price, wallet, ctx, + ); + assert!( + option::is_some(&is_matched_with_price), + EMarketOrderNotFilled, ); - assert!(option::is_some(&is_matched_with_price), EMarketOrderNotFilled); option::destroy_some(is_matched_with_price) } @@ -404,7 +608,7 @@ module liquidity_layer::orderbook { /// Cancel a bid owned by the sender at given price. If there are two bids /// with the same price, the one created later is cancelled. - public fun cancel_bid( + public entry fun cancel_bid( book: &mut Orderbook, bid_price_level: u64, wallet: &mut Coin, @@ -421,7 +625,7 @@ module liquidity_layer::orderbook { // // This API might be improved in future as we use a different data // structure for the orderbook. - public fun cancel_ask( + public entry fun cancel_ask( book: &mut Orderbook, seller_kiosk: &mut Kiosk, nft_price_level: u64, @@ -492,7 +696,12 @@ module liquidity_layer::orderbook { beneficiary, commission_ft, ); create_ask_( - book, seller_kiosk, requested_tokens, option::some(commission), nft_id, ctx + book, + seller_kiosk, + requested_tokens, + option::some(commission), + nft_id, + ctx, ) } @@ -518,7 +727,12 @@ module liquidity_layer::orderbook { commission_ft, ); create_ask_( - book, seller_kiosk, requested_tokens, option::some(commission), nft_id, ctx + book, + seller_kiosk, + requested_tokens, + option::some(commission), + nft_id, + ctx, ) } @@ -549,7 +763,10 @@ module liquidity_layer::orderbook { nft_id, ctx, ); - assert!(option::is_some(&is_matched_with_price), EMarketOrderNotFilled); + assert!( + option::is_some(&is_matched_with_price), + EMarketOrderNotFilled, + ); option::destroy_some(is_matched_with_price) } @@ -560,7 +777,7 @@ module liquidity_layer::orderbook { /// Firstly, we always emit `AskRemovedEvent` for the old ask. /// Then either `AskCreatedEvent` or `TradeFilledEvent`. /// Depends on whether the ask is filled immediately or not. - public fun edit_ask( + public entry fun edit_ask( book: &mut Orderbook, seller_kiosk: &mut Kiosk, old_price: u64, @@ -570,12 +787,14 @@ module liquidity_layer::orderbook { ) { assert!(!book.protected_actions.create_ask, EActionNotPublic); - let commission = cancel_ask_(book, seller_kiosk, old_price, nft_id, ctx); + let commission = cancel_ask_( + book, seller_kiosk, old_price, nft_id, ctx, + ); create_ask_(book, seller_kiosk, new_price, commission, nft_id, ctx); } /// Cancels the old bid and creates a new one with new price. - public fun edit_bid( + public entry fun edit_bid( book: &mut Orderbook, buyer_kiosk: &mut Kiosk, old_price: u64, @@ -670,183 +889,157 @@ module liquidity_layer::orderbook { trade_id: ID, seller_kiosk: &mut Kiosk, buyer_kiosk: &mut Kiosk, - ctx: &mut TxContext + ctx: &mut TxContext, ): Option> { - assert_version(book); - let t = trade(book, trade_id); - let kiosks_match = &t.seller_kiosk == &object::id(seller_kiosk) && &t.buyer_kiosk == &object::id(buyer_kiosk); + let kiosks_match = &t.seller_kiosk == &object::id(seller_kiosk) + && &t.buyer_kiosk == &object::id(buyer_kiosk); if (kiosks_match) { - option::some(finish_trade(book, trade_id, seller_kiosk, buyer_kiosk, ctx)) + option::some( + finish_trade(book, trade_id, seller_kiosk, buyer_kiosk, ctx), + ) } else { option::none() } } - // === Create orderbook === - - /// NFTs of type `T` to be traded, and `F`ungible `T`oken to be - /// quoted for an NFT in such a collection. - /// - /// By default, an orderbook has no restriction on actions, ie. all can be - /// called with public functions. - /// - /// To implement specific logic in your smart contract, you can toggle the - /// protection on specific actions. That will make them only accessible via - /// witness protected methods. - public fun new( - _witness: DelegatedWitness, - transfer_policy: &TransferPolicy, - protected_actions: WitnessProtectedActions, - ctx: &mut TxContext, - ): Orderbook { - assert!(transfer_request::is_originbyte(transfer_policy), ENotOriginBytePolicy); - - new_(protected_actions, ctx) - } - - /// Returns a new orderbook without any protection, ie. all endpoints can - /// be called as entry points. - public fun new_unprotected( - witness: DelegatedWitness, - transfer_policy: &TransferPolicy, - ctx: &mut TxContext - ): Orderbook { - new(witness, transfer_policy, no_protection(), ctx) - } - - public fun new_with_protected_actions( - witness: DelegatedWitness, - transfer_policy: &TransferPolicy, - protected_actions: WitnessProtectedActions, - ctx: &mut TxContext, - ): Orderbook { - new(witness, transfer_policy, protected_actions, ctx) - } - - /// Creates a new empty orderbook as a shared object. - /// - /// All actions can be called as entry points. - public fun create_unprotected( - witness: DelegatedWitness, - transfer_policy: &TransferPolicy, - ctx: &mut TxContext - ): ID { - let ob = new(witness, transfer_policy, no_protection(), ctx); - let ob_id = object::id(&ob); - share_object(ob); - ob_id - } - - public fun create_for_external( - transfer_policy: &TransferPolicy, - ctx: &mut TxContext - ): ID { - assert!(!transfer_request::is_originbyte(transfer_policy), ENotExternalPolicy); - let ob = new_(no_protection(), ctx); - let ob_id = object::id(&ob); - share_object(ob); - ob_id - } - - fun new_( - protected_actions: WitnessProtectedActions, - ctx: &mut TxContext, - ): Orderbook { - let id = object::new(ctx); - - event::emit(OrderbookCreatedEvent { - orderbook: object::uid_to_inner(&id), - nft_type: type_name::into_string(type_name::get()), - ft_type: type_name::into_string(type_name::get()), - }); + // === Manage orderbook === - Orderbook { - id, - version: VERSION, - tick_size: DEFAULT_TICK_SIZE, - protected_actions, - asks: crit_bit::empty(), - bids: crit_bit::empty(), - } + /// Change tick size of orderbook + /// + /// #### Panics + /// + /// Panics if provided `Publisher` did not publish type `T` + public entry fun change_tick_size( + publisher: &Publisher, + orderbook: &mut Orderbook, + tick_size: u64, + ) { + change_tick_size_with_witness( + witness::from_publisher(publisher), orderbook, tick_size, + ) } - public fun change_tick_size( + /// Change tick size of orderbook + public fun change_tick_size_with_witness( _witness: DelegatedWitness, - book: &mut Orderbook, - new_tick: u64, + orderbook: &mut Orderbook, + tick_size: u64, ) { - assert_version(book); - assert!(new_tick < book.tick_size, 0); - book.tick_size = new_tick; - } - - public fun share(ob: Orderbook) { - share_object(ob); - } - - /// Settings where all endpoints can be called as entry point functions. - public fun no_protection(): WitnessProtectedActions { - custom_protection(false, false, false) + assert!(tick_size < orderbook.tick_size, 0); + orderbook.tick_size = tick_size; } - - /// Select which actions are witness protected (true). - public fun custom_protection( + + /// Change protection level of an existing orderbook + /// + /// #### Panics + /// + /// Panics if provided `Publisher` did not publish type `T` + public entry fun set_protection( + publisher: &Publisher, + orderbook: &mut Orderbook, buy_nft: bool, create_ask: bool, create_bid: bool, - ): WitnessProtectedActions { - WitnessProtectedActions { + ) { + set_protection_with_witness( + witness::from_publisher(publisher), + orderbook, buy_nft, create_ask, create_bid, - } + ) } - /// Change protection level of an existing orderbook. - public fun set_protection( + /// Change protection level of an existing orderbook + public fun set_protection_with_witness( _witness: DelegatedWitness, - ob: &mut Orderbook, - protected_actions: WitnessProtectedActions, + orderbook: &mut Orderbook, + buy_nft: bool, + create_ask: bool, + create_bid: bool, + ) { + orderbook.protected_actions = WitnessProtectedActions { + buy_nft, create_ask, create_bid, + }; + } + + /// Helper method to protect all endpoints thus disabling trading + /// + /// #### Panics + /// + /// Panics if provided `Publisher` did not publish type `T` + public entry fun disable_trading( + publisher: &Publisher, + orderbook: &mut Orderbook, + ) { + set_protection_with_witness( + witness::from_publisher(publisher), orderbook, true, true, true, + ) + } + + /// Helper method to unprotect all endpoints thus enabling trading + /// + /// #### Panics + /// + /// Panics if provided `Publisher` did not publish type `T` + public entry fun enable_trading( + publisher: &Publisher, + orderbook: &mut Orderbook, ) { - assert_version(ob); - ob.protected_actions = protected_actions; + set_protection_with_witness( + witness::from_publisher(publisher), orderbook, false, false, false, + ) } // === Getters === public fun borrow_bids( book: &Orderbook, - ): &CBTree>> { &book.bids } + ): &CritbitTree>> { + &book.bids + } - public fun bid_offer(bid: &Bid): &Balance { &bid.offer } + public fun bid_offer(bid: &Bid): &Balance { + &bid.offer + } - public fun bid_owner(bid: &Bid): address { bid.owner } + public fun bid_owner(bid: &Bid): address { + bid.owner + } public fun borrow_asks( book: &Orderbook, - ): &CBTree> { &book.asks } - - public fun ask_price(ask: &Ask): u64 { ask.price } + ): &CritbitTree> { + &book.asks + } - public fun ask_owner(ask: &Ask): address { ask.owner } + public fun ask_price(ask: &Ask): u64 { + ask.price + } - public fun protected_actions( - book: &Orderbook, - ): &WitnessProtectedActions { &book.protected_actions } + public fun ask_owner(ask: &Ask): address { + ask.owner + } - public fun is_create_ask_protected( - protected_actions: &WitnessProtectedActions - ): bool { protected_actions.create_ask } + public fun is_create_ask_protected( + orderbook: &Orderbook, + ): bool { + orderbook.protected_actions.create_ask + } - public fun is_create_bid_protected( - protected_actions: &WitnessProtectedActions - ): bool { protected_actions.create_bid } + public fun is_create_bid_protected( + orderbook: &Orderbook, + ): bool { + orderbook.protected_actions.create_bid + } - public fun is_buy_nft_protected( - protected_actions: &WitnessProtectedActions - ): bool { protected_actions.buy_nft } + public fun is_buy_nft_protected( + orderbook: &Orderbook, + ): bool { + orderbook.protected_actions.buy_nft + } public fun trade_id(trade: &TradeInfo): ID { trade.trade_id @@ -856,10 +1049,11 @@ module liquidity_layer::orderbook { trade.trade_price } - public fun trade(book: &Orderbook, trade_id: ID): &TradeIntermediate { - df::borrow( - &book.id, TradeIntermediateDfKey { trade_id } - ) + public fun trade( + book: &Orderbook, + trade_id: ID, + ): &TradeIntermediate { + df::borrow(&book.id, TradeIntermediateDfKey { trade_id }) } // === Priv fns === @@ -882,6 +1076,7 @@ module liquidity_layer::orderbook { ): Option { assert_version(book); assert_tick_level(price, book.tick_size); + ob_kiosk::assert_is_ob_kiosk(buyer_kiosk); ob_kiosk::assert_permission(buyer_kiosk, ctx); ob_kiosk::assert_can_deposit_permissionlessly(buyer_kiosk); @@ -892,10 +1087,10 @@ module liquidity_layer::orderbook { let asks = &mut book.asks; // if map empty, then lowest ask price is 0 - let (can_be_filled, lowest_ask_price) = if (crit_bit::is_empty(asks)) { + let (can_be_filled, lowest_ask_price) = if (critbit::is_empty(asks)) { (false, 0) } else { - let lowest_ask_price = crit_bit::min_key(asks); + let (lowest_ask_price, _) = critbit::min_leaf(asks); (lowest_ask_price <= price, lowest_ask_price) }; @@ -935,16 +1130,16 @@ module liquidity_layer::orderbook { commission: bid_commission, }; - if (crit_bit::has_key(&book.bids, price)) { + let (has_key, price_level_idx) = critbit::find_leaf(&book.bids, price); + + if (has_key) { vector::push_back( - crit_bit::borrow_mut(&mut book.bids, price), + critbit::borrow_mut_leaf_by_index(&mut book.bids, price_level_idx), order ); } else { - crit_bit::insert( - &mut book.bids, - price, - vector::singleton(order), + critbit::insert_leaf( + &mut book.bids, price, vector::singleton(order), ); }; @@ -962,16 +1157,15 @@ module liquidity_layer::orderbook { ): ID { let asks = &mut book.asks; let buyer = tx_context::sender(ctx); - let price_level = crit_bit::borrow_mut(asks, lowest_ask_price); + let price_level = critbit::borrow_mut_leaf_by_key(asks, lowest_ask_price); + + // remove zeroth for FIFO, must exist due to `can_be_filled` + let ask = vector::remove(price_level, 0); - let ask = vector::remove( - price_level, - // remove zeroth for FIFO, must exist due to `can_be_filled` - 0, - ); if (vector::length(price_level) == 0) { // to simplify impl, always delete empty price level - vector::destroy_empty(crit_bit::pop(asks, lowest_ask_price)); + let price_level = critbit::remove_leaf_by_key(asks, lowest_ask_price); + vector::destroy_empty(price_level); }; let Ask { @@ -1033,16 +1227,15 @@ module liquidity_layer::orderbook { ): ID { let bids = &mut book.bids; let seller = tx_context::sender(ctx); - let price_level = crit_bit::borrow_mut(bids, highest_bid_price); + let price_level = critbit::borrow_mut_leaf_by_key(bids, highest_bid_price); - let bid = vector::remove( - price_level, - // remove zeroth for FIFO, must exist due to `can_be_filled` - 0, - ); + // remove zeroth for FIFO, must exist due to `can_be_filled` + let bid = vector::remove(price_level, 0); + if (vector::length(price_level) == 0) { // to simplify impl, always delete empty price level - vector::destroy_empty(crit_bit::pop(bids, highest_bid_price)); + let price_level = critbit::remove_leaf_by_key(bids, highest_bid_price); + vector::destroy_empty(price_level); }; let Bid { @@ -1102,11 +1295,15 @@ module liquidity_layer::orderbook { wallet: &mut Coin, ctx: &mut TxContext, ): Option> { + assert_version(book); + let sender = tx_context::sender(ctx); let bids = &mut book.bids; - assert!(crit_bit::has_key(bids, bid_price_level), EOrderDoesNotExist); - let price_level = crit_bit::borrow_mut(bids, bid_price_level); + let (has_key, price_level_idx) = critbit::find_leaf(bids, bid_price_level); + assert!(has_key, EOrderDoesNotExist); + + let price_level = critbit::borrow_mut_leaf_by_index(bids, price_level_idx); let index = 0; let bids_count = vector::length(price_level); @@ -1127,7 +1324,8 @@ module liquidity_layer::orderbook { if (vector::length(price_level) == 0) { // to simplify impl, always delete empty price level - vector::destroy_empty(crit_bit::pop(bids, bid_price_level)); + let price_level = critbit::remove_leaf_by_index(bids, price_level_idx); + vector::destroy_empty(price_level); }; event::emit(BidClosedEvent { @@ -1148,6 +1346,8 @@ module liquidity_layer::orderbook { wallet: &mut Coin, ctx: &mut TxContext, ) { + assert_version(book); + let commission = cancel_bid_except_commission(book, bid_price_level, wallet, ctx); @@ -1208,10 +1408,10 @@ module liquidity_layer::orderbook { let bids = &mut book.bids; // if map empty, then highest bid ask price is 0 - let (can_be_filled, highest_bid_price) = if (crit_bit::is_empty(bids)) { + let (can_be_filled, highest_bid_price) = if (critbit::is_empty(bids)) { (false, 0) } else { - let highest_bid_price = crit_bit::max_key(bids); + let (highest_bid_price, _) = critbit::max_leaf(bids); (highest_bid_price >= price, highest_bid_price) }; @@ -1249,12 +1449,15 @@ module liquidity_layer::orderbook { commission: ask_commission, }; // store the Ask object - if (crit_bit::has_key(&book.asks, price)) { + let (has_key, price_level_idx) = critbit::find_leaf(&book.asks, price); + + if (has_key) { vector::push_back( - crit_bit::borrow_mut(&mut book.asks, price), ask + critbit::borrow_mut_leaf_by_index(&mut book.asks, price_level_idx), + ask, ); } else { - crit_bit::insert(&mut book.asks, price, vector::singleton(ask)); + critbit::insert_leaf(&mut book.asks, price, vector::singleton(ask)); }; option::none() @@ -1269,6 +1472,7 @@ module liquidity_layer::orderbook { nft_id: ID, ctx: &mut TxContext, ): Option { + assert_version(book); let sender = tx_context::sender(ctx); let Ask { @@ -1303,6 +1507,7 @@ module liquidity_layer::orderbook { wallet: &mut Coin, ctx: &mut TxContext, ): TransferRequest { + assert_version(book); let buyer = tx_context::sender(ctx); let Ask { @@ -1355,6 +1560,7 @@ module liquidity_layer::orderbook { ctx: &mut TxContext, ): TransferRequest { assert_version(book); + let trade = df::remove( &mut book.id, TradeIntermediateDfKey { trade_id } ); @@ -1409,10 +1615,11 @@ module liquidity_layer::orderbook { /// Finds an ask of a given NFT advertized for the given price. Removes it /// from the asks vector preserving order and returns it. - fun remove_ask(asks: &mut CBTree>, price: u64, nft_id: ID): Ask { - assert!(crit_bit::has_key(asks, price), EOrderDoesNotExist); + fun remove_ask(asks: &mut CritbitTree>, price: u64, nft_id: ID): Ask { + let (has_key, price_level_idx) = critbit::find_leaf(asks, price); + assert!(has_key, EOrderDoesNotExist); - let price_level = crit_bit::borrow_mut(asks, price); + let price_level = critbit::borrow_mut_leaf_by_index(asks, price_level_idx); let index = 0; let asks_count = vector::length(price_level); @@ -1432,7 +1639,8 @@ module liquidity_layer::orderbook { if (vector::length(price_level) == 0) { // to simplify impl, always delete empty price level - vector::destroy_empty(crit_bit::pop(asks, price)); + let price_level = critbit::remove_leaf_by_index(asks, price_level_idx); + vector::destroy_empty(price_level); }; ask @@ -1454,14 +1662,16 @@ module liquidity_layer::orderbook { // Only the publisher of type `T` can upgrade entry fun migrate_as_creator( - self: &mut Orderbook, pub: &Publisher + self: &mut Orderbook, + pub: &Publisher, ) { assert!(package::from_package(pub), 0); self.version = VERSION; } entry fun migrate_as_pub( - self: &mut Orderbook, pub: &Publisher + self: &mut Orderbook, + pub: &Publisher, ) { assert!(package::from_package(pub), 0); self.version = VERSION; @@ -1528,6 +1738,5 @@ module liquidity_layer::orderbook { assert!(check_tick_level(777_777_777_777_777_777, DEFAULT_TICK_SIZE) == true, 0); assert!(check_tick_level(7_777_777_777_777_777_777, DEFAULT_TICK_SIZE) == true, 0); // assert!(check_tick_level(70_000_000_000_000_000_000, DEFAULT_TICK_SIZE) == true, 0); - } } diff --git a/tests/orderbook/depth.move b/tests/orderbook/depth.move index 9963f48..1f7f8f4 100644 --- a/tests/orderbook/depth.move +++ b/tests/orderbook/depth.move @@ -17,13 +17,10 @@ module liquidity_layer::test_orderbook_depth { use sui::test_scenario::{Self, ctx}; use ob_permissions::witness; - - use originmate::crit_bit_u64::{Self as crit_bit}; - use ob_kiosk::ob_kiosk; - use ob_request::transfer_request; + use critbit::critbit_u64 as critbit; use liquidity_layer::orderbook::{Self, Orderbook}; const OFFER_SUI: u64 = 100; @@ -42,8 +39,8 @@ module liquidity_layer::test_orderbook_depth { let dw = witness::from_witness(Witness {}); let ob = orderbook::new_unprotected(dw, policy, ctx); - orderbook::change_tick_size(dw, &mut ob, 1); - orderbook::share(ob); + orderbook::change_tick_size_with_witness(dw, &mut ob, 1); + transfer::public_share_object(ob); } #[test] @@ -66,7 +63,7 @@ module liquidity_layer::test_orderbook_depth { let book = test_scenario::take_shared>(&scenario); // We had one here to account for the first iteration - let price_levels = crit_bit::length(orderbook::borrow_asks(&book)) + 1; + let price_levels = critbit::size(orderbook::borrow_asks(&book)) + 1; let quantity = 100; let depth = 10; @@ -99,7 +96,7 @@ module liquidity_layer::test_orderbook_depth { ob_kiosk::assert_exclusively_listed(&mut seller_kiosk, nft_id); // 2. New price level gets added with new Ask - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == price_levels, 0); if (j == depth) { price_levels = price_levels + 1; @@ -110,17 +107,17 @@ module liquidity_layer::test_orderbook_depth { i = i - 1; }; - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 1)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 2)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 3)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 4)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 5)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 6)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 7)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 8)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 9)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 10)) == 10, 0); - assert!(crit_bit::has_key(orderbook::borrow_asks(&book), 11) == false, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 1)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 2)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 3)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 4)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 5)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 6)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 7)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 8)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 9)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 10)) == 10, 0); + assert!(critbit::has_leaf(orderbook::borrow_asks(&book), 11) == false, 0); test_scenario::next_tx(&mut scenario, BUYER); @@ -158,8 +155,8 @@ module liquidity_layer::test_orderbook_depth { }; // Assert that orderbook is empty - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -194,7 +191,7 @@ module liquidity_layer::test_orderbook_depth { let initial_funds = 1_000_000; let funds_locked = 0; - let price_levels = crit_bit::length(orderbook::borrow_bids(&book)) + 1; + let price_levels = critbit::size(orderbook::borrow_bids(&book)) + 1; let coin = coin::mint_for_testing(initial_funds, ctx(&mut scenario)); let quantity = 100; @@ -225,7 +222,7 @@ module liquidity_layer::test_orderbook_depth { assert!(coin::value(&coin) == initial_funds - funds_locked, 0); // 2. New price level gets added with new Bid - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); if (j == depth) { price_levels = price_levels + 1; @@ -236,17 +233,17 @@ module liquidity_layer::test_orderbook_depth { i = i - 1; }; - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 1)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 2)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 3)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 4)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 5)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 6)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 7)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 8)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 9)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 10)) == 10, 0); - assert!(crit_bit::has_key(orderbook::borrow_bids(&book), 11) == false, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 1)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 2)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 3)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 4)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 5)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 6)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 7)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 8)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 9)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 10)) == 10, 0); + assert!(critbit::has_leaf(orderbook::borrow_bids(&book), 11) == false, 0); test_scenario::next_tx(&mut scenario, SELLER); @@ -288,8 +285,8 @@ module liquidity_layer::test_orderbook_depth { }; // Assert that orderbook is empty - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -321,7 +318,7 @@ module liquidity_layer::test_orderbook_depth { let book = test_scenario::take_shared>(&scenario); // We had one here to account for the first iteration - let price_levels = crit_bit::length(orderbook::borrow_asks(&book)) + 1; + let price_levels = critbit::size(orderbook::borrow_asks(&book)) + 1; let quantity = 100; let depth = 10; @@ -358,7 +355,7 @@ module liquidity_layer::test_orderbook_depth { ob_kiosk::assert_exclusively_listed(&mut seller_kiosk, nft_id); // 2. New price level gets added with new Ask - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == price_levels, 0); if (j == depth) { price_levels = price_levels + 1; @@ -369,17 +366,17 @@ module liquidity_layer::test_orderbook_depth { i = i - 1; }; - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 1)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 2)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 3)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 4)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 5)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 6)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 7)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 8)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 9)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_asks(&book), 10)) == 10, 0); - assert!(crit_bit::has_key(orderbook::borrow_asks(&book), 11) == false, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 1)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 2)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 3)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 4)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 5)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 6)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 7)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 8)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 9)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_asks(&book), 10)) == 10, 0); + assert!(critbit::has_leaf(orderbook::borrow_asks(&book), 11) == false, 0); let i = quantity; let price = 10; @@ -410,7 +407,7 @@ module liquidity_layer::test_orderbook_depth { }; // Assert that orderbook state - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); // coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -445,7 +442,7 @@ module liquidity_layer::test_orderbook_depth { let initial_funds = 1_000_000; let funds_locked = 0; - let price_levels = crit_bit::length(orderbook::borrow_bids(&book)) + 1; + let price_levels = critbit::size(orderbook::borrow_bids(&book)) + 1; let coin = coin::mint_for_testing(initial_funds, ctx(&mut scenario)); let quantity = 100; @@ -476,7 +473,7 @@ module liquidity_layer::test_orderbook_depth { assert!(coin::value(&coin) == initial_funds - funds_locked, 0); // 2. New price level gets added with new Bid - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); if (j == depth) { price_levels = price_levels + 1; @@ -487,17 +484,17 @@ module liquidity_layer::test_orderbook_depth { i = i - 1; }; - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 1)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 2)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 3)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 4)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 5)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 6)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 7)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 8)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 9)) == 10, 0); - assert!(vector::length(crit_bit::borrow(orderbook::borrow_bids(&book), 10)) == 10, 0); - assert!(crit_bit::has_key(orderbook::borrow_bids(&book), 11) == false, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 1)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 2)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 3)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 4)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 5)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 6)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 7)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 8)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 9)) == 10, 0); + assert!(vector::length(critbit::borrow_leaf_by_key(orderbook::borrow_bids(&book), 10)) == 10, 0); + assert!(critbit::has_leaf(orderbook::borrow_bids(&book), 11) == false, 0); let i = quantity; let price = 10; @@ -526,7 +523,7 @@ module liquidity_layer::test_orderbook_depth { }; // Assert that orderbook state - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -588,9 +585,11 @@ module liquidity_layer::test_orderbook_depth { }; // Assert that orderbook state - assert!(crit_bit::max_key(orderbook::borrow_asks(&book)) == 300, 0); - assert!(crit_bit::min_key(orderbook::borrow_asks(&book)) == 1, 0); - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == 300, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_asks(&book)); + assert!(max_key == 300, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_asks(&book)); + assert!(min_key == 1, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == 300, 0); let i = quantity; let price = 1; @@ -615,10 +614,12 @@ module liquidity_layer::test_orderbook_depth { // Assert that orderbook state // All orders are concentrated into one price level - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == 1, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == 1, 0); - assert!(crit_bit::max_key(orderbook::borrow_asks(&book)) == 500, 0); - assert!(crit_bit::min_key(orderbook::borrow_asks(&book)) == 500, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_asks(&book)); + assert!(max_key == 500, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_asks(&book)); + assert!(min_key == 500, 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); diff --git a/tests/orderbook/orderbook.move b/tests/orderbook/orderbook.move index c222252..37e1865 100644 --- a/tests/orderbook/orderbook.move +++ b/tests/orderbook/orderbook.move @@ -19,12 +19,13 @@ module liquidity_layer::test_orderbook { // fun it_fails_if_buyer_safe_eq_seller_safe_with_generic_collection() // fun it_fails_if_buyer_safe_eq_seller_safe_with_generic_collection() { use ob_permissions::witness; - use originmate::crit_bit_u64::{Self as crit_bit}; use ob_request::transfer_request; use ob_kiosk::ob_kiosk::{Self, OwnerToken}; use liquidity_layer::orderbook::{Self, Orderbook}; + use critbit::critbit_u64 as critbit; + const OFFER_SUI: u64 = 100; const CREATOR: address = @0xA1C05; @@ -41,8 +42,8 @@ module liquidity_layer::test_orderbook { let dw = witness::from_witness(Witness {}); let ob = orderbook::new_unprotected(dw, policy, ctx); - orderbook::change_tick_size(dw, &mut ob, 1); - orderbook::share(ob); + orderbook::change_tick_size_with_witness(dw, &mut ob, 1); + transfer::public_share_object(ob); } #[test] @@ -96,12 +97,12 @@ module liquidity_layer::test_orderbook { // to OriginByte, in other words, if the creator did not use OriginByte // transfer_request module to initiate the policy or never added OriginByte // rules to the policy. - orderbook::create_for_external( + orderbook::init_external( &transfer_policy, ctx(&mut scenario), ); // When this is the case, anyone can come in a create an orderbook - orderbook::create_for_external( + orderbook::init_external( &transfer_policy, ctx(&mut scenario), ); @@ -121,7 +122,7 @@ module liquidity_layer::test_orderbook { let (transfer_policy, policy_cap) = transfer_request::init_policy(&publisher, ctx(&mut scenario)); // When this is the case, anyone can come in a create an orderbook - orderbook::create_for_external( + orderbook::init_external( &transfer_policy, ctx(&mut scenario), ); @@ -208,7 +209,7 @@ module liquidity_layer::test_orderbook { let publisher = package::test_claim(Witness {}, ctx(&mut scenario)); let (tx_policy, policy_cap) = transfer_policy::new(&publisher, ctx(&mut scenario)); - orderbook::create_for_external(&tx_policy, ctx(&mut scenario)); + orderbook::init_external(&tx_policy, ctx(&mut scenario)); // 2. Create Kiosks let (buyer_kiosk, _) = ob_kiosk::new_for_address(BUYER, ctx(&mut scenario)); @@ -277,7 +278,7 @@ module liquidity_layer::test_orderbook { let publisher = package::test_claim(Witness {}, ctx(&mut scenario)); let (tx_policy, policy_cap) = transfer_policy::new(&publisher, ctx(&mut scenario)); - orderbook::create_for_external(&tx_policy, ctx(&mut scenario)); + orderbook::init_external(&tx_policy, ctx(&mut scenario)); // 2. Create Kiosks test_scenario::next_tx(&mut scenario, BUYER); @@ -370,7 +371,7 @@ module liquidity_layer::test_orderbook { let book = test_scenario::take_shared>(&mut scenario); - let price_levels = crit_bit::length(orderbook::borrow_asks(&book)); + let price_levels = critbit::size(orderbook::borrow_asks(&book)); let quantity = 300; let i = quantity; @@ -398,7 +399,7 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Ask price_levels = price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == price_levels, 0); i = i - 1; price = price + 1; @@ -430,8 +431,8 @@ module liquidity_layer::test_orderbook { }; // Assert that orderbook is empty - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -463,7 +464,7 @@ module liquidity_layer::test_orderbook { let book = test_scenario::take_shared>(&mut scenario); let initial_funds = 1_000_000; - let price_levels = crit_bit::length(orderbook::borrow_bids(&book)); + let price_levels = critbit::size(orderbook::borrow_bids(&book)); let funds_locked = 0; let coin = coin::mint_for_testing(initial_funds, ctx(&mut scenario)); @@ -492,7 +493,7 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Bid price_levels = price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); price = price + 1; i = i - 1; @@ -529,8 +530,8 @@ module liquidity_layer::test_orderbook { }; // Assert that orderbook is empty - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -561,7 +562,7 @@ module liquidity_layer::test_orderbook { let book = test_scenario::take_shared>(&mut scenario); - let price_levels = crit_bit::length(orderbook::borrow_asks(&book)); + let price_levels = critbit::size(orderbook::borrow_asks(&book)); let quantity = 300; let i = quantity; @@ -589,7 +590,7 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Ask price_levels = price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == price_levels, 0); i = i - 1; price = price + 1; @@ -626,7 +627,7 @@ module liquidity_layer::test_orderbook { // 2. Ask gets popped and price level removed price_levels = price_levels - 1; - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == price_levels, 0); // 3. Assert trade match let trade_info = option::extract(&mut trade_info_opt); @@ -638,8 +639,8 @@ module liquidity_layer::test_orderbook { }; // Assert that orderbook is empty - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -671,7 +672,7 @@ module liquidity_layer::test_orderbook { let book = test_scenario::take_shared>(&mut scenario); let initial_funds = 1_000_000; - let bid_price_levels = crit_bit::length(orderbook::borrow_bids(&book)); + let bid_price_levels = critbit::size(orderbook::borrow_bids(&book)); let funds_locked = 0; let coin = coin::mint_for_testing(1_000_000, ctx(&mut scenario)); @@ -700,7 +701,7 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Bid bid_price_levels = bid_price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == bid_price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == bid_price_levels, 0); price = price + 1; i = i - 1; @@ -709,7 +710,7 @@ module liquidity_layer::test_orderbook { test_scenario::next_tx(&mut scenario, BUYER); // Seller gets best price (highest) - let ask_price_levels = crit_bit::length(orderbook::borrow_asks(&book)); + let ask_price_levels = critbit::size(orderbook::borrow_asks(&book)); let price = 301; let i = quantity; @@ -737,17 +738,22 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Ask ask_price_levels = ask_price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == ask_price_levels, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == ask_price_levels, 0); i = i - 1; price = price + 1; }; // Assert orderbook state - assert!(crit_bit::max_key(orderbook::borrow_bids(&book)) == 300, 0); - assert!(crit_bit::min_key(orderbook::borrow_bids(&book)) == 1, 0); - assert!(crit_bit::max_key(orderbook::borrow_asks(&book)) == 600, 0); - assert!(crit_bit::min_key(orderbook::borrow_asks(&book)) == 301, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_bids(&book)); + assert!(max_key == 300, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_bids(&book)); + assert!(min_key == 1, 0); + + let (max_key, _) = critbit::max_leaf(orderbook::borrow_asks(&book)); + assert!(max_key == 600, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_asks(&book)); + assert!(min_key == 301, 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -809,8 +815,10 @@ module liquidity_layer::test_orderbook { }; // Assert that orderbook state - assert!(crit_bit::max_key(orderbook::borrow_asks(&book)) == 300, 0); - assert!(crit_bit::min_key(orderbook::borrow_asks(&book)) == 1, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_asks(&book)); + assert!(max_key == 300, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_asks(&book)); + assert!(min_key == 1, 0); let i = quantity; let price = 1; @@ -833,7 +841,7 @@ module liquidity_layer::test_orderbook { }; // Assert that orderbook state - assert!(crit_bit::is_empty(orderbook::borrow_asks(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_asks(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -865,7 +873,7 @@ module liquidity_layer::test_orderbook { let book = test_scenario::take_shared>(&mut scenario); let initial_funds = 1_000_000; - let price_levels = crit_bit::length(orderbook::borrow_bids(&book)); + let price_levels = critbit::size(orderbook::borrow_bids(&book)); let funds_locked = 0; let coin = coin::mint_for_testing(initial_funds, ctx(&mut scenario)); @@ -894,15 +902,17 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Bid price_levels = price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); price = price + 1; i = i - 1; }; // Assert that orderbook state - assert!(crit_bit::max_key(orderbook::borrow_bids(&book)) == 300, 0); - assert!(crit_bit::min_key(orderbook::borrow_bids(&book)) == 1, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_bids(&book)); + assert!(max_key == 300, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_bids(&book)); + assert!(min_key == 1, 0); let i = quantity; let price = 1; @@ -927,14 +937,14 @@ module liquidity_layer::test_orderbook { // 2. New price level gets removed with Bid popped price_levels = price_levels - 1; - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); price = price + 1; i = i - 1; }; // Assert that orderbook state - assert!(crit_bit::is_empty(orderbook::borrow_bids(&book)), 0); + assert!(critbit::is_empty(orderbook::borrow_bids(&book)), 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -996,9 +1006,12 @@ module liquidity_layer::test_orderbook { }; // Assert that orderbook state - assert!(crit_bit::max_key(orderbook::borrow_asks(&book)) == 300, 0); - assert!(crit_bit::min_key(orderbook::borrow_asks(&book)) == 1, 0); - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == 300, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == 300, 0); + + let (max_key, _) = critbit::max_leaf(orderbook::borrow_asks(&book)); + assert!(max_key == 300, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_asks(&book)); + assert!(min_key == 1, 0); let i = quantity; let price = 1; @@ -1023,10 +1036,12 @@ module liquidity_layer::test_orderbook { // Assert that orderbook state // All orders are concentrated into one price level - assert!(crit_bit::length(orderbook::borrow_asks(&book)) == 1, 0); + assert!(critbit::size(orderbook::borrow_asks(&book)) == 1, 0); - assert!(crit_bit::max_key(orderbook::borrow_asks(&book)) == 500, 0); - assert!(crit_bit::min_key(orderbook::borrow_asks(&book)) == 500, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_asks(&book)); + assert!(max_key == 500, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_asks(&book)); + assert!(min_key == 500, 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR); @@ -1058,7 +1073,7 @@ module liquidity_layer::test_orderbook { let book = test_scenario::take_shared>(&mut scenario); let initial_funds = 1_000_000; - let price_levels = crit_bit::length(orderbook::borrow_bids(&book)); + let price_levels = critbit::size(orderbook::borrow_bids(&book)); let funds_locked = 0; let coin = coin::mint_for_testing(initial_funds, ctx(&mut scenario)); @@ -1087,15 +1102,17 @@ module liquidity_layer::test_orderbook { // 2. New price level gets added with new Bid price_levels = price_levels + 1; - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); price = price + 1; i = i - 1; }; - // Assert that orderbook state - assert!(crit_bit::max_key(orderbook::borrow_bids(&book)) == 300, 0); - assert!(crit_bit::min_key(orderbook::borrow_bids(&book)) == 1, 0); + // Assert orderbook state + let (max_key, _) = critbit::max_leaf(orderbook::borrow_bids(&book)); + assert!(max_key == 300, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_bids(&book)); + assert!(min_key == 1, 0); let i = quantity; let price = 1; @@ -1124,7 +1141,7 @@ module liquidity_layer::test_orderbook { // price level - In the first iteration the length does not really change because // we are just swapping one price level for another. price_levels = if (i == quantity) {price_levels} else {price_levels - 1}; - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == price_levels, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == price_levels, 0); price = price + 1; i = i - 1; @@ -1132,10 +1149,12 @@ module liquidity_layer::test_orderbook { // Assert orderbook state // All orders are concentrated into one price level - assert!(crit_bit::length(orderbook::borrow_bids(&book)) == 1, 0); + assert!(critbit::size(orderbook::borrow_bids(&book)) == 1, 0); - assert!(crit_bit::max_key(orderbook::borrow_bids(&book)) == 500, 0); - assert!(crit_bit::min_key(orderbook::borrow_bids(&book)) == 500, 0); + let (max_key, _) = critbit::max_leaf(orderbook::borrow_bids(&book)); + assert!(max_key == 500, 0); + let (min_key, _) = critbit::min_leaf(orderbook::borrow_bids(&book)); + assert!(min_key == 500, 0); coin::burn_for_testing(coin); transfer::public_transfer(publisher, CREATOR);