/
user.move
5874 lines (5685 loc) · 240 KB
/
user.move
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// User-side asset, collateral, and order management.
///
/// Contains data structures and functionality for tracking a user's
/// assets and open orders. Upon market account registration, users can
/// either preside over their own account, or delegate custody to a
/// custodian who manage their orders and withdrawals. For each market,
/// a user can open multiple market accounts, each with a unique
/// custodian.
///
/// # General overview sections
///
/// [Architecture](#architecture)
///
/// * [Market account IDs](#market-account-IDs)
/// * [Market accounts](#market-accounts)
/// * [Orders and access keys](#orders-and-access-keys)
/// * [Market order IDs](#market-order-IDs)
///
/// [Function index](#function-index)
///
/// * [View functions](#view-functions)
/// * [Public functions](#public-functions)
/// * [Public entry functions](#public-entry-functions)
/// * [Public friend functions](#public-friend-functions)
/// * [Dependency charts](#dependency-charts)
///
/// [Complete DocGen index](#complete-docgen-index)
///
/// # Architecture
///
/// ## Market account IDs
///
/// Markets, defined in the global registry, are assigned a 1-indexed
/// `u64` market ID, as are custodians. The concatenated result of a
/// market ID and a custodian ID is known as a market account ID, which
/// is used as a key in assorted user-side lookup operations: the 64
/// least-significant bits in a market account ID are the custodian ID
/// for the given market account (`NIL` if no delegated custodian),
/// while the 64 most-significant bits are the market ID. See
/// `get_custodian_id()`, `get_market_account_id()`, and
/// `get_market_id()` for implementation details.
///
/// ## Market accounts
///
/// When a user opens a market account, a `MarketAccount` entry is
/// added to their `MarketAccounts`, and a coin entry is added to their
/// `Collateral` for the given market's quote coin type. If the market's
/// base asset is a coin, a `Collateral` entry is similarly created for
/// the base coin type.
///
/// ## Orders and access keys
///
/// When users place an order on the order book, an `Order` is added to
/// their corresponding `MarketAccount`. If they then cancel the order,
/// the corresponding `Order` is not deallocated, but rather, marked
/// "inactive" and pushed onto a stack of inactive orders for the
/// corresponding side (`MarketAccount.asks_stack_top` or
/// `MarketAccount.bids_stack_top`). Then, when a user places another
/// order, rather than allocating a new `Order`, the inactive order at
/// the top of the stack is popped off the stack and marked active.
///
/// This approach is motivated by global storage gas costs: as of the
/// time of this writing, per-item creations cost approximately 16.7
/// times as much as per-item writes, and there is no incentive to
/// deallocate from memory. Hence the inactive stack paradigm allows
/// for orders to be recycled in a way that reduces overall storage
/// costs. In practice, however, this means that each `Order` is
/// assigned a static "access key" that persists throughout subsequent
/// active order states: if a user places an order, cancels the order,
/// then places another order, the `Order` will have the same access key
/// in each active instance. In other words, access keys are the lookup
/// ID in the relevant `Order` data structure for the given side
/// (`MarketAccount.asks` or `MarketAccount.bids`), and are not
/// necessarily unique for orders across time.
///
/// ## Market order IDs
///
/// Market order IDs, however, are unique across time for a given market
/// ID, and are tracked in a users' `Order.market_order_id`. A market
/// order ID is a unique identifier for an order on a given order book.
///
/// # Function index
///
/// ## View functions
///
/// Constant getters:
///
/// * `get_ASK()`
/// * `get_BID()`
/// * `get_CANCEL_REASON_EVICTION()`
/// * `get_CANCEL_REASON_IMMEDIATE_OR_CANCEL()`
/// * `get_CANCEL_REASON_MANUAL_CANCEL()`
/// * `get_CANCEL_REASON_MAX_QUOTE_TRADED()`
/// * `get_CANCEL_REASON_NOT_ENOUGH_LIQUIDITY()`
/// * `get_CANCEL_REASON_SELF_MATCH_MAKER()`
/// * `get_CANCEL_REASON_SELF_MATCH_TAKER()`
/// * `get_CANCEL_REASON_TOO_SMALL_TO_FILL_LOT()`
/// * `get_CANCEL_REASON_VIOLATED_LIMIT_PRICE()`
/// * `get_NO_CUSTODIAN()`
///
/// Market account lookup:
///
/// * `get_all_market_account_ids_for_market_id()`
/// * `get_all_market_account_ids_for_user()`
/// * `get_market_account()`
/// * `get_market_accounts()`
/// * `get_market_event_handle_creation_numbers()`
/// * `has_market_account()`
/// * `has_market_account_by_market_account_id()`
/// * `has_market_account_by_market_id()`
///
/// Market account ID lookup:
///
/// * `get_custodian_id()`
/// * `get_market_account_id()`
/// * `get_market_id()`
///
/// ## Public functions
///
/// Market account lookup
///
/// * `get_asset_counts_custodian()`
/// * `get_asset_counts_user()`
/// * `get_market_account_market_info_custodian()`
/// * `get_market_account_market_info_user()`
///
/// Asset transfer:
///
/// * `deposit_coins()`
/// * `deposit_generic_asset()`
/// * `withdraw_coins_custodian()`
/// * `withdraw_coins_user()`
/// * `withdraw_generic_asset_custodian()`
/// * `withdraw_generic_asset_user()`
///
/// ## Public entry functions
///
/// Asset transfer:
///
/// * `deposit_from_coinstore()`
/// * `withdraw_to_coinstore()`
///
/// Account registration:
///
/// * `init_market_event_handles_if_missing()`
/// * `register_market_account()`
/// * `register_market_account_generic_base()`
///
/// ## Public friend functions
///
/// Order management:
///
/// * `cancel_order_internal()`
/// * `change_order_size_internal()`
/// * `get_open_order_id_internal()`
/// * `fill_order_internal()`
/// * `place_order_internal()`
///
/// Asset management:
///
/// * `deposit_assets_internal()`
/// * `get_asset_counts_internal()`
/// * `withdraw_assets_internal()`
///
/// Order identifiers:
///
/// * `get_next_order_access_key_internal()`
/// * `get_active_market_order_ids_internal()`
///
/// Market events:
///
/// * `create_cancel_order_event_internal()`
/// * `create_fill_event_internal()`
/// * `emit_limit_order_events_internal()`
/// * `emit_market_order_events_internal()`
/// * `emit_swap_maker_fill_events_internal()`
///
/// ## Dependency charts
///
/// The below dependency charts use `mermaid.js` syntax, which can be
/// automatically rendered into a diagram (depending on the browser)
/// when viewing the documentation file generated from source code. If
/// a browser renders the diagrams with coloring that makes it difficult
/// to read, try a different browser.
///
/// Deposits:
///
/// ```mermaid
///
/// flowchart LR
///
/// deposit_coins --> deposit_asset
///
/// deposit_from_coinstore --> deposit_coins
///
/// deposit_assets_internal --> deposit_asset
/// deposit_assets_internal --> deposit_coins
///
/// deposit_generic_asset --> deposit_asset
/// deposit_generic_asset --> registry::get_underwriter_id
///
/// ```
///
/// Withdrawals:
///
/// ```mermaid
///
/// flowchart LR
///
/// withdraw_generic_asset_user --> withdraw_generic_asset
///
/// withdraw_generic_asset_custodian --> withdraw_generic_asset
/// withdraw_generic_asset_custodian --> registry::get_custodian_id
///
/// withdraw_coins_custodian --> withdraw_coins
/// withdraw_coins_custodian --> registry::get_custodian_id
///
/// withdraw_coins_user --> withdraw_coins
///
/// withdraw_to_coinstore --> withdraw_coins_user
///
/// withdraw_generic_asset --> withdraw_asset
/// withdraw_generic_asset --> registry::get_underwriter_id
///
/// withdraw_coins --> withdraw_asset
///
/// withdraw_assets_internal --> withdraw_asset
/// withdraw_assets_internal --> withdraw_coins
///
/// ```
///
/// Market account lookup:
///
/// ```mermaid
///
/// flowchart LR
///
/// get_asset_counts_user --> get_asset_counts_internal
///
/// get_asset_counts_custodian --> get_asset_counts_internal
/// get_asset_counts_custodian --> registry::get_custodian_id
///
/// get_market_account_market_info_custodian -->
/// get_market_account_market_info
/// get_market_account_market_info_custodian -->
/// registry::get_custodian_id
///
/// get_market_account_market_info_user -->
/// get_market_account_market_info
///
/// get_market_accounts --> get_all_market_account_ids_for_user
/// get_market_accounts --> get_market_id
/// get_market_accounts --> get_custodian_id
/// get_market_accounts --> get_market_account
///
/// get_market_account --> get_market_account_id
/// get_market_account --> has_market_account_by_market_account_id
/// get_market_account --> vectorize_open_orders
///
/// get_open_order_id_internal --> get_market_account_id
/// get_open_order_id_internal -->
/// has_market_account_by_market_account_id
///
/// has_market_account --> has_market_account_by_market_account_id
/// has_market_account --> get_market_account_id
///
/// get_market_event_handle_creation_numbers --> get_market_account_id
///
/// ```
///
/// Market account registration:
///
/// ```mermaid
///
/// flowchart LR
///
/// register_market_account --> registry::is_registered_custodian_id
/// register_market_account --> register_market_account_account_entries
/// register_market_account --> register_market_account_collateral_entry
/// register_market_account --> init_market_event_handles_if_missing
///
/// register_market_account_generic_base --> register_market_account
///
/// register_market_account_account_entries -->
/// registry::get_market_info_for_market_account
///
/// init_market_event_handles_if_missing --> has_market_account
///
/// ```
///
/// Internal order management:
///
/// ```mermaid
///
/// flowchart LR
///
/// change_order_size_internal --> cancel_order_internal
/// change_order_size_internal --> place_order_internal
///
/// ```
///
/// Market events:
///
/// ```mermaid
///
/// flowchart LR
///
/// emit_limit_order_events_internal --> emit_maker_fill_event
/// emit_market_order_events_internal --> emit_maker_fill_event
/// emit_swap_maker_fill_events_internal --> emit_maker_fill_event
///
/// ```
///
/// # Complete DocGen index
///
/// The below index is automatically generated from source code:
module econia::user {
// Uses >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
use aptos_framework::account;
use aptos_framework::coin::{Self, Coin};
use aptos_framework::guid;
use aptos_framework::table::{Self, Table};
use aptos_framework::event::{Self, EventHandle};
use aptos_framework::type_info::{Self, TypeInfo};
use econia::tablist::{Self, Tablist};
use econia::registry::{
Self, CustodianCapability, GenericAsset, UnderwriterCapability};
use std::option::{Self, Option};
use std::signer::address_of;
use std::string::String;
use std::vector;
// Uses <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Friends >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
friend econia::market;
// Friends <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Test-only uses >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#[test_only]
use econia::avl_queue::{u_128_by_32, u_64_by_32};
#[test_only]
use econia::assets::{Self, BC, QC, UC};
#[test_only]
use std::string;
// Test-only uses <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Structs >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/// Emitted when an order is cancelled.
struct CancelOrderEvent has copy, drop, store {
/// Market ID for order.
market_id: u64,
/// Unique ID for order within market.
order_id: u128,
/// User for market account that placed order.
user: address,
/// Custodian ID for market account that placed order.
custodian_id: u64,
/// Reason for the cancel, for example
/// `CANCEL_REASON_MANUAL_CANCEL`.
reason: u8
}
/// Emitted when the size of an open order is manually changed.
struct ChangeOrderSizeEvent has copy, drop, store {
/// Market ID for order.
market_id: u64,
/// Unique ID for order within market.
order_id: u128,
/// User for market account that placed order.
user: address,
/// Custodian ID for market account that placed order.
custodian_id: u64,
/// `ASK` or `BID`.
side: bool,
/// Order size after manual size change operation.
new_size: u64
}
/// All of a user's collateral across all market accounts.
struct Collateral<phantom CoinType> has key {
/// Map from market account ID to collateral for market account.
/// Separated into different table entries to reduce transaction
/// collisions across markets. Enables off-chain iterated
/// indexing by market account ID.
map: Tablist<u128, Coin<CoinType>>
}
/// Emitted when one order fills against another.
struct FillEvent has copy, drop, store {
/// Market ID for fill.
market_id: u64,
/// Amount filled, in lots.
size: u64,
/// Fill price, in ticks per lot.
price: u64,
/// `ASK` or `BID`, the side of the maker order.
maker_side: bool,
/// User address associated with market account for maker.
maker: address,
/// Custodian ID associated with market account for maker.
maker_custodian_id: u64,
/// Order ID for maker, unique within the market.
maker_order_id: u128,
/// User address associated with market account for taker.
taker: address,
/// Custodian ID associated with market account for taker.
taker_custodian_id: u64,
/// Order ID for taker, unique within the market.
taker_order_id: u128,
/// Amount of fees paid by taker on the fill, in indivisible
/// quote subunits.
taker_quote_fees_paid: u64,
/// Sequence number (0-indexed) of fill within a single trade,
/// which may have more than one fill. For example if a market
/// order results in two fills, the first will have sequence
/// number 0 and the second will have sequence number 1.
sequence_number_for_trade: u64
}
/// Represents a user's open orders and asset counts for a given
/// market account ID. Contains `registry::MarketInfo` field
/// duplicates to reduce global storage item queries against the
/// registry.
struct MarketAccount has store {
/// `registry::MarketInfo.base_type`.
base_type: TypeInfo,
/// `registry::MarketInfo.base_name_generic`.
base_name_generic: String,
/// `registry::MarketInfo.quote_type`.
quote_type: TypeInfo,
/// `registry::MarketInfo.lot_size`.
lot_size: u64,
/// `registry::MarketInfo.tick_size`.
tick_size: u64,
/// `registry::MarketInfo.min_size`.
min_size: u64,
/// `registry::MarketInfo.underwriter_id`.
underwriter_id: u64,
/// Map from order access key to open ask order.
asks: Tablist<u64, Order>,
/// Map from order access key to open bid order.
bids: Tablist<u64, Order>,
/// Access key of ask order at top of inactive stack, if any.
asks_stack_top: u64,
/// Access key of bid order at top of inactive stack, if any.
bids_stack_top: u64,
/// Total base asset units held as collateral.
base_total: u64,
/// Base asset units available to withdraw.
base_available: u64,
/// Amount `base_total` will increase to if all open bids fill.
base_ceiling: u64,
/// Total quote asset units held as collateral.
quote_total: u64,
/// Quote asset units available to withdraw.
quote_available: u64,
/// Amount `quote_total` will increase to if all open asks fill.
quote_ceiling: u64
}
/// User-friendly market account view function return.
struct MarketAccountView has store {
/// Market ID for given market account.
market_id: u64,
/// Custodian ID for given market account.
custodian_id: u64,
/// All open asks.
asks: vector<Order>,
/// All open bids.
bids: vector<Order>,
/// `MarketAccount.base_total`.
base_total: u64,
/// `MarketAccount.base_available`.
base_available: u64,
/// `MarketAccount.base_ceiling`.
base_ceiling: u64,
/// `MarketAccount.quote_total`.
quote_total: u64,
/// `MarketAccount.quote_available`.
quote_available: u64,
/// `MarketAccount.quote_ceiling`.
quote_ceiling: u64
}
/// All of a user's market accounts.
struct MarketAccounts has key {
/// Map from market account ID to `MarketAccount`.
map: Table<u128, MarketAccount>,
/// Map from market ID to vector of custodian IDs for which
/// a market account has been registered on the given market.
/// Enables off-chain iterated indexing by market account ID and
/// assorted on-chain queries.
custodians: Tablist<u64, vector<u64>>
}
/// View function return for getting event handle creation numbers
/// of a particular `MarketEventHandlesForMarketAccount`.
struct MarketEventHandleCreationNumbers has copy, drop {
/// Creation number of `cancel_order_events` handle in a
/// `MarketEventHandlesForMarketAccount`.
cancel_order_events_handle_creation_num: u64,
/// Creation number of `change_order_size_events` handle in a
/// `MarketEventHandlesForMarketAccount`.
change_order_size_events_handle_creation_num: u64,
/// Creation number of `fill_events` handle in a
/// `MarketEventHandlesForMarketAccount`.
fill_events_handle_creation_num: u64,
/// Creation number of `place_limit_order_events` handle in a
/// `MarketEventHandlesForMarketAccount`.
place_limit_order_events_handle_creation_num: u64,
/// Creation number of `place_market_order_events` handle in a
/// `MarketEventHandlesForMarketAccount`.
place_market_order_events_handle_creation_num: u64
}
/// All of a user's `MarketEventHandlesForMarketAccount`.
struct MarketEventHandles has key {
/// Map from market account ID to
/// `MarketEventHandlesForMarketAccount`.
map: Table<u128, MarketEventHandlesForMarketAccount>
}
/// Event handles for market events within a unique market account.
struct MarketEventHandlesForMarketAccount has store {
/// Event handle for `CancelOrderEvent`s.
cancel_order_events: EventHandle<CancelOrderEvent>,
/// Event handle for `ChangeOrderSizeEvent`s.
change_order_size_events: EventHandle<ChangeOrderSizeEvent>,
/// Event handle for `FillEvent`s.
fill_events: EventHandle<FillEvent>,
/// Event handle for `PlaceLimitOrderEvent`s.
place_limit_order_events: EventHandle<PlaceLimitOrderEvent>,
/// Event handle for `PlaceMarketOrderEvent`s.
place_market_order_events: EventHandle<PlaceMarketOrderEvent>
}
/// An open order, either ask or bid.
struct Order has store {
/// Market order ID. `NIL` if inactive.
market_order_id: u128,
/// Order size left to fill, in lots. When `market_order_id` is
/// `NIL`, indicates access key of next inactive order in stack.
size: u64
}
/// Emitted when a limit order is placed.
struct PlaceLimitOrderEvent has copy, drop, store {
/// Market ID for order.
market_id: u64,
/// User for market account that placed order.
user: address,
/// Custodian ID for market account that placed order.
custodian_id: u64,
/// Integrator address passed during limit order placement,
/// eligible for a portion of any generated taker fees.
integrator: address,
/// `ASK` or `BID`.
side: bool,
/// Size indicated during limit order placement.
size: u64,
/// Order limit price.
price: u64,
/// Restriction indicated during limit order placement, either
/// `market::FILL_OR_ABORT`, `market::IMMEDIATE_OR_CANCEL`,
/// `market::POST_OR_ABORT`, or `market::NO_RESTRICTION`.
restriction: u8,
/// Self match behavior indicated during limit order placement,
/// either `market::ABORT`, `market::CANCEL_BOTH`,
/// `market::CANCEL_MAKER`, or `market::CANCEL_TAKER`.
self_match_behavior: u8,
/// Order size remaining after the function call in which the
/// order was placed, which may include fills across the spread.
/// For example if an order of size 10 and restriction
/// `market::IMMEDIATE_OR_CANCEL` fills 6 lots across the
/// spread, the order will be cancelled and remaining size is 4.
remaining_size: u64,
/// Unique ID for order within market.
order_id: u128
}
/// Emitted when a market order is placed.
struct PlaceMarketOrderEvent has copy, drop, store {
/// Market ID for order.
market_id: u64,
/// User for market account that placed order.
user: address,
/// Custodian ID for market account that placed order.
custodian_id: u64,
/// Integrator address passed during market order placement,
/// eligible for a portion of any generated taker fees.
integrator: address,
/// Either `market::BUY` or `market::SELL`.
direction: bool,
/// Size indicated during market order placement.
size: u64,
/// Self match behavior indicated during market order placement,
/// either `market::ABORT`, `market::CANCEL_BOTH`,
/// `market::CANCEL_MAKER`, or `market::CANCEL_TAKER`.
self_match_behavior: u8,
/// Unique ID for order within market.
order_id: u128
}
// Structs <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Error codes >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/// Market account already exists.
const E_EXISTS_MARKET_ACCOUNT: u64 = 0;
/// Custodian ID has not been registered.
const E_UNREGISTERED_CUSTODIAN: u64 = 1;
/// No market accounts resource found.
const E_NO_MARKET_ACCOUNTS: u64 = 2;
/// No market account resource found.
const E_NO_MARKET_ACCOUNT: u64 = 3;
/// Asset type is not in trading pair for market.
const E_ASSET_NOT_IN_PAIR: u64 = 4;
/// Deposit would overflow asset ceiling.
const E_DEPOSIT_OVERFLOW_ASSET_CEILING: u64 = 5;
/// Underwriter is not valid for indicated market.
const E_INVALID_UNDERWRITER: u64 = 6;
/// Too little available for withdrawal.
const E_WITHDRAW_TOO_LITTLE_AVAILABLE: u64 = 7;
/// Price is zero.
const E_PRICE_0: u64 = 8;
/// Price exceeds maximum possible price.
const E_PRICE_TOO_HIGH: u64 = 9;
/// Ticks to fill an order overflows a `u64`.
const E_TICKS_OVERFLOW: u64 = 11;
/// Filling order would overflow asset received from trade.
const E_OVERFLOW_ASSET_IN: u64 = 12;
/// Not enough asset to trade away.
const E_NOT_ENOUGH_ASSET_OUT: u64 = 13;
/// No change in order size.
const E_CHANGE_ORDER_NO_CHANGE: u64 = 14;
/// Market order ID mismatch with user's open order.
const E_INVALID_MARKET_ORDER_ID: u64 = 15;
/// Mismatch between coin value and indicated amount.
const E_COIN_AMOUNT_MISMATCH: u64 = 16;
/// Expected order access key does not match assigned order access
/// key.
const E_ACCESS_KEY_MISMATCH: u64 = 17;
/// Coin type is generic asset.
const E_COIN_TYPE_IS_GENERIC_ASSET: u64 = 18;
/// Mismatch between expected size before operation and actual size
/// before operation.
const E_START_SIZE_MISMATCH: u64 = 19;
// Error codes <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Constants >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/// Flag for ask side
const ASK: bool = true;
/// Flag for bid side
const BID: bool = false;
/// Order cancelled because it was evicted from the price-time
/// priority queue.
const CANCEL_REASON_EVICTION: u8 = 1;
/// Order cancelled because it was an immediate-or-cancel order
/// that did not immediately fill.
const CANCEL_REASON_IMMEDIATE_OR_CANCEL: u8 = 2;
/// Order cancelled because it was manually cancelled by either
/// signing user or custodian.
const CANCEL_REASON_MANUAL_CANCEL: u8 = 3;
/// Order cancelled because no more quote asset could be traded.
const CANCEL_REASON_MAX_QUOTE_TRADED: u8 = 4;
/// Order cancelled because there was not enough liquidity to take
/// from.
const CANCEL_REASON_NOT_ENOUGH_LIQUIDITY: u8 = 5;
/// Order cancelled because it was on the maker side of an fill
/// where self match behavior indicated cancelling the maker order.
const CANCEL_REASON_SELF_MATCH_MAKER: u8 = 6;
/// Order cancelled because it was on the taker side of an fill
/// where self match behavior indicated cancelling the taker order.
const CANCEL_REASON_SELF_MATCH_TAKER: u8 = 7;
/// Flag to indicate that order is only temporarily cancelled from
/// market account memory because it will be subsequently re-placed
/// as part of a size change.
const CANCEL_REASON_SIZE_CHANGE_INTERNAL: u8 = 0;
/// Swap order cancelled because the remaining base asset amount to
/// match was too small to fill a single lot.
const CANCEL_REASON_TOO_SMALL_TO_FILL_LOT: u8 = 8;
/// Swap order cancelled because the next order on the book to match
/// against violated the swap order limit price.
const CANCEL_REASON_VIOLATED_LIMIT_PRICE: u8 = 9;
/// `u64` bitmask with all bits set, generated in Python via
/// `hex(int('1' * 64, 2))`.
const HI_64: u64 = 0xffffffffffffffff;
/// Maximum possible price that can be encoded in 32 bits. Generated
/// in Python via `hex(int('1' * 32, 2))`.
const HI_PRICE: u64 = 0xffffffff;
/// Flag for null value when null defined as 0.
const NIL: u64 = 0;
/// Custodian ID flag for no custodian.
const NO_CUSTODIAN: u64 = 0;
/// Underwriter ID flag for no underwriter.
const NO_UNDERWRITER: u64 = 0;
/// Number of bits market ID is shifted in market account ID.
const SHIFT_MARKET_ID: u8 = 64;
// Constants <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// View functions >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#[view]
/// Public constant getter for `ASK`.
///
/// # Testing
///
/// * `test_get_ASK()`
public fun get_ASK(): bool {ASK}
#[view]
/// Public constant getter for `BID`.
///
/// # Testing
///
/// * `test_get_BID()`
public fun get_BID(): bool {BID}
#[view]
/// Public constant getter for `CANCEL_REASON_EVICTION`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_EVICTION(): u8 {
CANCEL_REASON_EVICTION
}
#[view]
/// Public constant getter for `CANCEL_REASON_IMMEDIATE_OR_CANCEL`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_IMMEDIATE_OR_CANCEL(): u8 {
CANCEL_REASON_IMMEDIATE_OR_CANCEL
}
#[view]
/// Public constant getter for `CANCEL_REASON_MANUAL_CANCEL`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_MANUAL_CANCEL(): u8 {
CANCEL_REASON_MANUAL_CANCEL
}
#[view]
/// Public constant getter for `CANCEL_REASON_MAX_QUOTE_TRADED`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_MAX_QUOTE_TRADED(): u8 {
CANCEL_REASON_MAX_QUOTE_TRADED
}
#[view]
/// Public constant getter for `CANCEL_REASON_NOT_ENOUGH_LIQUIDITY`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_NOT_ENOUGH_LIQUIDITY(): u8 {
CANCEL_REASON_NOT_ENOUGH_LIQUIDITY
}
#[view]
/// Public constant getter for `CANCEL_REASON_SELF_MATCH_MAKER`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_SELF_MATCH_MAKER(): u8 {
CANCEL_REASON_SELF_MATCH_MAKER
}
#[view]
/// Public constant getter for `CANCEL_REASON_SELF_MATCH_TAKER`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_SELF_MATCH_TAKER(): u8 {
CANCEL_REASON_SELF_MATCH_TAKER
}
#[view]
/// Public constant getter for
/// `CANCEL_REASON_TOO_SMALL_TO_FILL_LOT`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_TOO_SMALL_TO_FILL_LOT(): u8 {
CANCEL_REASON_TOO_SMALL_TO_FILL_LOT
}
#[view]
/// Public constant getter for `CANCEL_REASON_VIOLATED_LIMIT_PRICE`.
///
/// # Testing
///
/// * `test_get_cancel_reasons()`
public fun get_CANCEL_REASON_VIOLATED_LIMIT_PRICE(): u8 {
CANCEL_REASON_VIOLATED_LIMIT_PRICE
}
#[view]
/// Public constant getter for `NO_CUSTODIAN`.
///
/// # Testing
///
/// * `test_get_NO_CUSTODIAN()`
public fun get_NO_CUSTODIAN(): u64 {NO_CUSTODIAN}
#[view]
/// Return all market account IDs associated with market ID.
///
/// # Parameters
///
/// * `user`: Address of user to check market account IDs for.
/// * `market_id`: Market ID to check market accounts for.
///
/// # Returns
///
/// * `vector<u128>`: Vector of user's market account IDs for given
/// market, empty if no market accounts.
///
/// # Gas considerations
///
/// Loops over all elements within a vector that is itself a single
/// item in global storage, and returns a vector via pass-by-value.
///
/// # Testing
///
/// * `test_market_account_getters()`
public fun get_all_market_account_ids_for_market_id(
user: address,
market_id: u64
): vector<u128>
acquires MarketAccounts {
let market_account_ids = vector::empty(); // Init empty vector.
// Return empty if user has no market accounts resource.
if (!exists<MarketAccounts>(user)) return market_account_ids;
let custodians_map_ref = // Immutably borrow custodians map.
&borrow_global<MarketAccounts>(user).custodians;
// Return empty if user has no market accounts for given market.
if (!tablist::contains(custodians_map_ref, market_id))
return market_account_ids;
// Immutably borrow list of custodians for given market.
let custodians_ref = tablist::borrow(custodians_map_ref, market_id);
// Initialize loop counter and number of elements in vector.
let (i, n_custodians) = (0, vector::length(custodians_ref));
while (i < n_custodians) { // Loop over all elements.
// Get custodian ID.
let custodian_id = *vector::borrow(custodians_ref, i);
// Get market account ID.
let market_account_id = ((market_id as u128) << SHIFT_MARKET_ID) |
(custodian_id as u128);
// Push back onto ongoing market account ID vector.
vector::push_back(&mut market_account_ids, market_account_id);
i = i + 1; // Increment loop counter
};
market_account_ids // Return market account IDs.
}
#[view]
/// Return all of a user's market account IDs.
///
/// # Parameters
///
/// * `user`: Address of user to check market account IDs for.
///
/// # Returns
///
/// * `vector<u128>`: Vector of user's market account IDs, empty if
/// no market accounts.
///
/// # Gas considerations
///
/// For each market that a user has market accounts for, loops over
/// a separate item in global storage, incurring a per-item read
/// cost. Additionally loops over a vector for each such per-item
/// read, incurring linearly-scaled vector operation costs. Returns
/// a vector via pass-by-value.
///
/// # Testing
///
/// * `test_market_account_getters()`
public fun get_all_market_account_ids_for_user(
user: address,
): vector<u128>
acquires MarketAccounts {
let market_account_ids = vector::empty(); // Init empty vector.
// Return empty if user has no market accounts resource.
if (!exists<MarketAccounts>(user)) return market_account_ids;
let custodians_map_ref = // Immutably borrow custodians map.
&borrow_global<MarketAccounts>(user).custodians;
// Get market ID option at head of market ID list.
let market_id_option = tablist::get_head_key(custodians_map_ref);
// While market IDs left to loop over:
while (option::is_some(&market_id_option)) {
// Get market ID.
let market_id = *option::borrow(&market_id_option);
// Immutably borrow list of custodians for given market and
// next market ID option in list.
let (custodians_ref, _, next) = tablist::borrow_iterable(
custodians_map_ref, market_id);
// Initialize loop counter and number of elements in vector.
let (i, n_custodians) = (0, vector::length(custodians_ref));
while (i < n_custodians) { // Loop over all elements.
// Get custodian ID.
let custodian_id = *vector::borrow(custodians_ref, i);
let market_account_id = // Get market account ID.
((market_id as u128) << SHIFT_MARKET_ID) |
(custodian_id as u128);
// Push back onto ongoing market account ID vector.
vector::push_back(&mut market_account_ids, market_account_id);
i = i + 1; // Increment loop counter
};
// Review next market ID option in list.
market_id_option = next;
};
market_account_ids // Return market account IDs.
}
#[view]
/// Return custodian ID encoded in market account ID.
///
/// # Testing
///
/// * `test_market_account_id_getters()`
public fun get_custodian_id(
market_account_id: u128
): u64 {
((market_account_id & (HI_64 as u128)) as u64)
}
#[view]
/// Return human-readable `MarketAccountView`.
///
/// Mutates state, so kept as a private view function.
///
/// # Aborts
///
/// * `E_NO_MARKET_ACCOUNT`: No such specified market account.
///
/// # Testing
///
/// * `test_deposits()`
/// * `test_get_market_account_no_market_account()`
/// * `test_get_market_accounts_open_orders()`
fun get_market_account(
user: address,
market_id: u64,
custodian_id: u64
): MarketAccountView
acquires MarketAccounts {
// Get market account ID from market ID, custodian ID.
let market_account_id = get_market_account_id(market_id, custodian_id);
// Verify user has market account.
assert!(has_market_account_by_market_account_id(
user, market_account_id), E_NO_MARKET_ACCOUNT);
// Mutably borrow market accounts map.
let market_accounts_map_ref_mut =
&mut borrow_global_mut<MarketAccounts>(user).map;
// Mutably borrow market account.
let market_account_ref_mut = table::borrow_mut(
market_accounts_map_ref_mut, market_account_id);
// Return market account view with parsed fields.
MarketAccountView{
market_id,
custodian_id,
asks: vectorize_open_orders(&mut market_account_ref_mut.asks),
bids: vectorize_open_orders(&mut market_account_ref_mut.bids),
base_total: market_account_ref_mut.base_total,
base_available: market_account_ref_mut.base_available,
base_ceiling: market_account_ref_mut.base_ceiling,
quote_total: market_account_ref_mut.quote_total,
quote_available: market_account_ref_mut.quote_available,
quote_ceiling: market_account_ref_mut.quote_ceiling
}
}
#[view]
/// Return market account ID with encoded market and custodian IDs.
///