├── .gitignore
├── LICENSE
├── README.md
├── logo
├── sea-logo-2.png
├── sea-logo.png
├── sea-logo.svg
└── sea.jpeg
└── src
├── Move.toml
├── deploy.sh
├── event.md
├── lending
└── README.md
├── mock.sh
├── sea_init
├── Move.toml
└── sources
│ └── spot_account.move
├── sea_lib
├── Move.toml
└── sources
│ ├── math.move
│ ├── rbtree.move
│ ├── u256.move
│ ├── u256.spec.move
│ └── u64x64.move
├── sea_lp
├── Move.toml
└── sources
│ └── lp.move
├── sea_mock
├── Move.toml
└── sources
│ └── mock_coins.move
└── sources
├── aggregator.move
├── amm.move
├── escrow.move
├── events.move
├── fee.move
├── grid.move
├── market.move
├── mining.move
├── price.move
├── router.move
├── sea.move
├── tests
├── test_env.move
├── test_mining.move
└── test_router.move
└── utils.move
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .vscode/
3 | .env
4 | docs/
5 | .VSCodeCounter/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Business Source License 1.1
2 |
3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
4 | "Business Source License" is a trademark of MariaDB Corporation Ab.
5 |
6 | -----------------------------------------------------------------------------
7 |
8 | Parameters
9 |
10 | Licensor: SeaProtocol LTD
11 |
12 | Licensed Work: Liquidswap Smart Contracts
13 | The Licensed Work is (c) 2022 SeaProtocol LTD
14 |
15 | Additional Use Grant: None
16 | Could be announced in the future as the attachment to the current document
17 |
18 | Change Date: The earlier of 2026-09-22
19 |
20 | Change License: GNU General Public License v2.0 or later
21 |
22 | -----------------------------------------------------------------------------
23 |
24 | Terms
25 |
26 | The Licensor hereby grants you the right to copy, modify, create derivative
27 | works, redistribute, and make non-production use of the Licensed Work. The
28 | Licensor may make an Additional Use Grant, above, permitting limited
29 | production use.
30 |
31 | Effective on the Change Date, or the fourth anniversary of the first publicly
32 | available distribution of a specific version of the Licensed Work under this
33 | License, whichever comes first, the Licensor hereby grants you rights under
34 | the terms of the Change License, and the rights granted in the paragraph
35 | above terminate.
36 |
37 | If your use of the Licensed Work does not comply with the requirements
38 | currently in effect as described in this License, you must purchase a
39 | commercial license from the Licensor, its affiliated entities, or authorized
40 | resellers, or you must refrain from using the Licensed Work.
41 |
42 | All copies of the original and modified Licensed Work, and derivative works
43 | of the Licensed Work, are subject to this License. This License applies
44 | separately for each version of the Licensed Work and the Change Date may vary
45 | for each version of the Licensed Work released by Licensor.
46 |
47 | You must conspicuously display this License on each original or modified copy
48 | of the Licensed Work. If you receive the Licensed Work in original or
49 | modified form from a third party, the terms and conditions set forth in this
50 | License apply to your use of that work.
51 |
52 | Any use of the Licensed Work in violation of this License will automatically
53 | terminate your rights under this License for the current and all other
54 | versions of the Licensed Work.
55 |
56 | This License does not grant you any right in any trademark or logo of
57 | Licensor or its affiliates (provided that you may use a trademark or logo of
58 | Licensor as expressly required by this License).
59 |
60 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
61 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
62 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
63 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
64 | TITLE.
65 |
66 | MariaDB hereby grants you permission to use this License’s text to license
67 | your works, and to refer to it using the trademark "Business Source License",
68 | as long as you comply with the Covenants of Licensor below.
69 |
70 | -----------------------------------------------------------------------------
71 |
72 | Covenants of Licensor
73 |
74 | In consideration of the right to use this License’s text and the "Business
75 | Source License" name and trademark, Licensor covenants to MariaDB, and to all
76 | other recipients of the licensed work to be provided by Licensor:
77 |
78 | 1. To specify as the Change License the GPL Version 2.0 or any later version,
79 | or a license that is compatible with GPL Version 2.0 or a later version,
80 | where "compatible" means that software provided under the Change License can
81 | be included in a program with software provided under GPL Version 2.0 or a
82 | later version. Licensor may specify additional Change Licenses without
83 | limitation.
84 |
85 | 2. To either: (a) specify an additional grant of rights to use that does not
86 | impose any additional restriction on the right granted in this License, as
87 | the Additional Use Grant; or (b) insert the text "None".
88 |
89 | 3. To specify a Change Date.
90 |
91 | 4. Not to modify this License in any other way.
92 |
93 | -----------------------------------------------------------------------------
94 |
95 | Notice
96 |
97 | The Business Source License (this document, or the "License") is not an Open
98 | Source license. However, the Licensed Work will eventually be made available
99 | under an Open Source License, as stated in this License.
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # seaprotocol
2 | We believe anybody has the right to trade any asset anywhere, anytime!
3 |
4 | Sea protocol is an ultimate DEX base on order-book & AMM, currently being developed on the Aptos & Sui blockchain.Sea protocol leverages the hyper parallelization of Aptos/Sui to bring incredible speed,reliability, and cost-effectiveness to decentralized trading.
5 |
6 |
7 | **Be the voice of freedom. Bank the unbanked. Speak for the silenced.**
8 |
9 | # design
10 |
11 | # Grid Orders
12 |
13 | Grid order is also known as grid trading. grid orders at regular intervals across a range. Buy orders are replaced with sell orders when they fill and sell orders are replaced with buy orders.
14 |
15 | for example, Alice place a grid order which has 8 orders, 4 is buy order, 4 is sell order, as following:
16 | | Side | Price | Qty |
17 | |---|---|---|
18 | | Sell3 | 103 | 2.5 |
19 | | Sell2 | 102 | 2.5 |
20 | | Sell1 | 101 | 2.5 |
21 | | Sell0 | 100 | 2.5 |
22 | | Buy0 | 98 | 2.5 |
23 | | Buy1 | 97 | 2.5 |
24 | | Buy2 | 96 | 2.5 |
25 | | Buy3 | 95 | 2.5 |
26 |
27 | If Sell0 is filled, this order got 100*2.5 = 250 quote; then this order will filp to buy order:
28 | | Side | Price | Qty |
29 | |---|---|---|
30 | | Buy | 99 | 2.5 |
31 |
32 | If the filp order is filled, then it become sell order again:
33 | | Side | Price | Qty |
34 | |---|---|---|
35 | | Sell | 100 | 2.5 |
36 |
37 | As the price fluctuates up and down, the grid filp again and again, the makes will got more and more profit.
38 |
39 | And we provide maximum flexibility: you can cancel any order in the grid at any time.
40 |
41 | Uniswap v3 a continual ranged grid order.
42 |
43 | # Pair
44 |
45 | ## Min Lot Size
46 |
47 | Sea protocol set min lot size to protect Sybil attack.
48 |
49 | The pair's min lot size is NOT set when the pair is created. The min lot size is set when first trade created, and the min lot size can be modify at anytime when the trade price is updated, but it is not necessary.
50 |
51 | ## Price
52 |
53 | Because aptos stdlib coin balance is u64, so the coin's decimals can NOT be too big, the recommend max is 8.
54 |
55 | When not consider the coin scale, price formula is following:
56 | ```
57 | price = quote_volume / base_volume
58 | ```
59 |
60 | If we consider the coin scale, it became:
61 | ```
62 | price = (quote_amount * base_scale) / (base_amount * quote_scale)
63 | ```
64 |
65 | If the price is express in big int, we should multiple the price by a big number, such as 10^8, we call this is price coefficient, so:
66 |
67 | ```
68 | price = price_coefficient * (quote_amount * base_scale) / (base_amount * quote_scale)
69 | ```
70 |
71 | If we define price_ratio as following:
72 | ```
73 | price_ratio = price_coefficient * base_scale / quote_scale
74 | ```
75 |
76 | then, the final price express:
77 | ```
78 | price = price_ratio * (quote_amount/base_amount)
79 | ```
80 |
81 | # zero spread
82 |
83 | You can place post-only orders with same price, reverse side!
84 |
85 | This is important for stable coin swap. For example, the USDT/USDC pair, in the orderbook, there have both sell orders with price 1 and buy orders with price 1, so anyone can buy USDT at price 1, or sell USDT at price 1.
86 |
87 | # Trading is mining
88 |
89 | Trading is mining.
90 |
91 | Uniswap does not allocate tokens to trades, I think this is unfair for traders.
92 |
93 | Traders paid the trade fee, paid the gas fee, but got nothing.
94 |
95 | We will avoid this and our token will incentivize both traders and LPs, for 50% to 50%.
96 |
97 |
98 | ## test
99 |
100 | ```
101 | aptos move test -i 1000000000
102 | ```
103 |
104 | # Join us
105 |
106 | Follow us on twitter:
107 |
108 | https://twitter.com/sea_protocol
109 |
110 | Join us on discord:
111 |
112 | https://discord.gg/fuEkecabwS
113 |
114 | And our medium:
115 |
116 | https://medium.com/@seaprotocol
117 |
118 |
--------------------------------------------------------------------------------
/logo/sea-logo-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sea-protocol/seaprotocol/9a85939aadbc6eba4217746475e7c812aa0f297f/logo/sea-logo-2.png
--------------------------------------------------------------------------------
/logo/sea-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sea-protocol/seaprotocol/9a85939aadbc6eba4217746475e7c812aa0f297f/logo/sea-logo.png
--------------------------------------------------------------------------------
/logo/sea-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/logo/sea.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sea-protocol/seaprotocol/9a85939aadbc6eba4217746475e7c812aa0f297f/logo/sea.jpeg
--------------------------------------------------------------------------------
/src/Move.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "Sea"
3 | version = "0.1.0" # Per SemVer
4 |
5 | [addresses]
6 | sea = "0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df"
7 |
8 | # sea_spot = "0x5e8c5122bd2ce18e45912699613ab527697bf58556815c08ad5463092dd91990"
9 | # sea_spot = "0x38140b684d7ac7e8d9a7f1d693dd0098be7bfef0092fc0730ba2d0688e991069"
10 | sea_spot = "0x4f7d2de5a2b9797a9c74f906ef405dfc9c417da12d7d7eeff782eeb1ec896d2b"
11 | sea_team = "0x2149e2b35e29c1d8a845a7dee9b9c83dd9986991aa4e27979b076e33b693a87d"
12 | sea_dev = "0xa33f058bc7a5cd7138d5a064778db49fd388359fa0b66af445f682d7905d58f6"
13 |
14 | # Mock users for unit tests
15 | user_1 = "0x1111"
16 | user_2 = "0x2222"
17 | user_3 = "0x3333"
18 | user_4 = "0x4444"
19 |
20 | [dependencies.SeaInit]
21 | local = "./sea_init/"
22 |
23 | [dependencies.SeaLP]
24 | local = "./sea_lp/"
25 |
26 | [dependencies.SeaLib]
27 | local = "./sea_lib/"
28 |
29 | [dependencies.AptosFramework]
30 | git = "https://github.com/aptos-labs/aptos-core.git"
31 | subdir = "aptos-move/framework/aptos-framework"
32 | # rev = "563e2abff8fa997aec81abe48772af8a20731f76"
33 | # rev = "main"
34 | rev = "1.0.4"
35 |
36 | # [dependencies.UQ64x64]
37 | # git = "https://github.com/pontem-network/UQ64x64.git"
38 | # rev = "v0.3.7"
39 |
40 | # [dependencies.U256]
41 | # git = "https://github.com/pontem-network/U256.git"
42 | # rev = "v0.3.8"
43 |
--------------------------------------------------------------------------------
/src/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo 'get fund ....'
4 | aptos account fund-with-faucet --account dev3 --amount 1000000000
5 | aptos account fund-with-faucet --account sealib --amount 1000000000
6 |
7 | echo 'deploy sea_init/sea_lp ....'
8 | cd sea_lp
9 | aptos move compile --save-metadata
10 | cd ../sea_init
11 | aptos move publish --profile dev3 --assume-yes
12 | aptos move run --profile dev3 --assume-yes --function-id 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::spot_account::initialize_spot_account
13 | aptos move run --profile dev3 --assume-yes --function-id 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::spot_account::publish_pkg --args hex:"`xxd -ps -c10000000 /Users/guotie/guotie/chain/seaprotocol/members/bigwin/seaprotocol/src/sea_lp/build/SeaLP/package-metadata.bcs`" hex:"`xxd -ps -c10000000 /Users/guotie/guotie/chain/seaprotocol/members/bigwin/seaprotocol/src/sea_lp/build/SeaLP/bytecode_modules/lp.mv`"
14 |
15 | echo 'deploy sealib ....'
16 | cd ../sea_lib
17 | aptos move publish --profile sealib --assume-yes
18 |
19 | echo 'deploy sea ....'
20 | cd ../
21 | aptos move publish --profile dev3 --assume-yes --included-artifacts none
22 |
23 |
24 | echo 'deploy sea_mock ....'
25 | cd sea_mock
26 | aptos move publish --profile dev3 --assume-yes
27 |
28 | aptos move run --function-id 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::market::register_quote \
29 | --args u64:10000000 --type-args 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::mock_coins::USDC --assume-yes --profile dev3
30 | aptos move run --function-id 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::market::register_quote \
31 | --args u64:10000000 --type-args 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::mock_coins::USDT --assume-yes --profile dev3
32 |
--------------------------------------------------------------------------------
/src/event.md:
--------------------------------------------------------------------------------
1 | ## place order
2 |
3 | ```
4 | struct EventOrderPlace has store, drop {
5 | qty: u64,
6 | pair_id: u64,
7 | order_id: u64,
8 | price: u64,
9 | side: u8,
10 | grid_id: u64,
11 | account_id: u64,
12 | is_flip: bool,
13 | }
14 | ```
15 |
16 | ## cancel order
17 | ```
18 | struct EventOrderCancel has store, drop {
19 | qty: u64,
20 | pair_id: u64,
21 | order_id: u64,
22 | price: u64,
23 | side: u8,
24 | grid_id: u64,
25 | account_id: u64,
26 | }
27 | ```
28 |
29 | ## order filled
30 | ```
31 | struct EventTrade has store, drop {
32 | qty: u64,
33 | quote_qty: u64,
34 | pair_id: u64,
35 | price: u64,
36 | fee_total: u64,
37 | fee_maker: u64,
38 | fee_dao: u64,
39 | }
40 | ```
41 |
42 | ## order complete
43 | ```
44 | struct EventOrderComplete has store, drop {
45 | pair_id: u64,
46 | order_id: u64,
47 | price: u64,
48 | side: u8,
49 | grid_id: u64,
50 | account_id: u64,
51 | }
52 | ```
53 |
54 | ## register account
55 | ```
56 | struct EventAccount has store, drop {
57 | account_id: u64,
58 | account_addr: address,
59 | }
60 | ```
61 |
62 | ## register coin
63 | ```
64 | struct EventCoin has store, drop {
65 | coin_id: u64,
66 | coin_info: TypeInfo,
67 | }
68 | ```
69 |
70 | ## register quote
71 | ```
72 | struct EventQuote has store, drop {
73 | coin_info: TypeInfo,
74 | coin_id: u64,
75 | min_notional: u64,
76 | }
77 | ```
78 |
79 | ## register pair/pool
80 | ```
81 | struct EventPair has store, drop {
82 | base: TypeInfo,
83 | quote: TypeInfo,
84 | fee_ratio: u64,
85 | base_id: u64,
86 | quote_id: u64,
87 | pair_id: u64,
88 | lot_size: u64,
89 | price_ratio: u64,
90 | price_coefficient: u64,
91 | base_decimals: u8,
92 | quote_decimals: u8,
93 | }
94 | ```
95 |
--------------------------------------------------------------------------------
/src/lending/README.md:
--------------------------------------------------------------------------------
1 | # NFT Lending
2 |
3 | We just have an idea about NFT lending, but we don't have time to verify it. Maybe later
4 |
--------------------------------------------------------------------------------
/src/mock.sh:
--------------------------------------------------------------------------------
1 |
2 | # create pairs
3 | aptos account fund-with-faucet --account pool --amount 1000000000
4 |
5 | aptos move run --function-id 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::market::register_pair \
6 | --args u64:500 --args u64:1000000000 --args u64:100 \
7 | --type-args 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::mock_coins::BTC \
8 | --type-args 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::mock_coins::USDT \
9 | --assume-yes --profile pool
10 |
11 | aptos move run --function-id 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::market::register_pair \
12 | --args u64:500 --args u64:1000000000 --args u64:100 \
13 | --type-args 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::mock_coins::ETH \
14 | --type-args 0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df::mock_coins::USDT \
15 | --assume-yes --profile pool
16 |
--------------------------------------------------------------------------------
/src/sea_init/Move.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = 'SeaInit'
3 | version = '0.1.0'
4 |
5 | [addresses]
6 | sea = "0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df"
7 |
8 | [dependencies.AptosFramework]
9 | git = "https://github.com/aptos-labs/aptos-core.git"
10 | subdir = "aptos-move/framework/aptos-framework"
11 | # rev = "563e2abff8fa997aec81abe48772af8a20731f76"
12 |
13 | # rev = "main"
14 | rev = "1.0.4"
15 |
--------------------------------------------------------------------------------
/src/sea_init/sources/spot_account.move:
--------------------------------------------------------------------------------
1 | /// # Module-level documentation sections
2 | ///
3 | /// * [Background](#Background)
4 | /// * [Implementation](#Implementation)
5 | /// * [Basic public functions](#Basic-public-functions)
6 | /// * [Traversal](#Traversal)
7 | ///
8 | /// # Background
9 | ///
10 | /// spot resource account
11 | ///
12 | module sea::spot_account {
13 | use std::signer;
14 |
15 | use aptos_framework::account::{Self, SignerCapability};
16 |
17 | const E_NO_AUTH: u64 = 1;
18 |
19 | /// Temporary storage for spot resource account signer capability.
20 | struct CapabilityStorage has key { signer_cap: SignerCapability }
21 |
22 | /// Creates new resource account for Sea spot DEX, puts signer capability into storage.
23 | /// Can be executed only from Sea account.
24 | public entry fun initialize_spot_account(
25 | sea_admin: &signer
26 | ) {
27 | assert!(signer::address_of(sea_admin) == @sea, E_NO_AUTH);
28 |
29 | let (_, signer_cap) =
30 | account::create_resource_account(sea_admin, b"sea_spot_account");
31 | move_to(sea_admin, CapabilityStorage { signer_cap });
32 | }
33 |
34 | public entry fun initialize_lp_account(
35 | sea_admin: &signer,
36 | lp_coin_metadata_serialized: vector,
37 | lp_coin_code: vector
38 | ) {
39 | assert!(signer::address_of(sea_admin) == @sea, E_NO_AUTH);
40 |
41 | let (lp_acc, signer_cap) =
42 | account::create_resource_account(sea_admin, b"sea_spot_account");
43 | aptos_framework::code::publish_package_txn(
44 | &lp_acc,
45 | lp_coin_metadata_serialized,
46 | vector[lp_coin_code]
47 | );
48 | move_to(sea_admin, CapabilityStorage { signer_cap });
49 | }
50 |
51 | public entry fun publish_pkg(
52 | sea_admin: &signer,
53 | lp_coin_metadata_serialized: vector,
54 | lp_coin_code: vector) acquires CapabilityStorage {
55 | assert!(signer::address_of(sea_admin) == @sea, E_NO_AUTH);
56 |
57 | let cap = borrow_global(@sea);
58 | let sign = account::create_signer_with_capability(&cap.signer_cap);
59 |
60 | aptos_framework::code::publish_package_txn(
61 | &sign,
62 | lp_coin_metadata_serialized,
63 | vector[lp_coin_code]
64 | );
65 | }
66 |
67 | /// Destroys temporary storage for resource account signer capability and returns signer capability.
68 | /// It needs for initialization of Sea DEX spot market.
69 | public fun retrieve_signer_cap(
70 | sea_admin: &signer
71 | ): SignerCapability acquires CapabilityStorage {
72 | assert!(signer::address_of(sea_admin) == @sea, E_NO_AUTH);
73 | let CapabilityStorage { signer_cap } =
74 | move_from(signer::address_of(sea_admin));
75 | signer_cap
76 | }
77 |
78 | #[test_only]
79 | use std::debug;
80 |
81 | #[test(sea_admin = @sea)]
82 | fun test_resource_account(
83 | sea_admin: &signer
84 | ): signer {
85 | let (_, signer_cap) =
86 | account::create_resource_account(sea_admin, b"sea_spot_account");
87 | let sig = account::create_signer_with_capability(&signer_cap);
88 | debug::print(&signer::address_of(&sig));
89 | return sig
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/sea_lib/Move.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = 'SeaLib'
3 | version = '0.1.0'
4 |
5 | [addresses]
6 | sealib = "0x7587a895cddb80d22b248603408f460acbdb901c733e01750ab88c71886fc7e6" # sealib
7 |
8 | [dependencies.AptosFramework]
9 | git = "https://github.com/aptos-labs/aptos-core.git"
10 | subdir = "aptos-move/framework/aptos-framework"
11 | # rev = "563e2abff8fa997aec81abe48772af8a20731f76"
12 | # rev = "main"
13 | rev = "1.0.4"
14 |
--------------------------------------------------------------------------------
/src/sea_lib/sources/math.move:
--------------------------------------------------------------------------------
1 | module sealib::math {
2 |
3 | const MAX_U128: u128 = 340282366920938463463374607431768211455;
4 |
5 | /// Returns 10^degree.
6 | public fun pow_10(degree: u8): u64 {
7 | let res = 1;
8 | let i = 0;
9 | while ({
10 | spec {
11 | invariant res == spec_pow(10, i);
12 | invariant 0 <= i && i <= degree;
13 | };
14 | i < degree
15 | }) {
16 | res = res * 10;
17 | i = i + 1;
18 | };
19 | res
20 | }
21 |
22 | spec fun spec_pow(y: u64, x: u64): u64 {
23 | if (x == 0) {
24 | 1
25 | } else {
26 | y * spec_pow(y, x-1)
27 | }
28 | }
29 | spec pow_10 {
30 | ensures degree == 0 ==> result == 1;
31 | ensures result == spec_pow(10, degree);
32 | }
33 |
34 | public fun min_u64(a: u64, b: u64): u64 {
35 | if (a < b) a else b
36 | }
37 |
38 | /// Get square root of `y`.
39 | /// Babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
40 | public fun sqrt(y: u128): u64 {
41 | if (y < 4) {
42 | if (y == 0) {
43 | 0u64
44 | } else {
45 | 1u64
46 | }
47 | } else {
48 | let z = y;
49 | let x = y / 2 + 1;
50 | while (x < z) {
51 | z = x;
52 | x = (y / x + x) / 2;
53 | };
54 | (z as u64)
55 | }
56 | }
57 |
58 | public fun sqrt_u128(y: u128): u128 {
59 | if (y < 4) {
60 | if (y == 0) {
61 | 0u128
62 | } else {
63 | 1u128
64 | }
65 | } else {
66 | let z = y;
67 | let x = y / 2 + 1;
68 | while (x < z) {
69 | z = x;
70 | x = (y / x + x) / 2;
71 | };
72 | z
73 | }
74 | }
75 |
76 | // Check if mul maybe overflow
77 | // The result maybe false positive
78 | public fun is_overflow_mul(a: u128, b: u128): bool {
79 | MAX_U128 / b <= a
80 | }
81 | }
--------------------------------------------------------------------------------
/src/sea_lib/sources/u256.move:
--------------------------------------------------------------------------------
1 | /// The implementation of large numbers written in Move language.
2 | /// Code derived from original work by Andrew Poelstra
3 | ///
4 | /// Rust Bitcoin Library
5 | /// Written in 2014 by
6 | /// Andrew Poelstra
7 | ///
8 | /// To the extent possible under law, the author(s) have dedicated all
9 | /// copyright and related and neighboring rights to this software to
10 | /// the public domain worldwide. This software is distributed without
11 | /// any warranty.
12 | ///
13 | /// Simplified impl by Parity Team - https://github.com/paritytech/parity-common/blob/master/uint/src/uint.rs
14 | ///
15 | /// Features:
16 | /// * mul
17 | /// * div
18 | /// * add
19 | /// * sub
20 | /// * shift left
21 | /// * shift right
22 | /// * bitwise and, xor, or.
23 | /// * compare
24 | /// * if math overflows the contract crashes.
25 | ///
26 | /// Would be nice to help with the following TODO list:
27 | /// * pow() , sqrt().
28 | /// * math funcs that don't abort on overflows, but just returns reminders.
29 | /// * Export of low_u128 (see original implementation).
30 | /// * Export of low_u64 (see original implementation).
31 | /// * Gas Optimisation:
32 | /// * We can optimize by replacing bytecode, as far as we know Move VM itself support slices, so probably
33 | /// we can try to replace parts works with (`v0`,`v1`,`v2`,`v3` etc) works.
34 | /// * More?
35 | /// * More tests (see current tests and TODOs i left):
36 | /// * u256_arithmetic_test - https://github.com/paritytech/bigint/blob/master/src/uint.rs#L1338
37 | /// * More from - https://github.com/paritytech/bigint/blob/master/src/uint.rs
38 | /// * Division:
39 | /// * Could be improved with div_mod_small (current version probably would took a lot of resources for small numbers).
40 | /// * Also could be improved with Knuth, TAOCP, Volume 2, section 4.3.1, Algorithm D (see link to Parity above).
41 | module sealib::u256 {
42 | // Errors.
43 | /// When can't cast `U256` to `u128` (e.g. number too large).
44 | const ECAST_OVERFLOW: u64 = 0;
45 |
46 | /// When trying to get or put word into U256 but it's out of index.
47 | const EWORDS_OVERFLOW: u64 = 1;
48 |
49 | /// When math overflows.
50 | const EOVERFLOW: u64 = 2;
51 |
52 | /// When attempted to divide by zero.
53 | const EDIV_BY_ZERO: u64 = 3;
54 |
55 | // Constants.
56 |
57 | /// Max `u64` value.
58 | const U64_MAX: u128 = 18446744073709551615;
59 |
60 | /// Max `u128` value.
61 | const U128_MAX: u128 = 340282366920938463463374607431768211455;
62 |
63 | /// Total words in `U256` (64 * 4 = 256).
64 | const WORDS: u64 = 4;
65 |
66 | /// When both `U256` equal.
67 | const EQUAL: u8 = 0;
68 |
69 | /// When `a` is less than `b`.
70 | const LESS_THAN: u8 = 1;
71 |
72 | /// When `b` is greater than `b`.
73 | const GREATER_THAN: u8 = 2;
74 |
75 | // Data structs.
76 |
77 | /// The `U256` resource.
78 | /// Contains 4 u64 numbers.
79 | struct U256 has copy, drop, store {
80 | v0: u64,
81 | v1: u64,
82 | v2: u64,
83 | v3: u64,
84 | }
85 |
86 | /// Double `U256` used for multiple (to store overflow).
87 | struct DU256 has copy, drop, store {
88 | v0: u64,
89 | v1: u64,
90 | v2: u64,
91 | v3: u64,
92 | v4: u64,
93 | v5: u64,
94 | v6: u64,
95 | v7: u64,
96 | }
97 |
98 | // Public functions.
99 | /// Adds two `U256` and returns sum.
100 | public fun add(a: U256, b: U256): U256 {
101 | let ret = zero();
102 | let carry = 0u64;
103 |
104 | let i = 0;
105 | while (i < WORDS) {
106 | let a1 = get(&a, i);
107 | let b1 = get(&b, i);
108 |
109 | if (carry != 0) {
110 | let (res1, is_overflow1) = overflowing_add(a1, b1);
111 | let (res2, is_overflow2) = overflowing_add(res1, carry);
112 | put(&mut ret, i, res2);
113 |
114 | carry = 0;
115 | if (is_overflow1) {
116 | carry = carry + 1;
117 | };
118 |
119 | if (is_overflow2) {
120 | carry = carry + 1;
121 | }
122 | } else {
123 | let (res, is_overflow) = overflowing_add(a1, b1);
124 | put(&mut ret, i, res);
125 |
126 | carry = 0;
127 | if (is_overflow) {
128 | carry = 1;
129 | };
130 | };
131 |
132 | i = i + 1;
133 | };
134 |
135 | assert!(carry == 0, EOVERFLOW);
136 |
137 | ret
138 | }
139 |
140 | /// Convert `U256` to `u128` value if possible (otherwise it aborts).
141 | public fun as_u128(a: U256): u128 {
142 | assert!(a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW);
143 | ((a.v1 as u128) << 64) + (a.v0 as u128)
144 | }
145 |
146 | /// Convert `U256` to `u64` value if possible (otherwise it aborts).
147 | public fun as_u64(a: U256): u64 {
148 | assert!(a.v1 == 0 && a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW);
149 | a.v0
150 | }
151 |
152 | /// Compares two `U256` numbers.
153 | public fun compare(a: &U256, b: &U256): u8 {
154 | let i = WORDS;
155 | while (i > 0) {
156 | i = i - 1;
157 | let a1 = get(a, i);
158 | let b1 = get(b, i);
159 |
160 | if (a1 != b1) {
161 | if (a1 < b1) {
162 | return LESS_THAN
163 | } else {
164 | return GREATER_THAN
165 | }
166 | }
167 | };
168 |
169 | EQUAL
170 | }
171 |
172 | /// Returns a `U256` from `u64` value.
173 | public fun from_u64(val: u64): U256 {
174 | from_u128((val as u128))
175 | }
176 |
177 | /// Returns a `U256` from `u128` value.
178 | public fun from_u128(val: u128): U256 {
179 | let (a2, a1) = split_u128(val);
180 |
181 | U256 {
182 | v0: a1,
183 | v1: a2,
184 | v2: 0,
185 | v3: 0,
186 | }
187 | }
188 |
189 | /// Multiples two `U256`.
190 | public fun mul(a: U256, b: U256): U256 {
191 | let ret = DU256 {
192 | v0: 0,
193 | v1: 0,
194 | v2: 0,
195 | v3: 0,
196 | v4: 0,
197 | v5: 0,
198 | v6: 0,
199 | v7: 0,
200 | };
201 |
202 | let i = 0;
203 | while (i < WORDS) {
204 | let carry = 0u64;
205 | let b1 = get(&b, i);
206 |
207 | let j = 0;
208 | while (j < WORDS) {
209 | let a1 = get(&a, j);
210 |
211 | if (a1 != 0 || carry != 0) {
212 | let (hi, low) = split_u128((a1 as u128) * (b1 as u128));
213 |
214 | let overflow = {
215 | let existing_low = get_d(&ret, i + j);
216 | let (low, o) = overflowing_add(low, existing_low);
217 | put_d(&mut ret, i + j, low);
218 | if (o) {
219 | 1
220 | } else {
221 | 0
222 | }
223 | };
224 |
225 | carry = {
226 | let existing_hi = get_d(&ret, i + j + 1);
227 | let hi = hi + overflow;
228 | let (hi, o0) = overflowing_add(hi, carry);
229 | let (hi, o1) = overflowing_add(hi, existing_hi);
230 | put_d(&mut ret, i + j + 1, hi);
231 |
232 | if (o0 || o1) {
233 | 1
234 | } else {
235 | 0
236 | }
237 | };
238 | };
239 |
240 | j = j + 1;
241 | };
242 |
243 | i = i + 1;
244 | };
245 |
246 | let (r, overflow) = du256_to_u256(ret);
247 | assert!(!overflow, EOVERFLOW);
248 | r
249 | }
250 |
251 | /// Subtracts two `U256`, returns result.
252 | public fun sub(a: U256, b: U256): U256 {
253 | let ret = zero();
254 |
255 | let carry = 0u64;
256 |
257 | let i = 0;
258 | while (i < WORDS) {
259 | let a1 = get(&a, i);
260 | let b1 = get(&b, i);
261 |
262 | if (carry != 0) {
263 | let (res1, is_overflow1) = overflowing_sub(a1, b1);
264 | let (res2, is_overflow2) = overflowing_sub(res1, carry);
265 | put(&mut ret, i, res2);
266 |
267 | carry = 0;
268 | if (is_overflow1) {
269 | carry = carry + 1;
270 | };
271 |
272 | if (is_overflow2) {
273 | carry = carry + 1;
274 | }
275 | } else {
276 | let (res, is_overflow) = overflowing_sub(a1, b1);
277 | put(&mut ret, i, res);
278 |
279 | carry = 0;
280 | if (is_overflow) {
281 | carry = 1;
282 | };
283 | };
284 |
285 | i = i + 1;
286 | };
287 |
288 | assert!(carry == 0, EOVERFLOW);
289 | ret
290 | }
291 |
292 | /// Divide `a` by `b`.
293 | public fun div(a: U256, b: U256): U256 {
294 | let ret = zero();
295 |
296 | let a_bits = bits(&a);
297 | let b_bits = bits(&b);
298 |
299 | assert!(b_bits != 0, EDIV_BY_ZERO); // DIVIDE BY ZERO.
300 | if (a_bits < b_bits) {
301 | // Immidiatelly return.
302 | return ret
303 | };
304 |
305 | let shift = a_bits - b_bits;
306 | b = shl(b, (shift as u8));
307 |
308 | loop {
309 | let cmp = compare(&a, &b);
310 | if (cmp == GREATER_THAN || cmp == EQUAL) {
311 | let index = shift / 64;
312 | let m = get(&ret, index);
313 | let c = m | 1 << ((shift % 64) as u8);
314 | put(&mut ret, index, c);
315 |
316 | a = sub(a, b);
317 | };
318 |
319 | b = shr(b, 1);
320 | if (shift == 0) {
321 | break
322 | };
323 |
324 | shift = shift - 1;
325 | };
326 |
327 | ret
328 | }
329 |
330 | /// Binary xor `a` by `b`.
331 | fun bitxor(a: U256, b: U256): U256 {
332 | let ret = zero();
333 |
334 | let i = 0;
335 | while (i < WORDS) {
336 | let a1 = get(&a, i);
337 | let b1 = get(&b, i);
338 | put(&mut ret, i, a1 ^ b1);
339 |
340 | i = i + 1;
341 | };
342 |
343 | ret
344 | }
345 |
346 | /// Binary and `a` by `b`.
347 | fun bitand(a: U256, b: U256): U256 {
348 | let ret = zero();
349 |
350 | let i = 0;
351 | while (i < WORDS) {
352 | let a1 = get(&a, i);
353 | let b1 = get(&b, i);
354 | put(&mut ret, i, a1 & b1);
355 |
356 | i = i + 1;
357 | };
358 |
359 | ret
360 | }
361 |
362 | /// Binary or `a` by `b`.
363 | fun bitor(a: U256, b: U256): U256 {
364 | let ret = zero();
365 |
366 | let i = 0;
367 | while (i < WORDS) {
368 | let a1 = get(&a, i);
369 | let b1 = get(&b, i);
370 | put(&mut ret, i, a1 | b1);
371 |
372 | i = i + 1;
373 | };
374 |
375 | ret
376 | }
377 |
378 | /// Shift right `a` by `shift`.
379 | public fun shr(a: U256, shift: u8): U256 {
380 | let ret = zero();
381 |
382 | let word_shift = (shift as u64) / 64;
383 | let bit_shift = (shift as u64) % 64;
384 |
385 | let i = word_shift;
386 | while (i < WORDS) {
387 | let m = get(&a, i) >> (bit_shift as u8);
388 | put(&mut ret, i - word_shift, m);
389 | i = i + 1;
390 | };
391 |
392 | if (bit_shift > 0) {
393 | let j = word_shift + 1;
394 | while (j < WORDS) {
395 | let m = get(&ret, j - word_shift - 1) + (get(&a, j) << (64 - (bit_shift as u8)));
396 | put(&mut ret, j - word_shift - 1, m);
397 | j = j + 1;
398 | };
399 | };
400 |
401 | ret
402 | }
403 |
404 | /// Shift left `a` by `shift`.
405 | public fun shl(a: U256, shift: u8): U256 {
406 | let ret = zero();
407 |
408 | let word_shift = (shift as u64) / 64;
409 | let bit_shift = (shift as u64) % 64;
410 |
411 | let i = word_shift;
412 | while (i < WORDS) {
413 | let m = get(&a, i - word_shift) << (bit_shift as u8);
414 | put(&mut ret, i, m);
415 | i = i + 1;
416 | };
417 |
418 | if (bit_shift > 0) {
419 | let j = word_shift + 1;
420 |
421 | while (j < WORDS) {
422 | let m = get(&ret, j) + (get(&a, j - 1 - word_shift) >> (64 - (bit_shift as u8)));
423 | put(&mut ret, j, m);
424 | j = j + 1;
425 | };
426 | };
427 |
428 | ret
429 | }
430 |
431 | /// Returns `U256` equals to zero.
432 | public fun zero(): U256 {
433 | U256 {
434 | v0: 0,
435 | v1: 0,
436 | v2: 0,
437 | v3: 0,
438 | }
439 | }
440 |
441 | // Private functions.
442 | /// Get bits used to store `a`.
443 | fun bits(a: &U256): u64 {
444 | let i = 1;
445 | while (i < WORDS) {
446 | let a1 = get(a, WORDS - i);
447 | if (a1 > 0) {
448 | return ((0x40 * (WORDS - i + 1)) - (leading_zeros_u64(a1) as u64))
449 | };
450 |
451 | i = i + 1;
452 | };
453 |
454 | let a1 = get(a, 0);
455 | 0x40 - (leading_zeros_u64(a1) as u64)
456 | }
457 |
458 | /// Get leading zeros of a binary representation of `a`.
459 | fun leading_zeros_u64(a: u64): u8 {
460 | if (a == 0) {
461 | return 64
462 | };
463 |
464 | let a1 = a & 0xFFFFFFFF;
465 | let a2 = a >> 32;
466 |
467 | if (a2 == 0) {
468 | let bit = 32;
469 |
470 | while (bit >= 1) {
471 | let b = (a1 >> (bit-1)) & 1;
472 | if (b != 0) {
473 | break
474 | };
475 |
476 | bit = bit - 1;
477 | };
478 |
479 | (32 - bit) + 32
480 | } else {
481 | let bit = 64;
482 | while (bit >= 1) {
483 | let b = (a >> (bit-1)) & 1;
484 | if (b != 0) {
485 | break
486 | };
487 | bit = bit - 1;
488 | };
489 |
490 | 64 - bit
491 | }
492 | }
493 |
494 | /// Similar to Rust `overflowing_add`.
495 | /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur.
496 | /// If an overflow would have occurred then the wrapped value is returned.
497 | fun overflowing_add(a: u64, b: u64): (u64, bool) {
498 | let a128 = (a as u128);
499 | let b128 = (b as u128);
500 |
501 | let r = a128 + b128;
502 | if (r > U64_MAX) {
503 | // overflow
504 | let overflow = r - U64_MAX - 1;
505 | ((overflow as u64), true)
506 | } else {
507 | (((a128 + b128) as u64), false)
508 | }
509 | }
510 |
511 | /// Similar to Rust `overflowing_sub`.
512 | /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur.
513 | /// If an overflow would have occurred then the wrapped value is returned.
514 | fun overflowing_sub(a: u64, b: u64): (u64, bool) {
515 | if (a < b) {
516 | let r = b - a;
517 | ((U64_MAX as u64) - r + 1, true)
518 | } else {
519 | (a - b, false)
520 | }
521 | }
522 |
523 | /// Extracts two `u64` from `a` `u128`.
524 | fun split_u128(a: u128): (u64, u64) {
525 | let a1 = ((a >> 64) as u64);
526 | let a2 = ((a & 0xFFFFFFFFFFFFFFFF) as u64);
527 |
528 | (a1, a2)
529 | }
530 |
531 | /// Get word from `a` by index `i`.
532 | public fun get(a: &U256, i: u64): u64 {
533 | if (i == 0) {
534 | a.v0
535 | } else if (i == 1) {
536 | a.v1
537 | } else if (i == 2) {
538 | a.v2
539 | } else if (i == 3) {
540 | a.v3
541 | } else {
542 | abort EWORDS_OVERFLOW
543 | }
544 | }
545 |
546 | /// Get word from `DU256` by index.
547 | fun get_d(a: &DU256, i: u64): u64 {
548 | if (i == 0) {
549 | a.v0
550 | } else if (i == 1) {
551 | a.v1
552 | } else if (i == 2) {
553 | a.v2
554 | } else if (i == 3) {
555 | a.v3
556 | } else if (i == 4) {
557 | a.v4
558 | } else if (i == 5) {
559 | a.v5
560 | } else if (i == 6) {
561 | a.v6
562 | } else if (i == 7) {
563 | a.v7
564 | } else {
565 | abort EWORDS_OVERFLOW
566 | }
567 | }
568 |
569 | /// Put new word `val` into `U256` by index `i`.
570 | fun put(a: &mut U256, i: u64, val: u64) {
571 | if (i == 0) {
572 | a.v0 = val;
573 | } else if (i == 1) {
574 | a.v1 = val;
575 | } else if (i == 2) {
576 | a.v2 = val;
577 | } else if (i == 3) {
578 | a.v3 = val;
579 | } else {
580 | abort EWORDS_OVERFLOW
581 | }
582 | }
583 |
584 | /// Put new word into `DU256` by index `i`.
585 | fun put_d(a: &mut DU256, i: u64, val: u64) {
586 | if (i == 0) {
587 | a.v0 = val;
588 | } else if (i == 1) {
589 | a.v1 = val;
590 | } else if (i == 2) {
591 | a.v2 = val;
592 | } else if (i == 3) {
593 | a.v3 = val;
594 | } else if (i == 4) {
595 | a.v4 = val;
596 | } else if (i == 5) {
597 | a.v5 = val;
598 | } else if (i == 6) {
599 | a.v6 = val;
600 | } else if (i == 7) {
601 | a.v7 = val;
602 | } else {
603 | abort EWORDS_OVERFLOW
604 | }
605 | }
606 |
607 | /// Convert `DU256` to `U256`.
608 | fun du256_to_u256(a: DU256): (U256, bool) {
609 | let b = U256 {
610 | v0: a.v0,
611 | v1: a.v1,
612 | v2: a.v2,
613 | v3: a.v3,
614 | };
615 |
616 | let overflow = false;
617 | if (a.v4 != 0 || a.v5 != 0 || a.v6 != 0 || a.v7 != 0) {
618 | overflow = true;
619 | };
620 |
621 | (b, overflow)
622 | }
623 |
624 | // Tests.
625 | #[test]
626 | fun test_get_d() {
627 | let a = DU256 {
628 | v0: 1,
629 | v1: 2,
630 | v2: 3,
631 | v3: 4,
632 | v4: 5,
633 | v5: 6,
634 | v6: 7,
635 | v7: 8,
636 | };
637 |
638 | assert!(get_d(&a, 0) == 1, 0);
639 | assert!(get_d(&a, 1) == 2, 1);
640 | assert!(get_d(&a, 2) == 3, 2);
641 | assert!(get_d(&a, 3) == 4, 3);
642 | assert!(get_d(&a, 4) == 5, 4);
643 | assert!(get_d(&a, 5) == 6, 5);
644 | assert!(get_d(&a, 6) == 7, 6);
645 | assert!(get_d(&a, 7) == 8, 7);
646 | }
647 |
648 | #[test]
649 | #[expected_failure(abort_code = EWORDS_OVERFLOW)] // 1
650 | fun test_get_d_overflow() {
651 | let a = DU256 {
652 | v0: 1,
653 | v1: 2,
654 | v2: 3,
655 | v3: 4,
656 | v4: 5,
657 | v5: 6,
658 | v6: 7,
659 | v7: 8,
660 | };
661 |
662 | get_d(&a, 8);
663 | }
664 |
665 | #[test]
666 | fun test_put_d() {
667 | let a = DU256 {
668 | v0: 1,
669 | v1: 2,
670 | v2: 3,
671 | v3: 4,
672 | v4: 5,
673 | v5: 6,
674 | v6: 7,
675 | v7: 8,
676 | };
677 |
678 | put_d(&mut a, 0, 10);
679 | put_d(&mut a, 1, 20);
680 | put_d(&mut a, 2, 30);
681 | put_d(&mut a, 3, 40);
682 | put_d(&mut a, 4, 50);
683 | put_d(&mut a, 5, 60);
684 | put_d(&mut a, 6, 70);
685 | put_d(&mut a, 7, 80);
686 |
687 | assert!(get_d(&a, 0) == 10, 0);
688 | assert!(get_d(&a, 1) == 20, 1);
689 | assert!(get_d(&a, 2) == 30, 2);
690 | assert!(get_d(&a, 3) == 40, 3);
691 | assert!(get_d(&a, 4) == 50, 4);
692 | assert!(get_d(&a, 5) == 60, 5);
693 | assert!(get_d(&a, 6) == 70, 6);
694 | assert!(get_d(&a, 7) == 80, 7);
695 | }
696 |
697 | #[test]
698 | #[expected_failure(abort_code = EWORDS_OVERFLOW)]
699 | fun test_put_d_overflow() {
700 | let a = DU256 {
701 | v0: 1,
702 | v1: 2,
703 | v2: 3,
704 | v3: 4,
705 | v4: 5,
706 | v5: 6,
707 | v6: 7,
708 | v7: 8,
709 | };
710 |
711 | put_d(&mut a, 8, 0);
712 | }
713 |
714 | #[test]
715 | fun test_du256_to_u256() {
716 | let a = DU256 {
717 | v0: 255,
718 | v1: 100,
719 | v2: 50,
720 | v3: 300,
721 | v4: 0,
722 | v5: 0,
723 | v6: 0,
724 | v7: 0,
725 | };
726 |
727 | let (m, overflow) = du256_to_u256(a);
728 | assert!(!overflow, 0);
729 | assert!(m.v0 == a.v0, 1);
730 | assert!(m.v1 == a.v1, 2);
731 | assert!(m.v2 == a.v2, 3);
732 | assert!(m.v3 == a.v3, 4);
733 |
734 | a.v4 = 100;
735 | a.v5 = 5;
736 |
737 | let (m, overflow) = du256_to_u256(a);
738 | assert!(overflow, 5);
739 | assert!(m.v0 == a.v0, 6);
740 | assert!(m.v1 == a.v1, 7);
741 | assert!(m.v2 == a.v2, 8);
742 | assert!(m.v3 == a.v3, 9);
743 | }
744 |
745 | #[test]
746 | fun test_get() {
747 | let a = U256 {
748 | v0: 1,
749 | v1: 2,
750 | v2: 3,
751 | v3: 4,
752 | };
753 |
754 | assert!(get(&a, 0) == 1, 0);
755 | assert!(get(&a, 1) == 2, 1);
756 | assert!(get(&a, 2) == 3, 2);
757 | assert!(get(&a, 3) == 4, 3);
758 | }
759 |
760 | #[test]
761 | #[expected_failure(abort_code = EWORDS_OVERFLOW)]
762 | fun test_get_aborts() {
763 | let _ = get(&zero(), 4);
764 | }
765 |
766 | #[test]
767 | fun test_put() {
768 | let a = zero();
769 | put(&mut a, 0, 255);
770 | assert!(get(&a, 0) == 255, 0);
771 |
772 | put(&mut a, 1, (U64_MAX as u64));
773 | assert!(get(&a, 1) == (U64_MAX as u64), 1);
774 |
775 | put(&mut a, 2, 100);
776 | assert!(get(&a, 2) == 100, 2);
777 |
778 | put(&mut a, 3, 3);
779 | assert!(get(&a, 3) == 3, 3);
780 |
781 | put(&mut a, 2, 0);
782 | assert!(get(&a, 2) == 0, 4);
783 | }
784 |
785 | #[test]
786 | #[expected_failure(abort_code = EWORDS_OVERFLOW)]
787 | fun test_put_overflow() {
788 | let a = zero();
789 | put(&mut a, 6, 255);
790 | }
791 |
792 | #[test]
793 | fun test_from_u128() {
794 | let i = 0;
795 | while (i < 1024) {
796 | let big = from_u128(i);
797 | assert!(as_u128(big) == i, 0);
798 | i = i + 1;
799 | };
800 | }
801 |
802 | #[test]
803 | fun test_add() {
804 | let a = from_u128(1000);
805 | let b = from_u128(500);
806 |
807 | let s = as_u128(add(a, b));
808 | assert!(s == 1500, 0);
809 |
810 | a = from_u128(U64_MAX);
811 | b = from_u128(U64_MAX);
812 |
813 | s = as_u128(add(a, b));
814 | assert!(s == (U64_MAX + U64_MAX), 1);
815 | }
816 |
817 | #[test]
818 | #[expected_failure(abort_code = EOVERFLOW)]
819 | fun test_add_overflow() {
820 | let max = (U64_MAX as u64);
821 |
822 | let a = U256 {
823 | v0: max,
824 | v1: max,
825 | v2: max,
826 | v3: max
827 | };
828 |
829 | let _ = add(a, from_u128(1));
830 | }
831 |
832 | #[test]
833 | fun test_sub() {
834 | let a = from_u128(1000);
835 | let b = from_u128(500);
836 |
837 | let s = as_u128(sub(a, b));
838 | assert!(s == 500, 0);
839 | }
840 |
841 | #[test]
842 | #[expected_failure(abort_code = EOVERFLOW)]
843 | fun test_sub_overflow() {
844 | let a = from_u128(0);
845 | let b = from_u128(1);
846 |
847 | let _ = sub(a, b);
848 | }
849 |
850 | #[test]
851 | #[expected_failure(abort_code = ECAST_OVERFLOW)]
852 | fun test_too_big_to_cast_to_u128() {
853 | let a = from_u128(U128_MAX);
854 | let b = from_u128(U128_MAX);
855 |
856 | let _ = as_u128(add(a, b));
857 | }
858 |
859 | #[test]
860 | fun test_overflowing_add() {
861 | let (n, z) = overflowing_add(10, 10);
862 | assert!(n == 20, 0);
863 | assert!(!z, 1);
864 |
865 | (n, z) = overflowing_add((U64_MAX as u64), 1);
866 | assert!(n == 0, 2);
867 | assert!(z, 3);
868 |
869 | (n, z) = overflowing_add((U64_MAX as u64), 10);
870 | assert!(n == 9, 4);
871 | assert!(z, 5);
872 |
873 | (n, z) = overflowing_add(5, 8);
874 | assert!(n == 13, 6);
875 | assert!(!z, 7);
876 | }
877 |
878 | #[test]
879 | fun test_overflowing_sub() {
880 | let (n, z) = overflowing_sub(10, 5);
881 | assert!(n == 5, 0);
882 | assert!(!z, 1);
883 |
884 | (n, z) = overflowing_sub(0, 1);
885 | assert!(n == (U64_MAX as u64), 2);
886 | assert!(z, 3);
887 |
888 | (n, z) = overflowing_sub(10, 10);
889 | assert!(n == 0, 4);
890 | assert!(!z, 5);
891 | }
892 |
893 | #[test]
894 | fun test_split_u128() {
895 | let (a1, a2) = split_u128(100);
896 | assert!(a1 == 0, 0);
897 | assert!(a2 == 100, 1);
898 |
899 | (a1, a2) = split_u128(U64_MAX + 1);
900 | assert!(a1 == 1, 2);
901 | assert!(a2 == 0, 3);
902 | }
903 |
904 | #[test]
905 | fun test_mul() {
906 | let a = from_u128(285);
907 | let b = from_u128(375);
908 |
909 | let c = as_u128(mul(a, b));
910 | assert!(c == 106875, 0);
911 |
912 | a = from_u128(0);
913 | b = from_u128(1);
914 |
915 | c = as_u128(mul(a, b));
916 |
917 | assert!(c == 0, 1);
918 |
919 | a = from_u128(U64_MAX);
920 | b = from_u128(2);
921 |
922 | c = as_u128(mul(a, b));
923 |
924 | assert!(c == 36893488147419103230, 2);
925 |
926 | a = from_u128(U128_MAX);
927 | b = from_u128(U128_MAX);
928 |
929 | let z = mul(a, b);
930 | assert!(bits(&z) == 256, 3);
931 | }
932 |
933 | #[test]
934 | #[expected_failure(abort_code = EOVERFLOW)]
935 | fun test_mul_overflow() {
936 | let max = (U64_MAX as u64);
937 |
938 | let a = U256 {
939 | v0: max,
940 | v1: max,
941 | v2: max,
942 | v3: max,
943 | };
944 |
945 | let _ = mul(a, from_u128(2));
946 | }
947 |
948 | #[test]
949 | fun test_zero() {
950 | let a = as_u128(zero());
951 | assert!(a == 0, 0);
952 |
953 | let a = zero();
954 | assert!(a.v0 == 0, 1);
955 | assert!(a.v1 == 0, 2);
956 | assert!(a.v2 == 0, 3);
957 | assert!(a.v3 == 0, 4);
958 | }
959 |
960 | #[test]
961 | fun test_or() {
962 | let a = from_u128(0);
963 | let b = from_u128(1);
964 | let c = bitor(a, b);
965 | assert!(as_u128(c) == 1, 0);
966 |
967 | let a = from_u128(0x0f0f0f0f0f0f0f0fu128);
968 | let b = from_u128(0xf0f0f0f0f0f0f0f0u128);
969 | let c = bitor(a, b);
970 | assert!(as_u128(c) == 0xffffffffffffffffu128, 1);
971 | }
972 |
973 | #[test]
974 | fun test_and() {
975 | let a = from_u128(0);
976 | let b = from_u128(1);
977 | let c = bitand(a, b);
978 | assert!(as_u128(c) == 0, 0);
979 |
980 | let a = from_u128(0x0f0f0f0f0f0f0f0fu128);
981 | let b = from_u128(0xf0f0f0f0f0f0f0f0u128);
982 | let c = bitand(a, b);
983 | assert!(as_u128(c) == 0, 1);
984 |
985 | let a = from_u128(0x0f0f0f0f0f0f0f0fu128);
986 | let b = from_u128(0x0f0f0f0f0f0f0f0fu128);
987 | let c = bitand(a, b);
988 | assert!(as_u128(c) == 0x0f0f0f0f0f0f0f0fu128, 1);
989 | }
990 |
991 | #[test]
992 | fun test_xor() {
993 | let a = from_u128(0);
994 | let b = from_u128(1);
995 | let c = bitxor(a, b);
996 | assert!(as_u128(c) == 1, 0);
997 |
998 | let a = from_u128(0x0f0f0f0f0f0f0f0fu128);
999 | let b = from_u128(0xf0f0f0f0f0f0f0f0u128);
1000 | let c = bitxor(a, b);
1001 | assert!(as_u128(c) == 0xffffffffffffffffu128, 1);
1002 | }
1003 |
1004 | #[test]
1005 | fun test_from_u64() {
1006 | let a = as_u128(from_u64(100));
1007 | assert!(a == 100, 0);
1008 |
1009 | // TODO: more tests.
1010 | }
1011 |
1012 | #[test]
1013 | fun test_compare() {
1014 | let a = from_u128(1000);
1015 | let b = from_u128(50);
1016 |
1017 | let cmp = compare(&a, &b);
1018 | assert!(cmp == 2, 0);
1019 |
1020 | a = from_u128(100);
1021 | b = from_u128(100);
1022 | cmp = compare(&a, &b);
1023 |
1024 | assert!(cmp == 0, 1);
1025 |
1026 | a = from_u128(50);
1027 | b = from_u128(75);
1028 |
1029 | cmp = compare(&a, &b);
1030 | assert!(cmp == 1, 2);
1031 | }
1032 |
1033 | #[test]
1034 | fun test_leading_zeros_u64() {
1035 | let a = leading_zeros_u64(0);
1036 | assert!(a == 64, 0);
1037 |
1038 | let a = leading_zeros_u64(1);
1039 | assert!(a == 63, 1);
1040 |
1041 | // TODO: more tests.
1042 | }
1043 |
1044 | #[test]
1045 | fun test_bits() {
1046 | let a = bits(&from_u128(0));
1047 | assert!(a == 0, 0);
1048 |
1049 | a = bits(&from_u128(255));
1050 | assert!(a == 8, 1);
1051 |
1052 | a = bits(&from_u128(256));
1053 | assert!(a == 9, 2);
1054 |
1055 | a = bits(&from_u128(300));
1056 | assert!(a == 9, 3);
1057 |
1058 | a = bits(&from_u128(60000));
1059 | assert!(a == 16, 4);
1060 |
1061 | a = bits(&from_u128(70000));
1062 | assert!(a == 17, 5);
1063 |
1064 | let b = from_u64(70000);
1065 | let sh = shl(b, 100);
1066 | assert!(bits(&sh) == 117, 6);
1067 |
1068 | let sh = shl(sh, 100);
1069 | assert!(bits(&sh) == 217, 7);
1070 |
1071 | let sh = shl(sh, 100);
1072 | assert!(bits(&sh) == 0, 8);
1073 | }
1074 |
1075 | #[test]
1076 | fun test_shift_left() {
1077 | let a = from_u128(100);
1078 | let b = shl(a, 2);
1079 |
1080 | assert!(as_u128(b) == 400, 0);
1081 |
1082 | // TODO: more shift left tests.
1083 | }
1084 |
1085 | #[test]
1086 | fun test_shift_right() {
1087 | let a = from_u128(100);
1088 | let b = shr(a, 2);
1089 |
1090 | assert!(as_u128(b) == 25, 0);
1091 |
1092 | // TODO: more shift right tests.
1093 | }
1094 |
1095 | #[test]
1096 | fun test_div() {
1097 | let a = from_u128(100);
1098 | let b = from_u128(5);
1099 | let d = div(a, b);
1100 |
1101 | assert!(as_u128(d) == 20, 0);
1102 |
1103 | let a = from_u128(U64_MAX);
1104 | let b = from_u128(U128_MAX);
1105 | let d = div(a, b);
1106 | assert!(as_u128(d) == 0, 1);
1107 |
1108 | let a = from_u128(U64_MAX);
1109 | let b = from_u128(U128_MAX);
1110 | let d = div(a, b);
1111 | assert!(as_u128(d) == 0, 2);
1112 |
1113 | let a = from_u128(U128_MAX);
1114 | let b = from_u128(U64_MAX);
1115 | let d = div(a, b);
1116 | assert!(as_u128(d) == 18446744073709551617, 2);
1117 | }
1118 |
1119 | #[test]
1120 | #[expected_failure(abort_code = EDIV_BY_ZERO)]
1121 | fun test_div_by_zero() {
1122 | let a = from_u128(1);
1123 | let _z = div(a, from_u128(0));
1124 | }
1125 |
1126 | #[test]
1127 | fun test_as_u64() {
1128 | let _ = as_u64(from_u64((U64_MAX as u64)));
1129 | let _ = as_u64(from_u128(1));
1130 | }
1131 |
1132 | #[test]
1133 | #[expected_failure(abort_code = ECAST_OVERFLOW)]
1134 | fun test_as_u64_overflow() {
1135 | let _ = as_u64(from_u128(U128_MAX));
1136 | }
1137 | }
--------------------------------------------------------------------------------
/src/sea_lib/sources/u256.spec.move:
--------------------------------------------------------------------------------
1 | spec sealib::u256 {
2 | spec leading_zeros_u64 {
3 | pragma opaque;
4 | }
5 |
6 | spec split_u128 {
7 | pragma opaque;
8 | }
9 |
10 | spec div {
11 | pragma opaque;
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/sea_lib/sources/u64x64.move:
--------------------------------------------------------------------------------
1 | /// Implementation of FixedPoint u64 in Move language.
2 | module sealib::uq64x64 {
3 | // Error codes.
4 |
5 | /// When divide by zero attempted.
6 | const ERR_DIVIDE_BY_ZERO: u64 = 100;
7 |
8 | // Constants.
9 |
10 | const Q64: u128 = 18446744073709551615;
11 |
12 | /// When a and b are equals.
13 | const EQUAL: u8 = 0;
14 |
15 | /// When a is less than b equals.
16 | const LESS_THAN: u8 = 1;
17 |
18 | /// When a is greater than b.
19 | const GREATER_THAN: u8 = 2;
20 |
21 | /// The resource to store `UQ64x64`.
22 | struct UQ64x64 has copy, store, drop {
23 | v: u128
24 | }
25 |
26 | /// Encode `u64` to `UQ64x64`
27 | public fun encode(x: u64): UQ64x64 {
28 | let v = (x as u128) * Q64;
29 | UQ64x64{ v }
30 | }
31 | spec encode {
32 | ensures Q64 == MAX_U64;
33 | ensures result.v == x * Q64;
34 | ensures result.v <= MAX_U128;
35 | }
36 |
37 | /// Decode a `UQ64x64` into a `u64` by truncating after the radix point.
38 | public fun decode(uq: UQ64x64): u64 {
39 | ((uq.v / Q64) as u64)
40 | }
41 | spec decode {
42 | ensures result == uq.v / Q64;
43 | }
44 |
45 | /// Get `u128` from UQ64x64
46 | public fun to_u128(uq: UQ64x64): u128 {
47 | uq.v
48 | }
49 | spec to_u128 {
50 | ensures result == uq.v;
51 | }
52 |
53 | /// Multiply a `UQ64x64` by a `u64`, returning a `UQ64x64`
54 | public fun mul(uq: UQ64x64, y: u64): UQ64x64 {
55 | // vm would direct abort when overflow occured
56 | let v = uq.v * (y as u128);
57 |
58 | UQ64x64{ v }
59 | }
60 | spec mul {
61 | ensures result.v == uq.v * y;
62 | }
63 |
64 | /// Divide a `UQ64x64` by a `u128`, returning a `UQ64x64`.
65 | public fun div(uq: UQ64x64, y: u64): UQ64x64 {
66 | assert!(y != 0, ERR_DIVIDE_BY_ZERO);
67 |
68 | let v = uq.v / (y as u128);
69 | UQ64x64{ v }
70 | }
71 | spec div {
72 | aborts_if y == 0 with ERR_DIVIDE_BY_ZERO;
73 | ensures result.v == uq.v / y;
74 | }
75 |
76 | /// Returns a `UQ64x64` which represents the ratio of the numerator to the denominator.
77 | public fun fraction(numerator: u64, denominator: u64): UQ64x64 {
78 | assert!(denominator != 0, ERR_DIVIDE_BY_ZERO);
79 |
80 | let r = (numerator as u128) * Q64;
81 | let v = r / (denominator as u128);
82 |
83 | UQ64x64{ v }
84 | }
85 | spec fraction {
86 | aborts_if denominator == 0 with ERR_DIVIDE_BY_ZERO;
87 | ensures result.v == numerator * Q64 / denominator;
88 | }
89 |
90 | /// Compare two `UQ64x64` numbers.
91 | public fun compare(left: &UQ64x64, right: &UQ64x64): u8 {
92 | if (left.v == right.v) {
93 | return EQUAL
94 | } else if (left.v < right.v) {
95 | return LESS_THAN
96 | } else {
97 | return GREATER_THAN
98 | }
99 | }
100 | spec compare {
101 | ensures left.v == right.v ==> result == EQUAL;
102 | ensures left.v < right.v ==> result == LESS_THAN;
103 | ensures left.v > right.v ==> result == GREATER_THAN;
104 | }
105 |
106 | /// Check if `UQ64x64` is zero
107 | public fun is_zero(uq: &UQ64x64): bool {
108 | uq.v == 0
109 | }
110 | spec is_zero {
111 | ensures uq.v == 0 ==> result == true;
112 | ensures uq.v > 0 ==> result == false;
113 | }
114 | }
--------------------------------------------------------------------------------
/src/sea_lp/Move.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = 'SeaLP'
3 | version = '0.1.0'
4 |
5 | # this is the resource account
6 | [addresses]
7 | # sea_spot = "0xe9d0916444fee21d6cce8409823d9962a304db7889b2cbfd816a7c2561c631d6"
8 | sea_spot = "0x4f7d2de5a2b9797a9c74f906ef405dfc9c417da12d7d7eeff782eeb1ec896d2b"
9 |
10 | [dependencies.AptosFramework]
11 | git = "https://github.com/aptos-labs/aptos-core.git"
12 | subdir = "aptos-move/framework/aptos-framework"
13 | # rev = "563e2abff8fa997aec81abe48772af8a20731f76"
14 | # rev = "main"
15 | rev = "1.0.4"
16 |
--------------------------------------------------------------------------------
/src/sea_lp/sources/lp.move:
--------------------------------------------------------------------------------
1 | /// # Module-level documentation sections
2 | ///
3 | /// * [Background](#Background)
4 | /// * [Implementation](#Implementation)
5 | /// * [Basic public functions](#Basic-public-functions)
6 | /// * [Traversal](#Traversal)
7 | ///
8 | /// # Background
9 | ///
10 | /// spot AMM LP
11 | ///
12 | module sea_spot::lp {
13 | // LP token
14 | struct LP {}
15 | }
--------------------------------------------------------------------------------
/src/sea_mock/Move.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = 'SeaMock'
3 | version = '0.1.0'
4 |
5 | [addresses]
6 | sea = "0xf5de9e9d7a718c10964a8e5ce32de33c591979e2b2e76a1e58dcc9e6f74480df"
7 |
8 | [dependencies.AptosFramework]
9 | git = "https://github.com/aptos-labs/aptos-core.git"
10 | subdir = "aptos-move/framework/aptos-framework"
11 | # rev = "563e2abff8fa997aec81abe48772af8a20731f76"
12 | # rev = "main"
13 | rev = "1.0.4"
14 |
--------------------------------------------------------------------------------
/src/sea_mock/sources/mock_coins.move:
--------------------------------------------------------------------------------
1 | module sea::mock_coins {
2 | use std::signer;
3 | use std::string;
4 |
5 | use aptos_framework::simple_map::{Self, SimpleMap};
6 | use aptos_framework::coin;
7 |
8 | /// coins
9 | struct BTC {}
10 | struct SEA {}
11 | struct ETH {}
12 | struct USDC {}
13 | struct USDT {}
14 |
15 | const T_USDC_DECIMALS: u8 = 6;
16 | const T_USDT_DECIMALS: u8 = 4;
17 | const T_BTC_DECIMALS: u8 = 8;
18 | const T_SEA_DECIMALS: u8 = 4;
19 | const T_ETH_DECIMALS: u8 = 8;
20 |
21 | /// faucet amount
22 | const T_USDC_AMT: u64 = 10000*1000000; // 6 decimals
23 | const T_USDT_AMT: u64 = 10000*10000; // 4 decimals
24 | const T_BTC_AMT: u64 = 1*100000000; // 8 decimals
25 | const T_SEA_AMT: u64 = 100000*10000; // 4 decimals
26 | const T_ETH_AMT: u64 = 100*100000000; // 8 decimals
27 |
28 | struct FaucetAccounts has key {
29 | claimed_accounts: SimpleMap,
30 | btc_mint_cap: coin::MintCapability,
31 | sea_mint_cap: coin::MintCapability,
32 | eth_mint_cap: coin::MintCapability,
33 | usdc_mint_cap: coin::MintCapability,
34 | usdt_mint_cap: coin::MintCapability,
35 | }
36 |
37 | fun init_module(
38 | sea_admin: &signer
39 | ) {
40 | let btc_cap = create_coin(sea_admin, b"BTC", T_BTC_DECIMALS);
41 | let sea_cap = create_coin(sea_admin, b"SEA", T_SEA_DECIMALS);
42 | let eth_cap = create_coin(sea_admin, b"ETH", T_ETH_DECIMALS);
43 | let usdc_cap = create_coin(sea_admin, b"USDC", T_USDC_DECIMALS);
44 | let usdt_cap = create_coin(sea_admin, b"USDT", T_USDT_DECIMALS);
45 |
46 | move_to(sea_admin,
47 | FaucetAccounts {
48 | claimed_accounts: simple_map::create(),
49 | btc_mint_cap: btc_cap,
50 | sea_mint_cap: sea_cap,
51 | eth_mint_cap: eth_cap,
52 | usdc_mint_cap: usdc_cap,
53 | usdt_mint_cap: usdt_cap,
54 | });
55 | }
56 |
57 | fun create_coin(
58 | sea_admin: &signer,
59 | name: vector,
60 | decimals: u8,
61 | ): coin::MintCapability {
62 | let (bc, fc, mc) = coin::initialize(sea_admin,
63 | string::utf8(name),
64 | string::utf8(name),
65 | decimals,
66 | false);
67 |
68 | coin::destroy_burn_cap(bc);
69 | coin::destroy_freeze_cap(fc);
70 |
71 | mc
72 | }
73 |
74 | public entry fun claim_faucet(
75 | account: &signer,
76 | ) acquires FaucetAccounts {
77 | let fc = borrow_global_mut(@sea);
78 | let addr = signer::address_of(account);
79 |
80 | assert!(!simple_map::contains_key(&fc.claimed_accounts, &addr), 0x1);
81 | simple_map::add(&mut fc.claimed_accounts, addr, true);
82 |
83 | mint_faucet_to(account, &fc.btc_mint_cap, T_BTC_AMT);
84 | mint_faucet_to(account, &fc.sea_mint_cap, T_SEA_AMT);
85 | mint_faucet_to(account, &fc.eth_mint_cap, T_ETH_AMT);
86 | mint_faucet_to(account, &fc.usdc_mint_cap, T_USDC_AMT);
87 | mint_faucet_to(account, &fc.usdt_mint_cap, T_USDT_AMT);
88 | }
89 |
90 | fun mint_faucet_to(
91 | account: &signer,
92 | mint_cap: &coin::MintCapability,
93 | amount: u64,
94 | ) {
95 | let addr = signer::address_of(account);
96 |
97 | coin::register(account);
98 | coin::deposit(addr, coin::mint(amount, mint_cap));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/sources/amm.move:
--------------------------------------------------------------------------------
1 | /// # Module-level documentation sections
2 | ///
3 | /// * [Background](#Background)
4 | /// * [Implementation](#Implementation)
5 | /// * [Basic public functions](#Basic-public-functions)
6 | /// * [Traversal](#Traversal)
7 | ///
8 | /// # Background
9 | ///
10 | /// AMM
11 | ///
12 | module sea::amm {
13 | use std::option;
14 | use std::signer::address_of;
15 | use std::string::{Self, String};
16 | use aptos_framework::coin::{Self, Coin};
17 | use aptos_framework::timestamp;
18 | use aptos_framework::event;
19 | use aptos_framework::account;
20 |
21 | use sealib::u256;
22 | use sealib::uq64x64;
23 |
24 | use sealib::math;
25 |
26 | use sea::fee;
27 | use sea::escrow;
28 | use sea_spot::lp::{LP};
29 |
30 | // Friends >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
31 | friend sea::market;
32 |
33 | // Constants ====================================================
34 | const MIN_LIQUIDITY: u64 = 1000;
35 |
36 | // Errors ====================================================
37 | const E_NO_AUTH: u64 = 5000;
38 | const E_INITIALIZED: u64 = 5001;
39 | const E_POOL_LOCKED: u64 = 5002;
40 | const E_MIN_LIQUIDITY: u64 = 5003;
41 | const E_INSUFFICIENT_LIQUIDITY_BURNED: u64 = 5004;
42 | const E_INSUFFICIENT_INPUT_AMOUNT: u64 = 5005;
43 | const E_INSUFFICIENT_OUTPUT_AMOUNT: u64 = 5006;
44 | const ERR_K_ERROR: u64 = 5007;
45 | const E_INVALID_LOAN_PARAM: u64 = 5008;
46 | const E_INSUFFICIENT_AMOUNT: u64 = 5009;
47 | const E_PAY_LOAN_ERROR: u64 = 5010;
48 | const E_INSUFFICIENT_BASE_AMOUNT: u64 = 5011;
49 | const E_INSUFFICIENT_QUOTE_AMOUNT: u64 = 5012;
50 | const E_INTERNAL_ERROR: u64 = 5013;
51 | const E_AMM_LOCKED: u64 = 5014;
52 | const E_INVALID_DAO_FEE: u64 = 5015;
53 | const E_POOL_EXISTS: u64 = 5016;
54 |
55 | // Events ====================================================
56 | struct EventSwap has store, drop {
57 | base_in: u64,
58 | quote_in: u64,
59 | base_out: u64,
60 | quote_out: u64,
61 | pair_id: u64,
62 | fee_ratio: u64,
63 | base_reserve: u64,
64 | quote_reserve: u64,
65 | k_last: u128,
66 | timestamp: u64,
67 | }
68 |
69 | struct EventPoolUpdated has store, drop {
70 | pair_id: u64,
71 | base_reserve: u64,
72 | quote_reserve: u64,
73 | last_price_x_cumulative: u128,
74 | last_price_y_cumulative: u128,
75 | k_last: u128,
76 | timestamp: u64,
77 | }
78 |
79 | // Pool liquidity pool
80 | struct Pool has key {
81 | base_id: u64,
82 | quote_id: u64,
83 | pair_id: u64,
84 | base_reserve: Coin,
85 | quote_reserve: Coin,
86 | last_timestamp: u64,
87 | last_price_x_cumulative: u128,
88 | last_price_y_cumulative: u128,
89 | k_last: u128,
90 | lp_mint_cap: coin::MintCapability>,
91 | lp_burn_cap: coin::BurnCapability>,
92 | locked: bool,
93 | fee_ratio: u64,
94 | mining_weight: u64,
95 | event_swap: event::EventHandle,
96 | event_pool_updated: event::EventHandle,
97 | }
98 |
99 | // AMMConfig global AMM config
100 | struct AMMConfig has key {
101 | dao_fee: u64, // DAO will take 1/dao_fee from trade fee
102 | locked: bool,
103 | }
104 |
105 | // Flashloan flash loan
106 | struct Flashloan {
107 | x_loan: u64,
108 | y_loan: u64,
109 | }
110 |
111 | // initialize
112 | fun init_module(sea_admin: &signer) {
113 | initialize(sea_admin);
114 | }
115 |
116 | public fun initialize(sea_admin: &signer) {
117 | // init amm config
118 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
119 | assert!(!exists(address_of(sea_admin)), E_INITIALIZED);
120 | // let signer_cap = spot_account::retrieve_signer_cap(sea_admin);
121 | // move_to(sea_admin, SpotAccountCapability { signer_cap });
122 | move_to(sea_admin, AMMConfig {
123 | dao_fee: 10, // 1/10
124 | locked: false,
125 | });
126 | }
127 |
128 | public entry fun set_market_locked(
129 | sea_admin: &signer,
130 | locked: bool,
131 | ) acquires AMMConfig {
132 | // init amm config
133 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
134 | let ac = borrow_global_mut(@sea);
135 |
136 | ac.locked = locked
137 | }
138 |
139 | public entry fun set_market_dao_fee(
140 | sea_admin: &signer,
141 | dao_fee: u64,
142 | ) acquires AMMConfig {
143 | // init amm config
144 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
145 | assert!(dao_fee < 50, E_INVALID_DAO_FEE);
146 | let ac = borrow_global_mut(@sea);
147 |
148 | ac.dao_fee = dao_fee
149 | }
150 |
151 | // create_pool should be called by spot register_pair
152 | public(friend) fun create_pool(
153 | res_account: &signer,
154 | base_id: u64,
155 | quote_id: u64,
156 | pair_id: u64,
157 | fee_ratio: u64,
158 | ) {
159 | let (name, symbol) = get_lp_name_symbol();
160 | let (lp_burn_cap, lp_freeze_cap, lp_mint_cap) =
161 | coin::initialize>(
162 | res_account,
163 | name,
164 | symbol,
165 | 6,
166 | true
167 | );
168 | coin::destroy_freeze_cap(lp_freeze_cap);
169 |
170 | assert!(!exists>(address_of(res_account)), E_POOL_EXISTS);
171 | let pool = Pool {
172 | base_id: base_id,
173 | quote_id: quote_id,
174 | pair_id: pair_id,
175 | base_reserve: coin::zero(),
176 | quote_reserve: coin::zero(),
177 | last_timestamp: 0,
178 | last_price_x_cumulative: 0,
179 | last_price_y_cumulative: 0,
180 | k_last: 0,
181 | lp_mint_cap,
182 | lp_burn_cap,
183 | locked: false,
184 | fee_ratio: fee_ratio,
185 | mining_weight: 0,
186 |
187 | event_swap: account::new_event_handle(res_account),
188 | event_pool_updated: account::new_event_handle(res_account),
189 | };
190 | move_to(res_account, pool);
191 | coin::register>(res_account);
192 | }
193 |
194 | public entry fun modify_pool_fee(
195 | sea_admin: &signer,
196 | fee_level: u64) acquires Pool {
197 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
198 | fee::assert_fee_level_valid(fee_level);
199 | let pool = borrow_global_mut>(@sea_spot);
200 |
201 | pool.fee_ratio = fee_level;
202 | }
203 |
204 | public entry fun set_pool_weight(
205 | sea_admin: &signer,
206 | weight: u64) acquires Pool {
207 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
208 |
209 | let pool = borrow_global_mut>(@sea_spot);
210 |
211 | pool.mining_weight = weight;
212 | }
213 |
214 | public fun get_min_liquidity(): u64 {
215 | MIN_LIQUIDITY
216 | }
217 |
218 | public fun pool_exist(): bool {
219 | exists>(@sea_spot)
220 | }
221 |
222 | public fun mint(
223 | base: Coin,
224 | quote: Coin,
225 | ): Coin> acquires Pool, AMMConfig {
226 | assert_amm_unlocked();
227 | escrow::validate_pair();
228 | let pool = borrow_global_mut>(@sea_spot);
229 | assert!(pool.locked == false, E_POOL_LOCKED);
230 |
231 | mint_fee(pool);
232 |
233 | let total_supply = option::extract(&mut coin::supply>());
234 | let base_reserve = coin::value(&pool.base_reserve);
235 | let quote_reserve = coin::value(&pool.quote_reserve);
236 | let base_vol = coin::value(&base);
237 | let quote_vol = coin::value("e);
238 | let liquidity: u64;
239 | if (total_supply == 0) {
240 | liquidity = math::sqrt((base_vol as u128) * (quote_vol as u128));
241 | assert!(liquidity > MIN_LIQUIDITY, E_MIN_LIQUIDITY);
242 | liquidity = liquidity - MIN_LIQUIDITY;
243 | } else {
244 | let x_liq = (((base_vol as u128) * total_supply / (base_reserve as u128)) as u64);
245 | let y_liq = (((quote_vol as u128) * total_supply / (quote_reserve as u128)) as u64);
246 | liquidity = math::min_u64(x_liq, y_liq);
247 | };
248 | assert!(liquidity > 0, E_MIN_LIQUIDITY);
249 |
250 | coin::merge(&mut pool.base_reserve, base);
251 | coin::merge(&mut pool.quote_reserve, quote);
252 |
253 | let lp = coin::mint>(liquidity, &pool.lp_mint_cap);
254 | update_pool(pool, base_reserve, quote_reserve);
255 | // here should update k_last to last reserve
256 | pool.k_last = (coin::value(&pool.base_reserve) as u128) * (coin::value(&pool.quote_reserve) as u128);
257 |
258 | lp
259 | }
260 |
261 | public fun burn(
262 | lp: Coin>,
263 | ): (Coin, Coin) acquires Pool, AMMConfig {
264 | assert_amm_unlocked();
265 | escrow::validate_pair();
266 | let pool = borrow_global_mut>(@sea_spot);
267 | assert!(pool.locked == false, E_POOL_LOCKED);
268 | let burn_vol = coin::value(&lp);
269 |
270 | mint_fee(pool);
271 |
272 | let total_supply = option::extract(&mut coin::supply>());
273 | let base_reserve = coin::value(&pool.base_reserve);
274 | let quote_reserve = coin::value(&pool.quote_reserve);
275 |
276 | // debug::print(&total_supply);
277 | // debug::print(&base_reserve);
278 | // debug::print("e_reserve);
279 |
280 | // how much base and quote to be returned
281 | let base_to_return_val = (((burn_vol as u128) * (base_reserve as u128) / total_supply) as u64);
282 | let quote_to_return_val = (((burn_vol as u128) * (quote_reserve as u128) / total_supply) as u64);
283 | assert!(base_to_return_val > 0 && quote_to_return_val > 0, E_INSUFFICIENT_LIQUIDITY_BURNED);
284 |
285 | // Withdraw those values from reserves
286 | let base_coin_to_return = coin::extract(&mut pool.base_reserve, base_to_return_val);
287 | let quote_coin_to_return = coin::extract(&mut pool.quote_reserve, quote_to_return_val);
288 |
289 | update_pool(pool, base_reserve, quote_reserve);
290 | pool.k_last = (base_reserve as u128) * (quote_reserve as u128);
291 | coin::burn(lp, &pool.lp_burn_cap);
292 |
293 | (base_coin_to_return, quote_coin_to_return)
294 | }
295 |
296 | public fun swap(
297 | base_in: Coin,
298 | base_out: u64,
299 | quote_in: Coin,
300 | quote_out: u64,
301 | ): (Coin, Coin) acquires Pool, AMMConfig {
302 | assert_amm_unlocked();
303 | escrow::validate_pair();
304 | let pool = borrow_global_mut>(@sea_spot);
305 | assert!(pool.locked == false, E_POOL_LOCKED);
306 | assert!(base_out > 0 || quote_out > 0, E_INSUFFICIENT_OUTPUT_AMOUNT);
307 |
308 | let base_in_vol = coin::value(&base_in);
309 | let quote_in_vol = coin::value("e_in);
310 | assert!(base_in_vol > 0 || quote_in_vol > 0, E_INSUFFICIENT_INPUT_AMOUNT);
311 |
312 | let base_reserve = coin::value(&pool.base_reserve);
313 | let quote_reserve = coin::value(&pool.quote_reserve);
314 |
315 | // Deposit new coins to liquidity pool.
316 | coin::merge(&mut pool.base_reserve, base_in);
317 | coin::merge(&mut pool.quote_reserve, quote_in);
318 |
319 | let base_swaped = coin::extract(&mut pool.base_reserve, base_out);
320 | let quote_swaped = coin::extract(&mut pool.quote_reserve, quote_out);
321 |
322 | let base_balance = coin::value(&mut pool.base_reserve);
323 | let quote_balance = coin::value(&mut pool.quote_reserve);
324 |
325 | assert_k_increase(base_balance, quote_balance, base_in_vol, quote_in_vol, base_reserve, quote_reserve, pool.fee_ratio);
326 |
327 | update_pool(pool, base_reserve, quote_reserve);
328 |
329 | // emit event
330 | event::emit_event(&mut pool.event_swap, EventSwap{
331 | base_in: base_in_vol,
332 | quote_in: quote_in_vol,
333 | base_out: base_out,
334 | quote_out: quote_out,
335 | pair_id: pool.pair_id,
336 | fee_ratio: pool.fee_ratio,
337 | base_reserve: base_reserve,
338 | quote_reserve: quote_reserve,
339 | k_last: pool.k_last,
340 | timestamp: timestamp::now_seconds(),
341 | });
342 |
343 | (base_swaped, quote_swaped)
344 | }
345 |
346 | /// Calculate optimal amounts of coins to add
347 | public fun calc_optimal_coin_values(
348 | amount_base_desired: u64,
349 | amount_quote_desired: u64,
350 | amount_base_min: u64,
351 | amount_quote_min: u64
352 | ): (u64, u64) acquires Pool {
353 | let pool = borrow_global>(@sea_spot);
354 | let (reserve_base, reserve_quote) = (coin::value(&pool.base_reserve), coin::value(&pool.quote_reserve));
355 | if (reserve_base == 0 && reserve_quote == 0) {
356 | (amount_base_desired, amount_quote_desired)
357 | } else {
358 | let amount_quote_optimal = quote(amount_base_desired, reserve_base, reserve_quote);
359 | if (amount_quote_optimal <= amount_quote_desired) {
360 | assert!(amount_quote_optimal >= amount_quote_min, E_INSUFFICIENT_QUOTE_AMOUNT);
361 | (amount_base_desired, amount_quote_optimal)
362 | } else {
363 | let amount_base_optimal = quote(amount_quote_desired, reserve_quote, reserve_base);
364 | assert!(amount_base_optimal <= amount_base_desired, E_INTERNAL_ERROR);
365 | assert!(amount_base_optimal >= amount_base_min, E_INSUFFICIENT_BASE_AMOUNT);
366 | (amount_base_optimal, amount_quote_desired)
367 | }
368 | }
369 | }
370 |
371 | // Get flash swap coins. User can loan any coins, and repay in the same tx.
372 | // In most cases, user may loan one coin, and repay the same or the other coin.
373 | // require X < Y.
374 | // * `loan_coin_x` - expected amount of X coins to loan.
375 | // * `loan_coin_y` - expected amount of Y coins to loan.
376 | // Returns both loaned X and Y coins: `(Coin, Coin, Flashloan(
378 | loan_coin_x: u64,
379 | loan_coin_y: u64
380 | ): (Coin, Coin, Flashloan) acquires Pool {
381 | // assert check
382 | escrow::validate_pair();
383 | assert!(loan_coin_x > 0 || loan_coin_y > 0, E_INVALID_LOAN_PARAM);
384 |
385 | let pool = borrow_global_mut>(@sea_spot);
386 | assert!(pool.locked == false, E_POOL_LOCKED);
387 | assert!(coin::value(&pool.base_reserve) >= loan_coin_x &&
388 | coin::value(&pool.quote_reserve) >= loan_coin_y, E_INSUFFICIENT_AMOUNT);
389 | pool.locked = true;
390 |
391 | let x_loan = coin::extract(&mut pool.base_reserve, loan_coin_x);
392 | let y_loan = coin::extract(&mut pool.quote_reserve, loan_coin_y);
393 |
394 | // Return loaned amount.
395 | (x_loan, y_loan, Flashloan {x_loan: loan_coin_x, y_loan: loan_coin_y})
396 | }
397 |
398 | public fun pay_flash_swap(
399 | base_in: Coin,
400 | quote_in: Coin,
401 | flash_loan: Flashloan
402 | ) acquires Pool {
403 | // assert check
404 | escrow::validate_pair();
405 |
406 | let Flashloan { x_loan, y_loan } = flash_loan;
407 | let amount_base_in = coin::value(&base_in);
408 | let amount_quote_in = coin::value("e_in);
409 |
410 | assert!(amount_base_in > 0 || amount_quote_in > 0, E_PAY_LOAN_ERROR);
411 |
412 | let pool = borrow_global_mut>(@sea_spot);
413 | let base_reserve = coin::value(&pool.base_reserve);
414 | let quote_reserve = coin::value(&pool.quote_reserve);
415 |
416 | // reserve size before loan out
417 | base_reserve = base_reserve + x_loan;
418 | quote_reserve = quote_reserve + y_loan;
419 |
420 | coin::merge(&mut pool.base_reserve, base_in);
421 | coin::merge(&mut pool.quote_reserve, quote_in);
422 |
423 | let base_balance = coin::value(&pool.base_reserve);
424 | let quote_balance = coin::value(&pool.quote_reserve);
425 | assert_k_increase(base_balance, quote_balance, amount_base_in, amount_quote_in, base_reserve, quote_reserve, pool.fee_ratio);
426 | // update internal
427 | update_pool(pool, base_reserve, quote_reserve);
428 |
429 | pool.locked = false;
430 | }
431 |
432 | public fun get_pool_reserve_fee(): (u64, u64, u64) acquires Pool {
433 | let pool = borrow_global_mut>(@sea_spot);
434 | assert!(pool.locked == false, E_POOL_LOCKED);
435 | let base_reserve = coin::value(&pool.base_reserve);
436 | let quote_reserve = coin::value(&pool.quote_reserve);
437 |
438 | (base_reserve, quote_reserve, pool.fee_ratio)
439 | }
440 |
441 | public fun get_pool_reserve_fee_u128(): (u128, u128, u128) acquires Pool {
442 | let pool = borrow_global_mut>(@sea_spot);
443 | assert!(pool.locked == false, E_POOL_LOCKED);
444 | let base_reserve = coin::value(&pool.base_reserve);
445 | let quote_reserve = coin::value(&pool.quote_reserve);
446 |
447 | ((base_reserve as u128), (quote_reserve as u128), (pool.fee_ratio as u128))
448 | }
449 |
450 | // Private functions ====================================================
451 |
452 | fun assert_amm_unlocked() acquires AMMConfig {
453 | let ac = borrow_global(@sea);
454 | assert!(ac.locked == false, E_AMM_LOCKED);
455 | }
456 |
457 | // k should not decrease
458 | fun assert_k_increase(
459 | base_balance: u64,
460 | quote_balance: u64,
461 | base_in: u64,
462 | quote_in: u64,
463 | base_reserve: u64,
464 | quote_reserve: u64,
465 | fee: u64,
466 | ) {
467 | let fee_deno = (fee::get_fee_denominate() as u128);
468 | // debug::print(&fee_deno);
469 | let base_balance_adjusted = (base_balance as u128) * fee_deno - (base_in as u128) * (fee as u128);
470 | let quote_balance_adjusted = (quote_balance as u128) * fee_deno - (quote_in as u128) * (fee as u128);
471 | let balance_k_old_not_scaled = (base_reserve as u128) * (quote_reserve as u128);
472 | let scale = fee_deno * fee_deno;
473 |
474 | // should be: new_reserve_x * new_reserve_y > old_reserve_x * old_eserve_y
475 | // gas saving
476 | if (
477 | math::is_overflow_mul(base_balance_adjusted, quote_balance_adjusted)
478 | || math::is_overflow_mul(balance_k_old_not_scaled, scale)
479 | ) {
480 | let balance_xy_adjusted = u256::mul(u256::from_u128(base_balance_adjusted), u256::from_u128(quote_balance_adjusted));
481 | let balance_xy_old = u256::mul(u256::from_u128(balance_k_old_not_scaled), u256::from_u128(scale));
482 | assert!(u256::compare(&balance_xy_adjusted, &balance_xy_old) == 2, ERR_K_ERROR);
483 | } else {
484 | assert!(base_balance_adjusted * quote_balance_adjusted >= balance_k_old_not_scaled * scale, ERR_K_ERROR)
485 | };
486 | }
487 |
488 | fun quote(
489 | amount_base: u64,
490 | reserve_base: u64,
491 | reserve_quote: u64
492 | ): u64 {
493 | assert!(amount_base > 0, E_INSUFFICIENT_AMOUNT);
494 | assert!(reserve_base > 0 && reserve_quote > 0, E_INSUFFICIENT_AMOUNT);
495 | ((amount_base as u128) * (reserve_quote as u128) / (reserve_base as u128) as u64)
496 | }
497 |
498 | fun get_lp_name_symbol(): (String, String) {
499 | let name = string::utf8(b"LP-");
500 | string::append(&mut name, coin::symbol());
501 | string::append_utf8(&mut name, b"-");
502 | string::append(&mut name, coin::symbol());
503 |
504 | let symbol = string::utf8(b"");
505 | string::append(&mut symbol, coin_symbol_prefix());
506 | string::append_utf8(&mut symbol, b"-");
507 | string::append(&mut symbol, coin_symbol_prefix());
508 |
509 | (name, symbol)
510 | }
511 |
512 | fun coin_symbol_prefix(): String {
513 | let symbol = coin::symbol();
514 | let prefix_length = math::min_u64(string::length(&symbol), 4);
515 | string::sub_string(&symbol, 0, prefix_length)
516 | }
517 |
518 | fun update_pool(
519 | pool: &mut Pool,
520 | base_reserve: u64,
521 | quote_reserve: u64,
522 | ) {
523 | let last_ts = pool.last_timestamp;
524 | let now_ts = timestamp::now_seconds();
525 |
526 | let time_elapsed = ((now_ts - last_ts) as u128);
527 |
528 | if (time_elapsed > 0 && base_reserve != 0 && quote_reserve != 0) {
529 | let last_price_x_cumulative = uq64x64::to_u128(uq64x64::fraction(quote_reserve, base_reserve)) * time_elapsed;
530 | let last_price_y_cumulative = uq64x64::to_u128(uq64x64::fraction(base_reserve, quote_reserve)) * time_elapsed;
531 |
532 | pool.last_price_x_cumulative = pool.last_price_x_cumulative + last_price_x_cumulative;
533 | pool.last_price_y_cumulative = pool.last_price_y_cumulative + last_price_y_cumulative;
534 | };
535 |
536 | pool.last_timestamp = now_ts;
537 | event::emit_event(&mut pool.event_pool_updated, EventPoolUpdated{
538 | pair_id: pool.pair_id,
539 | base_reserve: coin::value(&pool.base_reserve),
540 | quote_reserve: coin::value(&pool.quote_reserve),
541 | last_price_x_cumulative: pool.last_price_x_cumulative,
542 | last_price_y_cumulative: pool.last_price_y_cumulative,
543 | k_last: pool.k_last,
544 | timestamp: now_ts,
545 | });
546 | }
547 |
548 | fun mint_fee(
549 | pool: &mut Pool,
550 | ) acquires AMMConfig {
551 | let dao_fee = (borrow_global(@sea).dao_fee as u128);
552 | let k_last = pool.k_last;
553 | let base_reserve = coin::value(&pool.base_reserve);
554 | let quote_reserve = coin::value(&pool.quote_reserve);
555 |
556 | if (k_last != 0) {
557 | let root_k = math::sqrt_u128((base_reserve as u128) * (quote_reserve as u128));
558 | let root_k_last = math::sqrt_u128(k_last);
559 | let total_supply = option::extract(&mut coin::supply>());
560 | if (root_k > root_k_last) {
561 | let delta_k = (root_k - root_k_last);
562 | let liquidity;
563 | if (math::is_overflow_mul(total_supply, delta_k)) {
564 | let numerator = u256::mul(u256::from_u128(total_supply), u256::from_u128(delta_k));
565 | let denominator = u256::from_u128(root_k * dao_fee + root_k_last);
566 | liquidity = u256::as_u64(u256::div(numerator, denominator));
567 | } else {
568 | let numerator = total_supply * delta_k;
569 | let denominator = root_k * dao_fee + root_k_last;
570 | liquidity = ((numerator / denominator) as u64);
571 | };
572 | if (liquidity > 0) {
573 | let coins = coin::mint>(liquidity, &pool.lp_mint_cap);
574 | coin::deposit(@sea_spot, coins);
575 | }
576 | }
577 | };
578 | pool.k_last = (base_reserve as u128) * (quote_reserve as u128);
579 | }
580 |
581 | // Test-only functions ====================================================
582 | // Tests ==================================================================
583 | }
584 |
--------------------------------------------------------------------------------
/src/sources/escrow.move:
--------------------------------------------------------------------------------
1 | /// # Module-level documentation sections
2 | ///
3 | /// * [Background](#Background)
4 | /// * [Implementation](#Implementation)
5 | /// * [Basic public functions](#Basic-public-functions)
6 | /// * [Traversal](#Traversal)
7 | ///
8 | /// # Background
9 | ///
10 | /// escrow account, escrow assets
11 | ///
12 | module sea::escrow {
13 | use std::signer::address_of;
14 | use aptos_framework::coin;
15 | use aptos_framework::account::{Self, SignerCapability};
16 | use aptos_std::type_info::{Self, TypeInfo};
17 | use aptos_std::table::{Self, Table};
18 |
19 | use sea::spot_account;
20 | use sea::events;
21 | use sea::mining;
22 | use sea::utils;
23 | use sea::sea::SEA;
24 |
25 | // Friends >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
26 | friend sea::market;
27 | friend sea::amm;
28 | friend sea::router;
29 | friend sea::aggregator;
30 |
31 | // Constants ====================================================
32 | const E_NO_AUTH: u64 = 6000;
33 | const E_COIN_NOT_EQUAL: u64 = 6001;
34 | const E_NO_ESCROW_ASSET: u64 = 6002;
35 | const E_ACCOUNT_REGISTERED: u64 = 6003;
36 | const E_QUOTE_PRIORITY: u64 = 6004;
37 |
38 | struct EscrowAccountAsset has key {
39 | n_coin: u64,
40 | n_quote: u64,
41 | n_account: u64,
42 | coin_map: Table,
43 | quote_map: Table,
44 | address_map: Table,
45 | account_map: Table,
46 | }
47 |
48 | /// Stores resource account signer capability under Liquidswap account.
49 | struct SpotEscrowAccountCapability has key {
50 | signer_cap: SignerCapability
51 | }
52 |
53 | fun init_module(sea_admin: &signer) {
54 | initialize(sea_admin);
55 | }
56 |
57 | public fun initialize(sea_admin: &signer) {
58 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
59 | move_to(sea_admin, EscrowAccountAsset {
60 | n_coin: 0,
61 | n_quote: 0,
62 | n_account: 0,
63 | coin_map: table::new(),
64 | quote_map: table::new(),
65 | // assets_map: table::new(),
66 | address_map: table::new(),
67 | account_map: table::new(),
68 | });
69 |
70 | // the resource account signer
71 | let signer_cap = spot_account::retrieve_signer_cap(sea_admin);
72 | move_to(sea_admin, SpotEscrowAccountCapability { signer_cap });
73 | }
74 |
75 | public entry fun register_account(account: &signer, referer_addr: address) acquires EscrowAccountAsset {
76 | let addr = address_of(account);
77 | let ref = borrow_global_mut(@sea);
78 | assert!(!table::contains(&ref.address_map, addr), E_ACCOUNT_REGISTERED);
79 | let account_id: u64 = ref.n_account + 1;
80 | ref.n_account = account_id;
81 | table::add(&mut ref.address_map, addr, account_id);
82 | table::add(&mut ref.account_map, account_id, addr);
83 |
84 | // user mining info
85 | mining::init_user_mint_info(account, referer_addr);
86 |
87 | // create SEA token
88 | utils::register_coin_if_not_exist(account);
89 |
90 | events::emit_account_event(account_id, addr);
91 | }
92 |
93 | /*
94 | // get account escrow coin available
95 | public fun escrow_available(
96 | addr: address
97 | ): u64 acquires AccountEscrow {
98 | if (!exists>(addr)) {
99 | return 0
100 | };
101 | let ref = borrow_global>(addr);
102 | coin::value(&ref.available)
103 | }
104 |
105 | public fun check_init_account_escrow(
106 | account: &signer
107 | ) {
108 | let account_addr = address_of(account);
109 |
110 | if (!exists>(account_addr)) {
111 | move_to(account, AccountEscrow{
112 | available: coin::zero(),
113 | // frozen: coin::zero(),
114 | });
115 | }
116 | }
117 |
118 | public fun deposit(
119 | account: &signer,
120 | amount: u64,
121 | // is_frozen: bool
122 | ) acquires AccountEscrow {
123 | let account_addr = address_of(account);
124 | if (exists>(account_addr)) {
125 | let current = borrow_global_mut>(account_addr);
126 | // if (is_frozen) {
127 | // coin::merge(&mut current.frozen, coin::withdraw(account, amount));
128 | // } else {
129 | coin::merge(&mut current.available, coin::withdraw(account, amount));
130 | // }
131 | } else {
132 | // if (is_frozen) {
133 | // move_to(account, AccountEscrow{
134 | // available: coin::zero(),
135 | // frozen: coin::withdraw(account, amount),
136 | // });
137 | // } else {
138 | move_to(account, AccountEscrow{
139 | available: coin::withdraw(account, amount),
140 | // frozen: coin::zero(),
141 | });
142 | // }
143 | };
144 | // if (table::contains(&escrow_ref.assets_map, asset_id)) {
145 | // let asset_ref_mut = table::borrow_mut(&mut escrow_ref.assets_map, asset_id);
146 | // *asset_ref_mut = *asset_ref_mut + amount;
147 | // } else {
148 | // table::add(&mut escrow_ref.assets_map, asset_id, amount);
149 | // };
150 | }
151 |
152 | // withdraw
153 | public fun withdraw(
154 | account: &signer,
155 | amount: u64,
156 | ) acquires AccountEscrow {
157 | let account_addr = address_of(account);
158 | assert!(exists>(account_addr), E_NO_ESCROW_ASSET);
159 | let escrow_ref = borrow_global_mut>(account_addr);
160 |
161 | coin::deposit(account_addr, coin::extract(&mut escrow_ref.available, amount));
162 | // let coin_id = get_coin_id();
163 | // let account_addr = address_of(account);
164 | // let escrow_ref = borrow_global_mut(@sea);
165 | // let account_id = table::borrow(&escrow_ref.address_map, account_addr);
166 | // let asset_id = account_asset_id(*account_id, coin_id);
167 | // let escrow_amt = table::borrow_mut(&mut escrow_ref.assets_map, asset_id);
168 | // let escrower = get_spot_account();
169 | // if (amount > *escrow_amt) {
170 | // coin::transfer(&escrower, account_addr, *escrow_amt);
171 | // } else {
172 | // coin::transfer(&escrower, account_addr, amount);
173 | // };
174 | }
175 | */
176 |
177 | public fun is_quote_coin(): bool acquires EscrowAccountAsset {
178 | let info = type_info::type_of();
179 | let coinlist = borrow_global(@sea);
180 | table::contains(&coinlist.quote_map, info)
181 | }
182 |
183 | public fun get_account_id(addr: address): u64 acquires EscrowAccountAsset {
184 | let escrow_ref = borrow_global(@sea);
185 | *table::borrow(
186 | &escrow_ref.address_map,
187 | addr
188 | )
189 | }
190 |
191 | public fun get_account_addr_by_id(id: u64): address acquires EscrowAccountAsset {
192 | let escrow_ref = borrow_global(@sea);
193 | *table::borrow(
194 | &escrow_ref.account_map,
195 | id
196 | )
197 | }
198 |
199 | // validate pair
200 | public fun validate_pair() acquires EscrowAccountAsset {
201 | let base_id = get_coin_id();
202 | let quote_id = get_coin_id();
203 |
204 | assert!(quote_id < base_id, E_QUOTE_PRIORITY);
205 | }
206 |
207 | public(friend) fun get_or_register_account_id(addr: address): u64 acquires EscrowAccountAsset {
208 | let ref = borrow_global_mut(@sea);
209 | if (!table::contains(&ref.address_map, addr)) {
210 | let account_id: u64 = ref.n_account + 1;
211 | ref.n_account = account_id;
212 | table::add(&mut ref.address_map, addr, account_id);
213 | table::add(&mut ref.account_map, account_id, addr);
214 | events::emit_account_event(account_id, addr);
215 |
216 | account_id
217 | } else {
218 | *table::borrow(
219 | &ref.address_map,
220 | addr
221 | )
222 | }
223 | }
224 |
225 | public(friend) fun get_spot_account(): signer acquires SpotEscrowAccountCapability {
226 | let spot_cap = borrow_global(@sea);
227 | account::create_signer_with_capability(&spot_cap.signer_cap)
228 | }
229 |
230 | // available -> frozen
231 | // available -= amount
232 | // frozen += amount
233 | // public(friend) fun transfer_to_frozen(
234 | // addr: address,
235 | // amount: u64,
236 | // ) acquires AccountEscrow {
237 | // let escrow_ref = borrow_global_mut>(addr);
238 | // // assert!();
239 | // coin::merge(&mut escrow_ref.frozen, coin::extract(&mut escrow_ref.available, amount))
240 | // }
241 |
242 | // public(friend) fun transfer_from_frozen(
243 | // addr: address,
244 | // amount: u64,
245 | // ) acquires AccountEscrow {
246 | // let escrow_ref = borrow_global_mut>(addr);
247 | // coin::merge(&mut escrow_ref.available, coin::extract(&mut escrow_ref.frozen, amount))
248 | // }
249 |
250 | // increase the escrow account coin
251 | // public(friend) fun incr_escrow_coin(
252 | // addr: address,
253 | // amt: Coin,
254 | // // is_frozen: bool
255 | // ) acquires AccountEscrow {
256 | // let escrow_ref = borrow_global_mut>(addr);
257 | // // if (is_frozen) {
258 | // // coin::merge(&mut escrow_ref.frozen, amt);
259 | // // } else {
260 | // coin::merge(&mut escrow_ref.available, amt);
261 | // // }
262 | // }
263 |
264 | // public(friend) fun dec_escrow_coin(
265 | // addr: address,
266 | // amt: u64,
267 | // // is_frozen: bool
268 | // ): Coin acquires AccountEscrow {
269 | // let escrow_ref = borrow_global_mut>(addr);
270 | // // if (is_frozen) {
271 | // // coin::extract(&mut escrow_ref.frozen, amt)
272 | // // } else {
273 | // coin::extract(&mut escrow_ref.available, amt)
274 | // // }
275 | // }
276 |
277 | public(friend) fun get_or_register_coin_id(
278 | is_quote: bool,
279 | ): u64 acquires EscrowAccountAsset,
280 | SpotEscrowAccountCapability {
281 | let coinlist = borrow_global_mut(@sea);
282 | let info = type_info::type_of();
283 |
284 | if (table::contains(&coinlist.coin_map, info)) {
285 | let cid = table::borrow(&coinlist.coin_map, info);
286 | return *cid
287 | };
288 | // register coin for spot account
289 | coin::register(&get_spot_account());
290 | coinlist.n_coin = coinlist.n_coin + 1;
291 | let id = coinlist.n_coin;
292 | if (is_quote) {
293 | coinlist.n_quote = coinlist.n_quote + 1;
294 | table::add(&mut coinlist.quote_map, info, id);
295 | };
296 | table::add(&mut coinlist.coin_map, info, id);
297 | events::emit_coin_event(id);
298 |
299 | id
300 | }
301 |
302 | // Private functions ====================================================
303 |
304 | fun account_asset_id(account_id: u64, coin_id: u64): u64 {
305 | (account_id << 32) | (coin_id & 0xffffffff)
306 | }
307 |
308 | fun get_coin_id(): u64 acquires EscrowAccountAsset {
309 | let escrow_ref = borrow_global_mut(@sea);
310 | *table::borrow(
311 | &escrow_ref.coin_map,
312 | type_info::type_of())
313 | }
314 | }
--------------------------------------------------------------------------------
/src/sources/events.move:
--------------------------------------------------------------------------------
1 | /// # Module-level documentation sections
2 | ///
3 | /// * [Background](#Background)
4 | /// * [Implementation](#Implementation)
5 | /// * [Basic public functions](#Basic-public-functions)
6 | /// * [Traversal](#Traversal)
7 | ///
8 | /// # Background
9 | ///
10 | module sea::events {
11 | use std::signer::address_of;
12 | use std::string::{String};
13 | use aptos_framework::event::{Self, EventHandle};
14 | use aptos_std::type_info::{Self, TypeInfo};
15 | use aptos_framework::account;
16 | use aptos_framework::coin;
17 |
18 | friend sea::escrow;
19 | friend sea::market;
20 |
21 | const E_NO_AUTH: u64 = 10;
22 | const E_INITIALIZED: u64 = 11;
23 |
24 | // event pair
25 | struct EventPair has store, drop {
26 | base: TypeInfo,
27 | quote: TypeInfo,
28 | fee_ratio: u64,
29 | base_id: u64,
30 | quote_id: u64,
31 | pair_id: u64,
32 | lot_size: u64,
33 | price_ratio: u64,
34 | price_coefficient: u64,
35 | base_decimals: u8,
36 | quote_decimals: u8,
37 | }
38 |
39 | // event register quote
40 | struct EventQuote has store, drop {
41 | coin_info: TypeInfo,
42 | name: String,
43 | symbol: String,
44 | decimals: u8,
45 | coin_id: u64,
46 | min_notional: u64,
47 | }
48 |
49 | struct EventAccount has store, drop {
50 | account_id: u64,
51 | account_addr: address,
52 | }
53 |
54 | struct EventCoin has store, drop {
55 | coin_id: u64,
56 | name: String,
57 | symbol: String,
58 | decimals: u8,
59 | coin_info: TypeInfo,
60 | }
61 |
62 | struct EventContainer has key {
63 | event_pairs: EventHandle,
64 | event_quotes: EventHandle,
65 | event_coins: EventHandle,
66 | event_accounts: EventHandle,
67 | }
68 |
69 | fun init_module(sea_admin: &signer) {
70 | initialize(sea_admin);
71 | }
72 |
73 | public fun initialize(sea_admin: &signer) {
74 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
75 | assert!(!exists(address_of(sea_admin)), E_INITIALIZED);
76 |
77 | move_to(sea_admin, EventContainer {
78 | event_pairs: account::new_event_handle(sea_admin),
79 | event_quotes: account::new_event_handle(sea_admin),
80 | event_coins: account::new_event_handle(sea_admin),
81 | event_accounts: account::new_event_handle(sea_admin),
82 | });
83 | }
84 |
85 | public(friend) fun emit_pair_event(
86 | fee_ratio: u64,
87 | base_id: u64,
88 | quote_id: u64,
89 | pair_id: u64,
90 | lot_size: u64,
91 | price_ratio: u64,
92 | price_coefficient: u64,
93 | ) acquires EventContainer {
94 | let container = borrow_global_mut(@sea);
95 |
96 | event::emit_event(
97 | &mut container.event_pairs,
98 | EventPair{
99 | base: type_info::type_of(),
100 | quote: type_info::type_of(),
101 | fee_ratio: fee_ratio,
102 | base_id: base_id,
103 | quote_id: quote_id,
104 | pair_id: pair_id,
105 | lot_size: lot_size,
106 | price_ratio: price_ratio,
107 | price_coefficient: price_coefficient,
108 | base_decimals: coin::decimals(),
109 | quote_decimals: coin::decimals(),
110 | },
111 | );
112 | }
113 |
114 | public(friend) fun emit_quote_event(
115 | coin_id: u64,
116 | min_notional: u64,
117 | ) acquires EventContainer {
118 | let container = borrow_global_mut(@sea);
119 |
120 | event::emit_event(
121 | &mut container.event_quotes,
122 | EventQuote{
123 | coin_info: type_info::type_of(),
124 | coin_id: coin_id,
125 | name: coin::name(),
126 | symbol: coin::symbol(),
127 | decimals: coin::decimals(),
128 | min_notional: min_notional,
129 | },
130 | );
131 | }
132 |
133 | public(friend) fun emit_coin_event(
134 | coin_id: u64,
135 | ) acquires EventContainer {
136 | let container = borrow_global_mut(@sea);
137 |
138 | event::emit_event(
139 | &mut container.event_coins,
140 | EventCoin{
141 | coin_info: type_info::type_of(),
142 | name: coin::name(),
143 | symbol: coin::symbol(),
144 | decimals: coin::decimals(),
145 | coin_id: coin_id,
146 | },
147 | );
148 | }
149 |
150 | public(friend) fun emit_account_event(
151 | account_id: u64,
152 | account_addr: address,
153 | ) acquires EventContainer {
154 | let container = borrow_global_mut(@sea);
155 |
156 | event::emit_event(
157 | &mut container.event_accounts,
158 | EventAccount{
159 | account_id: account_id,
160 | account_addr: account_addr,
161 | },
162 | );
163 | }
164 |
165 | // #[test]
166 | // fun test_events() {
167 | // let sea_admin = aptos_framework::account::create_account_for_test(@sea);
168 | // initialize(&sea_admin);
169 | // }
170 | }
171 |
--------------------------------------------------------------------------------
/src/sources/fee.move:
--------------------------------------------------------------------------------
1 | //
2 | // fee denominator is 1000000
3 | module sea::fee {
4 | use std::signer::address_of;
5 | // use aptos_std::type_info;
6 | use aptos_std::table;
7 |
8 | // // fee ratio 0.02%
9 | // struct FeeRatio200 has store {}
10 |
11 | // // fee ratio 0.05%
12 | // struct FeeRatio500 has store {}
13 |
14 | // // fee ratio 0.1%
15 | // struct FeeRatio1000 has store {}
16 |
17 | struct MakerProportion has key {
18 | grid_proportion: u128,
19 | order_proportion: u128,
20 | }
21 |
22 | struct FeeConfig has key {
23 | fee_levels: table::Table,
24 | }
25 |
26 | const PROP_DENOMINATE: u128 = 1000;
27 | const FEE_DENOMINATE: u64 = 1000000;
28 |
29 | /// Errors
30 | const E_NO_FEE_RATIO: u64 = 4000;
31 | const E_NO_AUTH: u64 = 4001;
32 | const E_INVALID_SHARE: u64 = 4002;
33 | const E_INVALID_FEE_LEVEL: u64 = 4003;
34 |
35 | fun init_module(sea_admin: &signer) {
36 | initialize(sea_admin);
37 | }
38 |
39 | public fun initialize(sea_admin: &signer) {
40 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
41 |
42 | let fee: table::Table = table::new();
43 | table::add(&mut fee, 100, true);
44 | table::add(&mut fee, 200, true);
45 | table::add(&mut fee, 500, true);
46 | table::add(&mut fee, 1000, true);
47 |
48 | move_to(sea_admin, FeeConfig{
49 | fee_levels: fee,
50 | });
51 | move_to(sea_admin, MakerProportion{
52 | grid_proportion: 600, // 400
53 | order_proportion: 300, // 50
54 | })
55 | }
56 |
57 | public entry fun assert_fee_level_valid(fee_level: u64) acquires FeeConfig {
58 | let fee_conf = borrow_global(@sea);
59 | assert!(table::contains(&fee_conf.fee_levels, fee_level), E_INVALID_FEE_LEVEL);
60 | }
61 |
62 | public entry fun modify_maker_port(
63 | sea_admin: &signer,
64 | grid: u64,
65 | order: u64) acquires MakerProportion {
66 | assert!(address_of(sea_admin) == @sea, E_NO_AUTH);
67 | assert!(grid <= (PROP_DENOMINATE as u64), E_INVALID_SHARE);
68 | assert!(order <= (PROP_DENOMINATE as u64), E_INVALID_SHARE);
69 |
70 | let prop = borrow_global_mut(@sea);
71 | prop.grid_proportion = (grid as u128);
72 | prop.order_proportion = (order as u128);
73 | }
74 |
75 | // maker fee shares
76 | // ratio is maker's proportion
77 | // return: (maker return fee, platform fee)
78 | public fun get_maker_fee_shares(
79 | fee: u64,
80 | is_grid: bool
81 | ): (u64, u64) acquires MakerProportion {
82 | let prop = borrow_global(@sea);
83 | let ratio = if (is_grid) prop.grid_proportion else prop.order_proportion;
84 |
85 | let maker_share = (((fee as u128) * ratio / PROP_DENOMINATE) as u64);
86 | (maker_share, fee-maker_share)
87 | }
88 |
89 | /// get fee ratio by type
90 | // public fun get_fee_ratio(): u64 {
91 | // if (type_info::type_of() == type_info::type_of()) {
92 | // return 200
93 | // } else if (type_info::type_of() == type_info::type_of()) {
94 | // return 500
95 | // } else if (type_info::type_of() == type_info::type_of()) {
96 | // return 1000
97 | // };
98 |
99 | // assert!(false, E_NO_FEE_RATIO);
100 | // 0
101 | // }
102 |
103 | // fee denominate
104 | public fun get_fee_denominate(): u64 {
105 | FEE_DENOMINATE
106 | }
107 |
108 | // Tests ==================================================================
109 | #[test_only]
110 | use std::vector;
111 | // #[test_only]
112 | // use std::debug;
113 |
114 | // #[test]
115 | // fun test_get_fee_ratio() {
116 | // let fee200 = get_fee_ratio();
117 | // assert!(fee200 == 200, 200);
118 | // let fee500 = get_fee_ratio();
119 | // assert!(fee500 == 500, 500);
120 | // let fee1000 = get_fee_ratio();
121 | // assert!(fee1000 == 1000, 1000);
122 | // }
123 |
124 | #[test(sea_admin = @sea)]
125 | fun test_modify_fee_share(
126 | sea_admin: &signer
127 | ) acquires MakerProportion {
128 | initialize(sea_admin);
129 | modify_maker_port(sea_admin, 800, 200);
130 | let prop = borrow_global(@sea);
131 | assert!(prop.grid_proportion == 800, 800);
132 | assert!(prop.order_proportion == 200, 200);
133 | }
134 |
135 | #[test(
136 | sea_admin = @sea,
137 | account = @user_1
138 | )]
139 | #[expected_failure(abort_code = E_NO_AUTH)] // 4001
140 | fun test_modify_fee_share_unauth(
141 | sea_admin: &signer,
142 | account: &signer
143 | ) acquires MakerProportion {
144 | initialize(sea_admin);
145 | modify_maker_port(account, 800, 200);
146 | let prop = borrow_global(@sea);
147 | assert!(prop.grid_proportion == 800, 800);
148 | assert!(prop.order_proportion == 200, 200);
149 | }
150 |
151 | #[test(sea_admin = @sea)]
152 | fun test_get_maker_fee_shares(
153 | sea_admin: &signer
154 | ) acquires MakerProportion {
155 | initialize(sea_admin);
156 | modify_maker_port(sea_admin, 400, 50);
157 |
158 | let fee_fixtures = vector>[
159 | vector[1, 0, 1, 0, 1],
160 | vector[2, 0, 2, 0, 2],
161 | vector[3, 1, 2, 0, 3],
162 | vector[4, 1, 3, 0, 4],
163 | vector[5, 2, 3, 0, 5],
164 | vector[6, 2, 4, 0, 6],
165 | vector[7, 2, 5, 0, 7],
166 | vector[8, 3, 5, 0, 8],
167 | vector