├── .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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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[9, 3, 6, 0, 9], 168 | vector[10, 4, 6, 0, 10], 169 | vector[50, 20, 30, 2, 48], 170 | ]; 171 | let i = 0; 172 | while (i < vector::length(&fee_fixtures)) { 173 | let item = vector::borrow(&fee_fixtures, i); 174 | let total = *vector::borrow(item, 0); 175 | let maker_grid_part = *vector::borrow(item, 1); 176 | let plat_grid_part = *vector::borrow(item, 2); 177 | let maker_order_part = *vector::borrow(item, 3); 178 | let plat_order_part = *vector::borrow(item, 4); 179 | let (maker_grid_fee, plat_grid_fee) = get_maker_fee_shares(total, true); 180 | // debug::print(&maker_grid_fee); 181 | // debug::print(&maker_grid_part); 182 | assert!(maker_grid_fee == maker_grid_part, i*100+1); 183 | assert!(plat_grid_fee == plat_grid_part, i*100+2); 184 | let (maker_order_fee, plat_order_fee) = get_maker_fee_shares(total, false); 185 | assert!(maker_order_fee == maker_order_part, i*100+3); 186 | assert!(plat_order_fee == plat_order_part, i*100+4); 187 | 188 | i = i + 1; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/sources/grid.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 grid 11 | /// 12 | module sea::grid { 13 | use sea::price; 14 | use sea::utils; 15 | 16 | const BUY: u8 = 1; 17 | const SELL: u8 = 2; 18 | const PRICE_DENOMINATE_64: u64 = 100000; 19 | const PRICE_DENOMINATE_128: u128 = 100000; 20 | // arithmetic: price_diff = (grid_upper_limit - grid_lower_limit) / grid_count 21 | 22 | // fun is_grid_arith_mode(opts: u64): bool { 23 | // (opts & 0x01) == GRID_OPTS_ARITH 24 | // } 25 | 26 | // fun is_grid_base_equal(opts: u64): bool { 27 | // (opts & 0x2) == GRID_OPTS_EQUAL_BASE 28 | // } 29 | 30 | // sell orders: next level's price higher 31 | // buy orders: next level's price lower 32 | public fun next_level_price( 33 | side: u8, 34 | arithmetic: bool, 35 | price: u64, 36 | delta: u64, 37 | ): u64 { 38 | let nprice; 39 | 40 | if (arithmetic) { 41 | nprice = if (side == BUY) price - delta else price + delta; 42 | } else { 43 | if (side == BUY) { 44 | // buy 45 | nprice = (((price as u128) * PRICE_DENOMINATE_128 / ((PRICE_DENOMINATE_64 + delta) as u128)) as u64) 46 | } else { 47 | // flip order is sell order 48 | nprice = (((price as u128) * ((PRICE_DENOMINATE_64 + delta) as u128) / PRICE_DENOMINATE_128) as u64) 49 | }; 50 | }; 51 | 52 | price::to_valid_price(nprice) 53 | } 54 | 55 | public fun next_level_qty( 56 | base_equal: bool, 57 | qty: u64, 58 | price: u64, 59 | price_ratio: u64, 60 | lot_size: u64, 61 | ): u64 { 62 | let nqty; 63 | 64 | if (base_equal) { 65 | nqty = qty; 66 | } else { 67 | nqty = utils::calc_base_qty(qty, price, price_ratio); 68 | }; 69 | if (lot_size > 0) { 70 | nqty = nqty / lot_size * lot_size; 71 | }; 72 | 73 | nqty 74 | } 75 | 76 | // side: the flip order's side: 1: BUY; 2: SELL 77 | // return: (price, base_qty, quote_qty) 78 | public fun calc_grid_order_price_qty( 79 | side: u8, 80 | arithmetic: bool, 81 | price: u64, 82 | price_delta: u64, 83 | volume: u64, 84 | // quote_amt: u64, 85 | price_ratio: u64, 86 | lot_size: u64, 87 | ): (u64, u64, u64) { 88 | let nprice; 89 | let base_qty; 90 | let quote_qty = 0; 91 | 92 | // price 93 | if (arithmetic) { 94 | nprice = if (side == BUY) price - price_delta else price + price_delta; 95 | } else { 96 | if (side == BUY) { 97 | // buy 98 | nprice = (((price as u128) * PRICE_DENOMINATE_128 / ((PRICE_DENOMINATE_64 + price_delta) as u128)) as u64) 99 | } else { 100 | // flip order is sell order 101 | nprice = (((price as u128) * ((PRICE_DENOMINATE_64 + price_delta) as u128) / PRICE_DENOMINATE_128) as u64) 102 | }; 103 | nprice = price::to_valid_price(nprice); 104 | }; 105 | // base qty 106 | if (side == BUY) { 107 | // buy order 108 | base_qty = ((((volume as u128) * (price_ratio as u128)/ (nprice as u128)) as u64) / lot_size) * lot_size; 109 | quote_qty = (((nprice as u128) * (base_qty as u128) / (price_ratio as u128)) as u64); 110 | } else { 111 | // sell order 112 | base_qty = (volume / lot_size) * lot_size 113 | }; 114 | 115 | (nprice, base_qty, quote_qty) 116 | } 117 | 118 | #[test] 119 | fun test_grid_flip_eth() { 120 | use std::vector; 121 | // use std::debug; 122 | 123 | // eth/usdc price is about 1000 124 | // usdc is 6 decimals, eth is 8 decimals 125 | let price_ratio = 100000000000; 126 | let lot_size = 100000; 127 | let prices: vector = vector[45, 500, 612, 999, 1001, 1215, 4587, 10006]; 128 | // 20, 12, 8, 5, 3, 2, 1.8, 1.6, 1.5, 1.4, 1.3, 1.2, 1.15, 1.1, 1.05, 1.04, 1.01 129 | let ratios: vector = vector[1900000, 1100000, 700000, 400000, 200000, 100000, 80000, 130 | 60000, 50000, 40000, 30000, 20000, 15000, 10000, 5000, 4000, 1000]; 131 | // 0.1 0.2345, 1.0586 132 | let bases: vector = vector[10000000, 23450000, 105860000]; // flip to sell orders 133 | // 100, 1000, 5000 134 | let quotes: vector = vector[100000000, 1000000000, 5000000000]; // flip to buy orders 135 | 136 | let i: u64 = 0; 137 | let j: u64 = 0; 138 | let k: u64 = 0; 139 | // side = 2 140 | while (i < vector::length(&bases)) { 141 | let base = vector::borrow(&mut bases, i); 142 | while (j < vector::length(&ratios)) { 143 | let ratio = vector::borrow(&mut ratios, j); 144 | while (k < vector::length(&prices)) { 145 | let price = vector::borrow(&mut prices, k); 146 | 147 | let (nprice, base_qty, quote_qty) = calc_grid_order_price_qty(2, 148 | true, 149 | *price * price_ratio, 150 | *ratio, 151 | *base, 152 | price_ratio, 153 | lot_size 154 | ); 155 | 156 | nprice; 157 | base_qty; 158 | quote_qty; 159 | // debug::print(&nprice); 160 | // debug::print(&base_qty); 161 | // debug::print("e_qty); 162 | k = k +1; 163 | }; 164 | k = 0; 165 | j = j + 1; 166 | }; 167 | j = 0; 168 | i = i + 1; 169 | }; 170 | 171 | // side = 1 172 | i = 0; 173 | j = 0; 174 | k = 0; 175 | while (i < vector::length("es)) { 176 | let quote = vector::borrow(&mut quotes, i); 177 | while (j < vector::length(&ratios)) { 178 | let ratio = vector::borrow(&mut ratios, j); 179 | while (k < vector::length(&prices)) { 180 | let price = vector::borrow(&mut prices, k); 181 | 182 | let (nprice, base_qty, quote_qty) = calc_grid_order_price_qty(1, 183 | true, 184 | *price * price_ratio, 185 | *ratio, 186 | *quote, 187 | price_ratio, 188 | lot_size 189 | ); 190 | 191 | nprice; 192 | base_qty; 193 | quote_qty; 194 | // debug::print(&nprice); 195 | // debug::print(&base_qty); 196 | // debug::print("e_qty); 197 | k = k +1; 198 | }; 199 | k = 0; 200 | j = j + 1; 201 | }; 202 | j = 0; 203 | i = i + 1; 204 | }; 205 | } 206 | 207 | #[test] 208 | fun test_grid_flip_1() { 209 | // usdt/usdc 210 | // usdc is 6 decimals, usdt is 4 decimals 211 | use std::vector; 212 | // use std::debug; 213 | 214 | let price_ratio = 10000000; 215 | let lot_size = 100000; 216 | let prices: vector = vector[9996, 9997, 9998, 9999, 10000, 10001, 10002, 10003, 10004, 10005, 10006]; 217 | // 1.2, 1.1, 1.05, 1.04, 1.01 218 | let ratios: vector = vector[20000, 10000, 5000, 4000, 1000]; 219 | // 100, 200 usdt 220 | let bases: vector = vector[1000000, 2000000]; // flip to sell orders 221 | // 100, 200 usdc 222 | let quotes: vector = vector[100000000, 200000000]; // flip to buy orders 223 | 224 | let i: u64 = 0; 225 | let j: u64 = 0; 226 | let k: u64 = 0; 227 | // side = 2 228 | while (i < vector::length(&bases)) { 229 | let base = vector::borrow(&mut bases, i); 230 | while (j < vector::length(&ratios)) { 231 | let ratio = vector::borrow(&mut ratios, j); 232 | while (k < vector::length(&prices)) { 233 | let price = vector::borrow(&mut prices, k); 234 | 235 | let (nprice, base_qty, quote_qty) = calc_grid_order_price_qty(2, 236 | true, 237 | *price * price_ratio/10000, 238 | *ratio, 239 | *base, 240 | price_ratio, 241 | lot_size 242 | ); 243 | 244 | nprice; 245 | base_qty; 246 | quote_qty; 247 | // debug::print(&nprice); 248 | // debug::print(&base_qty); 249 | // debug::print("e_qty); 250 | k = k +1; 251 | }; 252 | k = 0; 253 | j = j + 1; 254 | }; 255 | j = 0; 256 | i = i + 1; 257 | }; 258 | 259 | // side = 1 260 | i = 0; 261 | j = 0; 262 | k = 0; 263 | while (i < vector::length("es)) { 264 | let quote = vector::borrow(&mut quotes, i); 265 | while (j < vector::length(&ratios)) { 266 | let ratio = vector::borrow(&mut ratios, j); 267 | while (k < vector::length(&prices)) { 268 | let price = vector::borrow(&mut prices, k); 269 | 270 | let (nprice, base_qty, quote_qty) = calc_grid_order_price_qty(1, 271 | true, 272 | *price * price_ratio, 273 | *ratio, 274 | *quote, 275 | price_ratio, 276 | lot_size 277 | ); 278 | 279 | nprice; 280 | base_qty; 281 | quote_qty; 282 | // debug::print(&nprice); 283 | // debug::print(&base_qty); 284 | // debug::print("e_qty); 285 | k = k +1; 286 | }; 287 | k = 0; 288 | j = j + 1; 289 | }; 290 | j = 0; 291 | i = i + 1; 292 | }; 293 | } 294 | 295 | #[test] 296 | fun test_grid_flip_0001() { 297 | // use std::debug; 298 | 299 | // price about 0.001/usdc 300 | // usdc is 6 decimals, base token is 8 decimals 301 | let price_ratio = 100000000000; 302 | let lot_size = 100000000; // 1 303 | let ratio = 12000; // 1.12 304 | let price = 9000000; // 0.009 305 | let i = 0; 306 | let qty = 1000000000000; // 10000 307 | // 1973600000000 308 | 309 | while (i < 100) { 310 | // flip 100 times 311 | // sell 312 | let (nprice, base_qty, _) = calc_grid_order_price_qty( 313 | SELL, 314 | true, 315 | price, 316 | ratio, 317 | qty, 318 | price_ratio, 319 | lot_size 320 | ); 321 | // debug::print(&i); 322 | // debug::print(&nprice); 323 | 324 | let quote_qty = (((base_qty as u128) * (nprice as u128) / (price_ratio as u128)) as u64); 325 | (price, qty, _) = calc_grid_order_price_qty( 326 | BUY, 327 | true, 328 | nprice, 329 | ratio, 330 | quote_qty, 331 | price_ratio, 332 | lot_size 333 | ); 334 | // debug::print(&price); 335 | // debug::print(&qty); 336 | // buy 337 | i = i + 1; 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/sources/mining.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 | /// trading is mining, for both traders and LPs 11 | /// 12 | module sea::mining { 13 | use std::signer::address_of; 14 | use aptos_framework::timestamp; 15 | 16 | use sea::sea; 17 | 18 | // Friends ==================================================== 19 | friend sea::market; 20 | friend sea::amm; 21 | 22 | // Structs ==================================================== 23 | 24 | struct UserMintInfo has key { 25 | volume: u64, 26 | referer_addr: address, 27 | } 28 | 29 | struct MintInfo has key { 30 | enabled: bool, 31 | sea_per_second: u64, // SEA issued every second 32 | last_ts: u64, 33 | pool_sea: u64, 34 | total_volume: u64, 35 | team_addr: address, 36 | dev_addr: address, 37 | } 38 | 39 | /// Errors ==================================================== 40 | const E_MINT_DISABLED: u64 = 900; 41 | const E_NOT_ENOUGH_SEA: u64 = 901; 42 | 43 | fun init_module( 44 | sender: &signer, 45 | ) { 46 | assert!(address_of(sender) == @sea, 1); 47 | 48 | move_to(sender, MintInfo { 49 | enabled: false, 50 | sea_per_second: 0, 51 | last_ts: 0, 52 | pool_sea: 0, 53 | total_volume: 0, 54 | team_addr: @sea_team, 55 | dev_addr: @sea_dev, 56 | }); 57 | } 58 | 59 | // Public functions ==================================================== 60 | // user should initialize it's mint info 61 | public entry fun init_user_mint_info( 62 | account: &signer, 63 | referer_addr: address, 64 | ) { 65 | if (!exists(address_of(account))) { 66 | move_to(account, UserMintInfo { 67 | volume: 0, 68 | referer_addr: referer_addr, 69 | }); 70 | }; 71 | } 72 | 73 | public fun claim_reward( 74 | account: &signer, 75 | ) acquires UserMintInfo, MintInfo { 76 | let mint_info = borrow_global_mut(@sea); 77 | assert!(mint_info.enabled, E_MINT_DISABLED); 78 | 79 | update_pool_info(mint_info); 80 | let addr = address_of(account); 81 | let maker_info = borrow_global_mut(addr); 82 | if (maker_info.volume == 0) { 83 | return 84 | }; 85 | 86 | let reward = (((maker_info.volume as u128) * (mint_info.pool_sea as u128) / (mint_info.total_volume as u128)) as u64); 87 | assert!(reward <= mint_info.pool_sea, E_NOT_ENOUGH_SEA); 88 | let trader_reward = reward/2; 89 | let team_reward = reward - trader_reward; 90 | let dev_reward = trader_reward/5; 91 | if (trader_reward > 0) { 92 | sea::mint(addr, trader_reward); 93 | }; 94 | // mint referer reward if exists: 5% 95 | if (maker_info.referer_addr != @0x0) { 96 | let referer_reward = trader_reward/10; 97 | team_reward = team_reward - referer_reward; 98 | sea::mint(addr, trader_reward); 99 | }; 100 | 101 | // mint team reward 102 | sea::mint(mint_info.dev_addr, dev_reward); 103 | sea::mint(mint_info.team_addr, team_reward - dev_reward); 104 | 105 | mint_info.total_volume = mint_info.total_volume - maker_info.volume; 106 | mint_info.pool_sea = mint_info.pool_sea - reward; 107 | maker_info.volume = 0; 108 | } 109 | 110 | public(friend) fun on_trade( 111 | taker_addr: address, 112 | maker_addr: address, 113 | vol: u64) acquires UserMintInfo, MintInfo { 114 | let pool_info = borrow_global_mut(@sea); 115 | if (!pool_info.enabled) { 116 | return 117 | }; 118 | 119 | if (exists(maker_addr)) { 120 | let maker_info = borrow_global_mut(maker_addr); 121 | maker_info.volume = maker_info.volume + vol; 122 | pool_info.total_volume = pool_info.total_volume + vol; 123 | }; 124 | 125 | if (!exists(taker_addr)) { 126 | return 127 | }; 128 | let info = borrow_global_mut(taker_addr); 129 | pool_info.total_volume = pool_info.total_volume + vol; 130 | info.volume = info.volume + vol; 131 | } 132 | 133 | public(friend) fun on_swap( 134 | taker: &signer, 135 | vol: u64) acquires UserMintInfo, MintInfo { 136 | let addr = address_of(taker); 137 | if (!exists(addr)) { 138 | return 139 | }; 140 | 141 | let pool_info = borrow_global_mut(@sea); 142 | if (!pool_info.enabled) { 143 | return 144 | }; 145 | 146 | pool_info.total_volume = pool_info.total_volume + vol; 147 | let info = borrow_global_mut(addr); 148 | info.volume = info.volume + vol; 149 | } 150 | 151 | // Admin functions ==================================================== 152 | public entry fun configure_trade_mining( 153 | admin: &signer, 154 | enabled: bool, 155 | sea_per_second: u64, 156 | ) acquires MintInfo { 157 | assert!(address_of(admin) == @sea, 1); 158 | let pool_info = borrow_global_mut(@sea); 159 | 160 | pool_info.enabled = enabled; 161 | pool_info.sea_per_second = sea_per_second; 162 | pool_info.last_ts = timestamp::now_seconds(); 163 | } 164 | 165 | public entry fun set_team_addr( 166 | admin: &signer, 167 | addr: address, 168 | ) acquires MintInfo { 169 | assert!(address_of(admin) == @sea, 1); 170 | let pool_info = borrow_global_mut(@sea); 171 | 172 | pool_info.team_addr = addr; 173 | } 174 | 175 | public entry fun set_dev_addr( 176 | admin: &signer, 177 | addr: address, 178 | ) acquires MintInfo { 179 | assert!(address_of(admin) == @sea, 1); 180 | let pool_info = borrow_global_mut(@sea); 181 | 182 | pool_info.dev_addr = addr; 183 | } 184 | 185 | // Private functions ==================================================== 186 | 187 | fun update_pool_info( 188 | pool: &mut MintInfo, 189 | ) { 190 | let ts = timestamp::now_seconds(); 191 | if (ts > pool.last_ts) { 192 | pool.pool_sea = pool.pool_sea + (ts - pool.last_ts) * pool.sea_per_second; 193 | pool.last_ts = ts; 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/sources/price.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 | /// Price is the most important part of an order. But there are two difficulty. 11 | /// First, in Cex, the price can be represent by decimal, but in solidity/move, 12 | /// decimal is expensive. so we use u128 to represent the price 13 | /// Second, we are permissionless DEX, there can be many pairs, some of the price 14 | /// is very high, but some of them is very low, so how can we inact an universal rule. 15 | /// here, the rule is effective digits 16 | /// 17 | module sea::price { 18 | // Constants ==================================================== 19 | const E1: u64 = 10; 20 | const E2: u64 = 100; 21 | const E4: u64 = 10000; 22 | const E8: u64 = 100000000; 23 | const MAX_EFFECTIVE_DIGITS: u64 = 100000; // only forex need more digits 24 | const MAX_U64: u128 = 0xffffffffffffffff; 25 | // 64 bit 26 | const ORDER_ID_MASK: u128 = 0xffffffffffffffff; 27 | 28 | public fun get_price_order_id(v: u128): (u64, u64) { 29 | (((v >> 64) as u64), ((v & ORDER_ID_MASK) as u64)) 30 | } 31 | 32 | // price_coefficient = 1,000,000,000 33 | // price_ratio = price_coefficient / math.pow(10, quote_decimals-base_decimals) 34 | // price = price_decimal * price_coefficient 35 | // quote = base * price / price_ratio = base * price_decimal * math.pow(10, quote_decimals-base_decimals) 36 | // price_ratio should >= 1 or else return false 37 | public fun calc_price_ratio( 38 | base_scale: u64, 39 | quote_scale: u64, 40 | price_coefficient: u64, 41 | ): (u64, bool) { 42 | if (quote_scale >= base_scale) { 43 | let delta = quote_scale/base_scale; 44 | if (price_coefficient < delta) { 45 | return (0, false) 46 | }; 47 | (price_coefficient/delta, true) 48 | } else { 49 | let ratio = ((price_coefficient as u128) * ((base_scale/quote_scale) as u128)); 50 | if (ratio > MAX_U64) { 51 | return (0, false) 52 | }; 53 | ((ratio as u64), true) 54 | } 55 | } 56 | 57 | // check the price is valid 58 | public fun is_valid_price(price: u64): bool { 59 | if (price == 0) { 60 | return false 61 | }; 62 | 63 | loop { 64 | if (price % E8 == 0) { 65 | price = price / E8; 66 | } else if (price % E4 == 0) { 67 | price = price / E4; 68 | } else if (price % E2 == 0) { 69 | price = price / E2; 70 | } else if (price % E1 == 0) { 71 | price = price / E1; 72 | } else { 73 | break 74 | } 75 | }; 76 | 77 | price < MAX_EFFECTIVE_DIGITS 78 | } 79 | 80 | public fun to_valid_price(price: u64): u64 { 81 | let max = (MAX_EFFECTIVE_DIGITS as u64); 82 | if (price < max) { 83 | return price 84 | }; 85 | let times = 1; 86 | let mod; 87 | loop { 88 | if (price / 10000 >= max) { 89 | price = price / 10000; 90 | times = times * 10000; 91 | } else if (price / 1000 >= max) { 92 | price = price / 1000; 93 | times = times * 1000; 94 | } else if (price / 100 >= max) { 95 | price = price / 100; 96 | times = times * 100; 97 | } else { 98 | mod = price % 10; 99 | price = price / 10; 100 | times = times * 10; 101 | if (price < max) { 102 | if (mod >= 5) { 103 | price = price + 1; 104 | }; 105 | break 106 | } 107 | } 108 | }; 109 | return price * times 110 | } 111 | 112 | #[test] 113 | fun test_valid_price() { 114 | let maxu128: u64 = 0xffffffffffffffff; // 0xffffffffffffffffffffffffffffffff; 115 | let i: u64 = 1; 116 | // while(i <= 100000) { 117 | while(i <= 100) { 118 | let price = i; 119 | loop { 120 | let ok = is_valid_price((price)); 121 | assert!(ok, (i as u64)); 122 | if (maxu128 / 10 < price) { 123 | break 124 | }; 125 | price = price * 10; 126 | }; 127 | i = i + 1; 128 | } 129 | } 130 | 131 | #[test] 132 | fun test_valid_price_10k() { 133 | use std::vector; 134 | 135 | let maxu128: u64 = 0xffffffffffffffff; // ffffffffffffffff; 136 | let valid_prices: vector = vector[10207, 10001, 24607, 99011, 137 | 99899, 5401, 78038, 304050, 38764, 846380, 769000, 70201, 138 | 847320, 45602, 87302, 65034, 54402, 50001, 90001, 901010]; 139 | 140 | while(vector::length(&valid_prices) > 0) { 141 | let price = vector::pop_back(&mut valid_prices); 142 | loop { 143 | let ok = is_valid_price(price); 144 | assert!(ok, (price as u64)); 145 | if (maxu128 / 10 < price) { 146 | break 147 | }; 148 | price = price * 10; 149 | }; 150 | } 151 | } 152 | 153 | #[test] 154 | fun test_invalid_price() { 155 | use std::vector; 156 | 157 | let maxu128: u64 = 0xffffffffffffffff; // 0xffffffffffffffffffffffffffffffff; 158 | let i: u64 = 1; 159 | let invalid_prices: vector = vector[10002347, 1000001, 2345671, 9990011, 160 | 238948362, 543210001, 7803282, 203040506, 32876401, 84638001, 7690001, 70000201, 161 | 8473201, 4560012, 87093202, 6509324, 5445002, 5000001, 9000001, 90000500010]; 162 | 163 | while(vector::length(&invalid_prices) > 0) { 164 | let price = vector::pop_back(&mut invalid_prices); 165 | loop { 166 | let ok = is_valid_price(price); 167 | assert!(!ok, (i as u64)); 168 | if (maxu128 / 10 < price) { 169 | break 170 | }; 171 | price = price * 10; 172 | }; 173 | } 174 | } 175 | 176 | #[test] 177 | fun test_to_valid_price() { 178 | use std::vector; 179 | // use std::debug; 180 | 181 | let prices: vector = vector[10002347, 1000001, 2345671, 9990011, 182 | 238948362, 543210001, 7803282, 203040506, 32876401, 84638001, 7690001, 70000201, 183 | 8473201, 4560012, 87093202, 6509324, 5445002, 5000001, 9000001, 90000500010, 184 | 100023470, 100000100, 2345671000, 99999510000, 100006000000, 185 | 23894800000, 543210000000, 7803282000000, 20304050000000, 3287640000000, 186 | 84638000000000, 7690000000000, 70002670000000]; 187 | 188 | while(vector::length(&prices) > 0) { 189 | let price = vector::pop_back(&mut prices); 190 | let nprice = to_valid_price(price); 191 | 192 | nprice; 193 | // debug::print(&nprice); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/sources/router.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 | /// router for orderbook and AMM 11 | /// 12 | module sea::router { 13 | use std::signer::address_of; 14 | use aptos_framework::coin::{Self, Coin}; 15 | 16 | use sea_spot::lp::{LP}; 17 | 18 | use sea::amm; 19 | use sea::escrow; 20 | use sea::utils; 21 | use sea::fee; 22 | 23 | const E_NO_AUTH: u64 = 100; 24 | const E_POOL_NOT_EXIST: u64 = 7000; 25 | const E_INSUFFICIENT_BASE_AMOUNT: u64 = 7001; 26 | const E_INSUFFICIENT_QUOTE_AMOUNT: u64 = 7002; 27 | const E_INSUFFICIENT_AMOUNT: u64 = 7003; 28 | const E_INVALID_AMOUNT_OUT: u64 = 7004; 29 | const E_INVALID_AMOUNT_IN: u64 = 7005; 30 | const E_INSUFFICIENT_LIQUIDITY: u64 = 7006; 31 | const E_INSUFFICIENT_QUOTE_RESERVE: u64 = 7007; 32 | const E_INSUFFICIENT_BASE_RESERVE: u64 = 7008; 33 | 34 | public entry fun add_liquidity( 35 | account: &signer, 36 | amt_base_desired: u64, 37 | amt_quote_desired: u64, 38 | amt_base_min: u64, 39 | amt_quote_min: u64 40 | ) { 41 | assert!(amm::pool_exist(), E_POOL_NOT_EXIST); 42 | 43 | let (amount_base, 44 | amount_quote) = amm::calc_optimal_coin_values( 45 | amt_base_desired, 46 | amt_quote_desired, 47 | amt_base_min, 48 | amt_quote_min); 49 | let coin_base = coin::withdraw(account, amount_base); 50 | let coin_quote = coin::withdraw(account, amount_quote); 51 | let lp_coins = amm::mint(coin_base, coin_quote); 52 | 53 | let acc_addr = address_of(account); 54 | utils::register_coin_if_not_exist>(account); 55 | coin::deposit(acc_addr, lp_coins); 56 | } 57 | 58 | public entry fun remove_liquidity( 59 | account: &signer, 60 | liquidity: u64, 61 | amt_base_min: u64, 62 | amt_quote_min: u64, 63 | ) { 64 | assert!(amm::pool_exist(), E_POOL_NOT_EXIST); 65 | let coins = coin::withdraw>(account, liquidity); 66 | let (base_out, quote_out) = amm::burn(coins); 67 | 68 | assert!(coin::value(&base_out) >= amt_base_min, E_INSUFFICIENT_BASE_AMOUNT); 69 | assert!(coin::value("e_out) >= amt_quote_min, E_INSUFFICIENT_QUOTE_AMOUNT); 70 | 71 | // transfer 72 | let account_addr = address_of(account); 73 | coin::deposit(account_addr, base_out); 74 | coin::deposit(account_addr, quote_out); 75 | } 76 | 77 | // user: buy exact quote 78 | // amount_out: quote amount out of pool 79 | // amount_in_max: base amount into pool 80 | public entry fun buy_exact_quote( 81 | account: &signer, 82 | amount_out: u64, 83 | amount_in_max: u64 84 | ) { 85 | let base_in_needed = get_amount_in(amount_out, false); 86 | assert!(base_in_needed <= amount_in_max, E_INSUFFICIENT_BASE_AMOUNT); 87 | let base_in = coin::withdraw(account, base_in_needed); 88 | let quote_out; 89 | quote_out = swap_base_for_quote(base_in, amount_out); 90 | utils::register_coin_if_not_exist(account); 91 | coin::deposit(address_of(account), quote_out); 92 | } 93 | 94 | // user: sell exact base 95 | public entry fun sell_exact_base( 96 | account: &signer, 97 | amount_in: u64, 98 | amount_out_min: u64 99 | ) { 100 | let coin_in = coin::withdraw(account, amount_in); 101 | let coin_out; 102 | coin_out = swap_base_for_quote(coin_in, amount_out_min); 103 | assert!(coin::value(&coin_out) >= amount_out_min, E_INSUFFICIENT_QUOTE_AMOUNT); 104 | utils::register_coin_if_not_exist(account); 105 | coin::deposit(address_of(account), coin_out); 106 | } 107 | 108 | // user: buy base 109 | // amount_out: the exact base amount 110 | public entry fun buy_exact_base( 111 | account: &signer, 112 | amount_out: u64, 113 | amount_in_max: u64 114 | ) { 115 | let coin_in_needed = get_amount_in(amount_out, true); 116 | assert!(coin_in_needed <= amount_in_max, E_INSUFFICIENT_BASE_AMOUNT); 117 | let coin_in = coin::withdraw(account, coin_in_needed); 118 | let coin_out; 119 | coin_out = swap_quote_for_base(coin_in, amount_out); 120 | utils::register_coin_if_not_exist(account); 121 | coin::deposit(address_of(account), coin_out); 122 | } 123 | 124 | // user: sell exact quote 125 | public entry fun sell_exact_quote( 126 | account: &signer, 127 | amount_in: u64, 128 | amount_out_min: u64 129 | ) { 130 | let coin_in = coin::withdraw(account, amount_in); 131 | let coin_out; 132 | coin_out = swap_quote_for_base(coin_in, amount_out_min); 133 | assert!(coin::value(&coin_out) >= amount_out_min, E_INSUFFICIENT_QUOTE_AMOUNT); 134 | utils::register_coin_if_not_exist(account); 135 | coin::deposit(address_of(account), coin_out); 136 | } 137 | 138 | public entry fun withdraw_dao_fee( 139 | account: &signer, 140 | to: address 141 | ) { 142 | assert!(address_of(account) == @sea, E_NO_AUTH); 143 | 144 | let amount = coin::balance>(@sea_spot) - amm::get_min_liquidity(); 145 | assert!(amount > 0, E_INSUFFICIENT_AMOUNT); 146 | coin::transfer>(&escrow::get_spot_account(), to, amount); 147 | } 148 | 149 | //////////////////////////////////////////////////////////////////////////// 150 | /// PUBLIC FUNCTIONS 151 | //////////////////////////////////////////////////////////////////////////// 152 | 153 | // sell base, buy quote 154 | public fun swap_base_for_quote( 155 | coin_in: Coin, 156 | coin_out_val: u64 157 | ): Coin { 158 | let (zero, coin_out) = amm::swap(coin_in, 0, coin::zero(), coin_out_val); 159 | coin::destroy_zero(zero); 160 | 161 | coin_out 162 | } 163 | 164 | // sell quote, buy base 165 | public fun swap_quote_for_base( 166 | coin_in: Coin, 167 | coin_out_val: u64, 168 | ): Coin { 169 | let (coin_out, zero) = amm::swap(coin::zero(), coin_out_val, coin_in, 0); 170 | coin::destroy_zero(zero); 171 | 172 | coin_out 173 | } 174 | 175 | /// in_is_quote: by user perspective 176 | public fun get_amount_in( 177 | amount_out: u64, 178 | in_is_quote: bool, 179 | ): u64 { 180 | assert!(amount_out > 0, E_INVALID_AMOUNT_OUT); 181 | let (base_reserve, quote_reserve, fee_ratio) = amm::get_pool_reserve_fee(); 182 | assert!(base_reserve> 0 && quote_reserve > 0, E_INSUFFICIENT_LIQUIDITY); 183 | 184 | let numerator: u128; 185 | let denominator: u128; 186 | let fee_deno = fee::get_fee_denominate(); 187 | if (in_is_quote) { 188 | assert!(base_reserve > amount_out, E_INSUFFICIENT_BASE_RESERVE); 189 | numerator = (quote_reserve as u128) * (amount_out as u128) * (fee_deno as u128); 190 | denominator = ((base_reserve - amount_out) as u128) * ((fee_deno - fee_ratio) as u128); 191 | } else { 192 | assert!(quote_reserve > amount_out, E_INSUFFICIENT_QUOTE_RESERVE); 193 | numerator = (base_reserve as u128) * (amount_out as u128) * (fee_deno as u128); 194 | denominator = ((quote_reserve - amount_out) as u128) * ((fee_deno - fee_ratio) as u128); 195 | }; 196 | 197 | // debug::print(&denominator); 198 | ((numerator / denominator + 1) as u64) 199 | } 200 | 201 | public fun get_amount_out( 202 | amount_in: u64, 203 | out_is_quote: bool, 204 | ): u64 { 205 | assert!(amount_in > 0, E_INVALID_AMOUNT_IN); 206 | let (base_reserve, quote_reserve, fee_ratio) = amm::get_pool_reserve_fee(); 207 | if (base_reserve == 0 || quote_reserve == 0) { 208 | return 0 209 | }; 210 | 211 | let fee_deno = fee::get_fee_denominate(); 212 | let amount_in_with_fee = (amount_in as u128) * ((fee_deno - fee_ratio) as u128); 213 | let numerator: u128; 214 | let denominator: u128; 215 | if (out_is_quote) { 216 | numerator = amount_in_with_fee * (quote_reserve as u128); 217 | denominator = (base_reserve as u128) * (fee_deno as u128) + amount_in_with_fee; 218 | } else { 219 | numerator = amount_in_with_fee * (base_reserve as u128); 220 | denominator = (quote_reserve as u128) * (fee_deno as u128) + amount_in_with_fee; 221 | }; 222 | 223 | let amount_out = numerator / denominator; 224 | // debug::print(&amount_out); 225 | (amount_out as u64) 226 | } 227 | 228 | // Tests ================================================================== 229 | #[test_only] 230 | use sea::market; 231 | #[test_only] 232 | use std::vector; 233 | // use std::debug; 234 | 235 | #[test( 236 | user1 = @user_1, 237 | user2 = @user_2, 238 | user3 = @user_3 239 | )] 240 | fun test_get_amount_in( 241 | user1: &signer, 242 | user2: &signer, 243 | user3: &signer 244 | ) { 245 | market::test_register_pair(user1, user2, user3); 246 | 247 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 248 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 249 | 250 | let i = 0; 251 | let out_bases = vector[10000000, 20000000, 50000000, 80000000, 160000000, 300000000, 600000000, 1100000000]; 252 | let exp_quotes = vector[151781576407, 304581821112, 769198158402, 1243361406731, 2556771343419, 5042521260631, 11345672836419, 26274189726443]; 253 | // buy exact base 254 | while (i < vector::length(&out_bases)) { 255 | let out_base: u64 = *vector::borrow(&out_bases, i); 256 | let exp_quote = *vector::borrow(&exp_quotes, i); 257 | let in_amt = get_amount_in(out_base, true); 258 | assert!(in_amt == exp_quote, i); 259 | 260 | i = i + 1; 261 | }; 262 | 263 | i = 0; 264 | // buy exact quote 265 | let out_quotes = vector[1000000000, 2000000000, 5000000000, 8000000000, 16000000000, 30000000000, 60000000000, 110000000000]; 266 | let exp_bases = vector[66173, 132348, 330890, 529459, 1059105, 1986434, 3975498, 7296466]; 267 | while (i < vector::length(&out_quotes)) { 268 | let out_quote: u64 = *vector::borrow(&out_quotes, i); 269 | let exp_base = *vector::borrow(&exp_bases, i); // base to sell 270 | let in_amt = get_amount_in(out_quote, false); 271 | assert!(exp_base == in_amt, 1000 + i); 272 | 273 | i = i + 1; 274 | } 275 | } 276 | 277 | #[test( 278 | user1 = @user_1, 279 | user2 = @user_2, 280 | user3 = @user_3 281 | )] 282 | fun test_get_amount_out( 283 | user1: &signer, 284 | user2: &signer, 285 | user3: &signer 286 | ) { 287 | market::test_register_pair(user1, user2, user3); 288 | 289 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 290 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 291 | 292 | let i = 0; 293 | let in_bases = vector[10000000, 20000000, 50000000, 80000000, 160000000, 300000000, 600000000, 1100000000]; 294 | let exp_quotes = vector[150622575785, 300248146517, 743240846236, 1177608020883, 2295618623256, 4121761898268, 7556849737478, 12165303150422]; 295 | // sell exact base 296 | while (i < vector::length(&in_bases)) { 297 | let in_base: u64 = *vector::borrow(&in_bases, i); 298 | let out_amt = get_amount_out(in_base, true); 299 | let exp_quote = *vector::borrow(&exp_quotes, i); 300 | assert!(out_amt == exp_quote, i); 301 | 302 | i = i + 1; 303 | }; 304 | 305 | i = 0; 306 | // sell exact quote 307 | let in_quotes = vector[1000000000, 2000000000, 5000000000, 8000000000, 16000000000, 30000000000, 60000000000, 110000000000]; 308 | let exp_bases = vector[66103, 132203, 330486, 528742, 1057299, 1981824, 3961032, 7253912]; 309 | while (i < vector::length(&in_quotes)) { 310 | let in_quote: u64 = *vector::borrow(&in_quotes, i); 311 | let out_amt = get_amount_out(in_quote, false); 312 | let exp_base = *vector::borrow(&exp_bases, i); // out base expect 313 | assert!(exp_base == out_amt, 1000 + i); 314 | 315 | i = i + 1; 316 | } 317 | } 318 | 319 | #[test( 320 | user1 = @user_1, 321 | user2 = @user_2, 322 | user3 = @user_3 323 | )] 324 | fun test_buy_exact_quote( 325 | user1: &signer, 326 | user2: &signer, 327 | user3: &signer, 328 | ) { 329 | market::test_register_pair(user1, user2, user3); 330 | 331 | let addr2 = address_of(user2); 332 | let btc_balance1 = coin::balance(addr2); 333 | let usd_balance1 = coin::balance(addr2); 334 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 335 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 336 | 337 | let amt_out = 1500000000000; // quote usdt 338 | let amt_in_max = get_amount_in(amt_out, false); 339 | buy_exact_quote(user2, amt_out, amt_in_max); 340 | let btc_balance2 = coin::balance(addr2); 341 | let usd_balance2 = coin::balance(addr2); 342 | assert!(btc_balance2 >= btc_balance1 - amt_in_max, 1); 343 | assert!(usd_balance2 == usd_balance1 + amt_out, 2); 344 | } 345 | 346 | #[test( 347 | user1 = @user_1, 348 | user2 = @user_2, 349 | user3 = @user_3 350 | )] 351 | fun test_sell_exact_base( 352 | user1: &signer, 353 | user2: &signer, 354 | user3: &signer, 355 | ) { 356 | market::test_register_pair(user1, user2, user3); 357 | 358 | let addr2 = address_of(user2); 359 | let btc_balance1 = coin::balance(addr2); 360 | let usd_balance1 = coin::balance(addr2); 361 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 362 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 363 | 364 | let amt_in = 15000000; // base btc 365 | let amt_out_min = get_amount_out(amt_in, true); 366 | sell_exact_base(user2, amt_in, amt_out_min); 367 | let btc_balance2 = coin::balance(addr2); 368 | let usd_balance2 = coin::balance(addr2); 369 | assert!(btc_balance2 == btc_balance1 - amt_in, 1); 370 | assert!(usd_balance2 >= usd_balance1 + amt_out_min, 2); 371 | } 372 | 373 | #[test( 374 | user1 = @user_1, 375 | user2 = @user_2, 376 | user3 = @user_3 377 | )] 378 | fun test_buy_exact_base( 379 | user1: &signer, 380 | user2: &signer, 381 | user3: &signer, 382 | ) { 383 | market::test_register_pair(user1, user2, user3); 384 | 385 | let addr2 = address_of(user2); 386 | let btc_balance1 = coin::balance(addr2); 387 | let usd_balance1 = coin::balance(addr2); 388 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 389 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 390 | 391 | let amt_out = 15000000; // base btc 392 | let amt_in_max = get_amount_in(amt_out, true); 393 | buy_exact_base(user2, amt_out, amt_in_max); 394 | let btc_balance2 = coin::balance(addr2); 395 | let usd_balance2 = coin::balance(addr2); 396 | 397 | assert!(btc_balance2 == btc_balance1 + amt_out, 1); 398 | assert!(usd_balance2 >= usd_balance1 - amt_in_max, 2); 399 | } 400 | 401 | #[test( 402 | user1 = @user_1, 403 | user2 = @user_2, 404 | user3 = @user_3 405 | )] 406 | fun test_sell_exact_quote( 407 | user1: &signer, 408 | user2: &signer, 409 | user3: &signer, 410 | ) { 411 | market::test_register_pair(user1, user2, user3); 412 | 413 | let addr2 = address_of(user2); 414 | let btc_balance1 = coin::balance(addr2); 415 | let usd_balance1 = coin::balance(addr2); 416 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 417 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 418 | 419 | let amt_in = 1500000000000; // quote usd 420 | let amt_out_min = get_amount_out(amt_in, false); 421 | sell_exact_quote(user2, amt_in, amt_out_min); 422 | let btc_balance2 = coin::balance(addr2); 423 | let usd_balance2 = coin::balance(addr2); 424 | assert!(btc_balance2 == btc_balance1 + amt_out_min, 1); 425 | assert!(usd_balance2 == usd_balance1 - amt_in, 2); 426 | } 427 | 428 | #[test( 429 | user1 = @user_1, 430 | user2 = @user_2, 431 | user3 = @user_3 432 | )] 433 | fun test_remove_liquidity( 434 | user1: &signer, 435 | user2: &signer, 436 | user3: &signer, 437 | ) { 438 | market::test_register_pair(user1, user2, user3); 439 | 440 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 441 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 442 | // add_liquidity(user1, 300000, 300000 * 15120, 0, 0); 443 | 444 | let addr1 = address_of(user1); 445 | let lp_balance = coin::balance>(addr1); 446 | remove_liquidity(user1, lp_balance, 0, 0); 447 | } 448 | 449 | // flash loan 450 | 451 | // loan base, return base 452 | #[test( 453 | user1 = @user_1, 454 | user2 = @user_2, 455 | user3 = @user_3 456 | )] 457 | fun test_flash_loan_base_back_base( 458 | user1: &signer, 459 | user2: &signer, 460 | user3: &signer, 461 | ) { 462 | market::test_register_pair(user1, user2, user3); 463 | 464 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 465 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 466 | 467 | let (coin_base, coin_quote, loan) = amm::flash_swap(100000000, 0); 468 | coin::merge(&mut coin_base, coin::withdraw(user2, 100000000 * 51 / 100000)); 469 | amm::pay_flash_swap(coin_base, coin_quote, loan); 470 | } 471 | 472 | #[test( 473 | user1 = @user_1, 474 | user2 = @user_2, 475 | user3 = @user_3 476 | )] 477 | fun test_flash_loan_base_back_quote( 478 | user1: &signer, 479 | user2: &signer, 480 | user3: &signer, 481 | ) { 482 | market::test_register_pair(user1, user2, user3); 483 | 484 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 485 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 486 | 487 | let (coin_base, coin_quote, loan) = amm::flash_swap(100000000, 0); 488 | coin::merge(&mut coin_quote, coin::withdraw(user2, 100000000 * 15120 * 501 / 1000000)); 489 | amm::pay_flash_swap(coin_base, coin_quote, loan); 490 | } 491 | 492 | #[test( 493 | user1 = @user_1, 494 | user2 = @user_2, 495 | user3 = @user_3 496 | )] 497 | fun test_flash_loan_quote_back_quote( 498 | user1: &signer, 499 | user2: &signer, 500 | user3: &signer, 501 | ) { 502 | market::test_register_pair(user1, user2, user3); 503 | 504 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 505 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 506 | 507 | let (coin_base, coin_quote, loan) = amm::flash_swap(0, 100000000000); 508 | coin::merge(&mut coin_quote, coin::withdraw(user2, 100000000000 * 501 / 1000000)); 509 | amm::pay_flash_swap(coin_base, coin_quote, loan); 510 | } 511 | 512 | #[test( 513 | user1 = @user_1, 514 | user2 = @user_2, 515 | user3 = @user_3 516 | )] 517 | fun test_flash_loan_quote_back_base( 518 | user1: &signer, 519 | user2: &signer, 520 | user3: &signer, 521 | ) { 522 | market::test_register_pair(user1, user2, user3); 523 | 524 | add_liquidity(user1, 1000000000, 1000000000 * 15120, 0, 0); 525 | add_liquidity(user1, 2000000000, 2000000000 * 15120, 0, 0); 526 | 527 | let (coin_base, coin_quote, loan) = amm::flash_swap(0, 100000000000); 528 | coin::merge(&mut coin_base, coin::withdraw(user2, 100000000000 * 501 / 15120 / 1000000)); 529 | amm::pay_flash_swap(coin_base, coin_quote, loan); 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /src/sources/sea.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 | /// SEA token 11 | /// 12 | module sea::sea { 13 | use std::string; 14 | use std::signer::address_of; 15 | use aptos_framework::coin::{Self, BurnCapability, MintCapability}; 16 | 17 | struct SEA {} 18 | 19 | const E_NO_AUTH: u64 = 1; 20 | const MAX_SEA_SUPPLY: u64 = 1000000000000000; // 1 billion 21 | 22 | // Friends >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 23 | friend sea::mining; 24 | 25 | /// Capabilities resource storing mint and burn capabilities. 26 | /// The resource is stored on the account that initialized coin `CoinType`. 27 | struct Capabilities has key { 28 | // owner: address, 29 | burn_cap: BurnCapability, 30 | // freeze_cap: FreezeCapability, 31 | mint_cap: MintCapability, 32 | 33 | supply: u64, 34 | } 35 | 36 | fun init_module( 37 | sender: &signer, 38 | ) { 39 | assert!(address_of(sender) == @sea, 1); 40 | 41 | let (burn_cap, freeze_cap, mint_cap) = coin::initialize( 42 | sender, 43 | string::utf8(b"SEA"), 44 | string::utf8(b"SEA"), 45 | 6, 46 | false, 47 | ); 48 | coin::destroy_freeze_cap(freeze_cap); 49 | move_to(sender, Capabilities { 50 | // owner: address_of(sender), 51 | burn_cap, 52 | mint_cap, 53 | supply: 0, 54 | }); 55 | } 56 | 57 | // Admin functions ==================================================== 58 | // public entry fun transfer_cap( 59 | // admin: &signer, 60 | // from_addr: address, 61 | // to_addr: address, 62 | // ) acquires Capabilities { 63 | // let cap = borrow_global_mut>(from_addr); 64 | // assert!(address_of(admin) == cap.owner, E_NO_AUTH); 65 | 66 | // cap.owner = to_addr; 67 | // } 68 | 69 | // public entry fun claim_cap( 70 | // admin: &signer, 71 | // from_addr: address, 72 | // ) acquires Capabilities { 73 | // let cap = borrow_global_mut>(from_addr); 74 | // assert!(address_of(admin) == cap.owner, E_NO_AUTH); 75 | 76 | // move_to(admin, cap); 77 | // } 78 | 79 | public entry fun mint_for( 80 | account: &signer, 81 | to_addr: address, 82 | amount: u64, 83 | ) acquires Capabilities { 84 | assert!(address_of(account) == @sea, 1); 85 | 86 | let capabilities = borrow_global_mut>(@sea); 87 | assert!(capabilities.supply + amount <= MAX_SEA_SUPPLY, 0x2); 88 | let coins_minted = coin::mint(amount, &capabilities.mint_cap); 89 | capabilities.supply = capabilities.supply + amount; 90 | 91 | coin::deposit(to_addr, coins_minted); 92 | } 93 | 94 | public(friend) fun mint( 95 | addr: address, 96 | amount: u64, 97 | ) acquires Capabilities { 98 | if (amount == 0) { 99 | return 100 | }; 101 | 102 | let capabilities = borrow_global_mut>(@sea); 103 | if (capabilities.supply + amount >= MAX_SEA_SUPPLY) { 104 | return 105 | }; 106 | 107 | let coins_minted = coin::mint(amount, &capabilities.mint_cap); 108 | capabilities.supply = capabilities.supply + amount; 109 | 110 | // utils::register_coin_if_not_exist(account); 111 | coin::deposit(addr, coins_minted); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/sources/tests/test_env.move: -------------------------------------------------------------------------------- 1 | #[test_only] 2 | module sea::test_env { 3 | use std::string; 4 | // use std::debug; 5 | use std::signer::address_of; 6 | 7 | use aptos_framework::coin; 8 | use aptos_framework::genesis; 9 | use aptos_framework::account; 10 | use aptos_framework::aptos_account; 11 | 12 | use sea::market; 13 | use sea::fee; 14 | use sea::amm; 15 | use sea::escrow; 16 | use sea::events; 17 | use sea::spot_account; 18 | 19 | /// coins 20 | struct T_BTC {} 21 | struct T_SEA {} 22 | struct T_ETH {} 23 | struct T_USDC {} 24 | struct T_USDT {} 25 | 26 | const T_USDC_DECIMALS: u8 = 6; 27 | const T_USDT_DECIMALS: u8 = 4; 28 | const T_BTC_DECIMALS: u8 = 8; 29 | const T_SEA_DECIMALS: u8 = 3; 30 | const T_ETH_DECIMALS: u8 = 8; 31 | 32 | const T_USDC_AMT: u64 = 10000000*1000000; // 6 decimals 33 | const T_USDT_AMT: u64 = 10000000*10000; // 4 decimals 34 | const T_BTC_AMT: u64 = 100*100000000; // 8 decimals 35 | const T_SEA_AMT: u64 = 100000000*1000; // 3 decimals 36 | const T_ETH_AMT: u64 = 10000*100000000; // 8 decimals 37 | 38 | public fun create_test_coin( 39 | sea_admin: &signer, 40 | name: vector, 41 | decimals: u8, 42 | user_a: &signer, 43 | user_b: &signer, 44 | user_c: &signer, 45 | user_d: &signer, 46 | amt_a: u64, 47 | amt_b: u64, 48 | amt_c: u64, 49 | amt_d: u64, 50 | ) { 51 | let (bc, fc, mc) = coin::initialize(sea_admin, 52 | string::utf8(name), 53 | string::utf8(name), 54 | decimals, 55 | false); 56 | 57 | coin::register(sea_admin); 58 | coin::register(user_a); 59 | coin::register(user_b); 60 | coin::register(user_c); 61 | coin::register(user_d); 62 | 63 | coin::deposit(address_of(user_a), coin::mint(amt_a, &mc)); 64 | coin::deposit(address_of(user_b), coin::mint(amt_b, &mc)); 65 | coin::deposit(address_of(user_c), coin::mint(amt_c, &mc)); 66 | coin::deposit(address_of(user_d), coin::mint(amt_d, &mc)); 67 | coin::destroy_burn_cap(bc); 68 | coin::destroy_freeze_cap(fc); 69 | coin::destroy_mint_cap(mc); 70 | } 71 | 72 | // before create quote, the coin should be registered 73 | public fun create_quote( 74 | sea_admin: &signer, 75 | min_notional: u64) { 76 | market::register_quote(sea_admin, min_notional); 77 | } 78 | 79 | // before create pairs, you should create quotes first 80 | public fun create_test_pairs( 81 | sea_admin: &signer, 82 | fee_level: u64, 83 | price_coefficient: u64, 84 | lot_size: u64, 85 | ) { 86 | market::register_pair(sea_admin, fee_level, price_coefficient, lot_size); 87 | } 88 | 89 | fun prepare_env(): signer { 90 | // account::create_account_for_test(@sea); 91 | genesis::setup(); 92 | account::create_account_for_test(@sea_spot); 93 | let sea_admin = account::create_account_for_test(@sea); 94 | 95 | spot_account::initialize_spot_account(&sea_admin); 96 | events::initialize(&sea_admin); 97 | market::initialize(&sea_admin); 98 | escrow::initialize(&sea_admin); 99 | fee::initialize(&sea_admin); 100 | amm::initialize(&sea_admin); 101 | 102 | sea_admin 103 | } 104 | 105 | public fun create_test_env( 106 | user_a: &signer, 107 | user_b: &signer, 108 | user_c: &signer, 109 | user_d: &signer, 110 | ) { 111 | let sea_admin = prepare_env(); 112 | 113 | aptos_account::create_account(address_of(user_a)); 114 | aptos_account::create_account(address_of(user_b)); 115 | aptos_account::create_account(address_of(user_c)); 116 | aptos_account::create_account(address_of(user_d)); 117 | 118 | create_test_coin(&sea_admin, b"T_BTC", T_BTC_DECIMALS, user_a, user_b, user_c, user_d, T_BTC_AMT, T_BTC_AMT, T_BTC_AMT, T_BTC_AMT); 119 | create_test_coin(&sea_admin, b"T_ETH", T_ETH_DECIMALS, user_a, user_b, user_c, user_d, T_ETH_AMT, T_ETH_AMT, T_ETH_AMT, T_ETH_AMT); 120 | create_test_coin(&sea_admin, b"T_USDC", T_USDC_DECIMALS, user_a, user_b, user_c, user_d, T_USDC_AMT, T_USDC_AMT, T_USDC_AMT, T_USDC_AMT); 121 | create_test_coin(&sea_admin, b"T_USDT", T_USDT_DECIMALS, user_a, user_b, user_c, user_d, T_USDT_AMT, T_USDT_AMT, T_USDT_AMT, T_USDT_AMT); 122 | create_test_coin(&sea_admin, b"T_SEA", T_SEA_DECIMALS, user_a, user_b, user_c, user_d, T_SEA_AMT, T_SEA_AMT, T_SEA_AMT, T_SEA_AMT); 123 | 124 | // min_notional: 1 USDC 125 | create_quote(&sea_admin, 1000000); 126 | // min: 1 USDT 127 | create_quote(&sea_admin, 10000); 128 | // min: 0.0001 129 | create_quote(&sea_admin, 10000); 130 | 131 | // create pair: BTC/USDC 132 | create_test_pairs(&sea_admin, 500, 1000000000, 10000); // lot_size sea: 0.00001 133 | create_test_pairs(&sea_admin, 500, 1000000000, 10000); 134 | create_test_pairs(&sea_admin, 500, 1000000000, 100000); // lot_size eth: 0.001 135 | create_test_pairs(&sea_admin, 500, 1000000000, 100000); 136 | create_test_pairs(&sea_admin, 500, 1000000000, 1000); 137 | create_test_pairs(&sea_admin, 500, 1000000000, 1000); // lot_size sea: 0.001 138 | create_test_pairs(&sea_admin, 500, 1000000000, 1000); 139 | create_test_pairs(&sea_admin, 500, 1000000000, 1000); 140 | 141 | create_test_pairs(&sea_admin, 500, 1000000000, 100); // lot_size: 0.01 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/sources/tests/test_mining.move: -------------------------------------------------------------------------------- 1 | #[test_only] 2 | module sea::test_mining { 3 | } 4 | -------------------------------------------------------------------------------- /src/sources/tests/test_router.move: -------------------------------------------------------------------------------- 1 | #[test_only] 2 | module sea::test_router { 3 | use std::signer; 4 | // use std::debug; 5 | 6 | use aptos_framework::coin; 7 | 8 | use sea::router; 9 | // use sea::fee::FeeRatio500; 10 | use sea::test_env::{Self, T_BTC, T_USDC}; 11 | use sea_spot::lp::{LP}; 12 | 13 | const T_BTC_PRECISION: u64 = 100000000; 14 | const T_USDC_PRECISION: u64 = 1000000; 15 | 16 | #[test_only] 17 | fun add_amm_liquidity( 18 | user: &signer, 19 | base_amt: u64, 20 | quote_amt: u64, 21 | slip: u64, // n/10000 22 | ): u64 { 23 | router::add_liquidity( 24 | user, 25 | base_amt, 26 | quote_amt, 27 | base_amt * (10000-slip) / 10000, 28 | quote_amt * (10000-slip) / 10000, 29 | ); 30 | let account_addr = signer::address_of(user); 31 | let lp_balance = coin::balance>(account_addr); 32 | // let base_balance = coin::balance(account_addr); 33 | // let quote_balance = coin::balance(account_addr); 34 | 35 | // debug::print(&lp_balance); 36 | // debug::print(&base_balance); 37 | // debug::print("e_balance); 38 | // debug::print(&9999999999999999999); 39 | 40 | lp_balance 41 | } 42 | 43 | #[test_only] 44 | fun remove_amm_liquidity( 45 | user: &signer, 46 | amt: u64, 47 | ) { 48 | router::remove_liquidity(user, amt, 0, 0); 49 | // let account_addr = signer::address_of(user); 50 | // let lp_balance = coin::balance>(account_addr); 51 | // let base_balance = coin::balance(account_addr); 52 | // let quote_balance = coin::balance(account_addr); 53 | 54 | // debug::print(&lp_balance); 55 | // debug::print(&base_balance); 56 | // debug::print("e_balance); 57 | // debug::print(&9999999999999999999); 58 | } 59 | 60 | #[test( 61 | user1 = @user_1, 62 | user2 = @user_2, 63 | user3 = @user_3, 64 | user4 = @user_4 65 | )] 66 | // #[expected_failure(abort_code = 5003)] 67 | #[expected_failure] 68 | public fun test_less_min_liquidity( 69 | user1: &signer, 70 | user2: &signer, 71 | user3: &signer, 72 | user4: &signer, 73 | ) { 74 | test_env::create_test_env(user1, user2, user3, user4); 75 | 76 | add_amm_liquidity( 77 | user1, 78 | 999, 79 | 999, 80 | 10, 81 | ); 82 | } 83 | 84 | #[test( 85 | user1 = @user_1, 86 | user2 = @user_2, 87 | user3 = @user_3, 88 | user4 = @user_4 89 | )] 90 | public fun test_add_min_liquidity( 91 | user1: &signer, 92 | user2: &signer, 93 | user3: &signer, 94 | user4: &signer, 95 | ) { 96 | test_env::create_test_env(user1, user2, user3, user4); 97 | 98 | let lp = add_amm_liquidity( 99 | user1, 100 | 1001, 101 | 1001, 102 | 10, 103 | ); 104 | assert!(lp == 1, 1); 105 | } 106 | 107 | #[test( 108 | user1 = @user_1, 109 | user2 = @user_2, 110 | user3 = @user_3, 111 | user4 = @user_4 112 | )] 113 | public fun test_add_liquidity( 114 | user1: &signer, 115 | user2: &signer, 116 | user3: &signer, 117 | user4: &signer, 118 | ) { 119 | test_env::create_test_env(user1, user2, user3, user4); 120 | 121 | add_amm_liquidity( 122 | user1, 123 | 1*T_BTC_PRECISION, 124 | 10000*T_USDC_PRECISION, 125 | 10, 126 | ); 127 | add_amm_liquidity( 128 | user2, 129 | 2*T_BTC_PRECISION, 130 | 20000*T_USDC_PRECISION, 131 | 10, 132 | ); 133 | add_amm_liquidity( 134 | user3, 135 | 3*T_BTC_PRECISION, 136 | 30000*T_USDC_PRECISION, 137 | 10, 138 | ); 139 | add_amm_liquidity( 140 | user4, 141 | T_BTC_PRECISION/10, 142 | 1000*T_USDC_PRECISION, 143 | 10, 144 | ); 145 | } 146 | 147 | #[test( 148 | user1 = @user_1, 149 | user2 = @user_2, 150 | user3 = @user_3, 151 | user4 = @user_4 152 | )] 153 | public fun test_add_remove_liquidity( 154 | user1: &signer, 155 | user2: &signer, 156 | user3: &signer, 157 | user4: &signer, 158 | ) { 159 | test_env::create_test_env(user1, user2, user3, user4); 160 | 161 | let liq1 = add_amm_liquidity( 162 | user1, 163 | 1*T_BTC_PRECISION, 164 | 10000*T_USDC_PRECISION, 165 | 10, 166 | ); 167 | remove_amm_liquidity(user1, liq1); 168 | 169 | let liq2 = add_amm_liquidity( 170 | user2, 171 | 2*T_BTC_PRECISION, 172 | 20000*T_USDC_PRECISION, 173 | 10, 174 | ); 175 | remove_amm_liquidity(user2, liq2); 176 | 177 | let liq3 = add_amm_liquidity( 178 | user3, 179 | 3*T_BTC_PRECISION, 180 | 30000*T_USDC_PRECISION, 181 | 10, 182 | ); 183 | remove_amm_liquidity(user3, liq3); 184 | 185 | let liq4 = add_amm_liquidity( 186 | user4, 187 | T_BTC_PRECISION/10, 188 | 1000*T_USDC_PRECISION, 189 | 10, 190 | ); 191 | remove_amm_liquidity(user4, liq4); 192 | } 193 | 194 | // quote_out linear quote out 195 | #[test_only] 196 | fun test_sell_exact_base( 197 | user: &signer, 198 | base_in: u64, 199 | quote_out: u64 200 | ) { 201 | let amt_out = router::get_amount_out(base_in, true); 202 | quote_out; 203 | // debug::print(&amt_out); 204 | // debug::print("e_out); 205 | // debug::print(&100000000000000001); 206 | router::sell_exact_base( 207 | user, 208 | base_in, 209 | amt_out 210 | ); 211 | } 212 | 213 | #[test( 214 | user1 = @user_1, 215 | user2 = @user_2, 216 | user3 = @user_3, 217 | user4 = @user_4 218 | )] 219 | public fun test_amm_sell_exact_base_e2e( 220 | user1: &signer, 221 | user2: &signer, 222 | user3: &signer, 223 | user4: &signer, 224 | ) { 225 | test_env::create_test_env(user1, user2, user3, user4); 226 | 227 | add_amm_liquidity( 228 | user1, 229 | 1*T_BTC_PRECISION, 230 | 10000*T_USDC_PRECISION, 231 | 10, 232 | ); 233 | 234 | test_sell_exact_base( 235 | user2, 236 | 1*T_BTC_PRECISION/100, 237 | 100*T_USDC_PRECISION 238 | ); 239 | } 240 | 241 | #[test_only] 242 | fun test_sell_exact_quote( 243 | user: &signer, 244 | quote_in: u64, 245 | base_out: u64 246 | ) { 247 | let amt_out = router::get_amount_out(quote_in, false); 248 | base_out; 249 | // debug::print(&amt_out); 250 | // debug::print(&base_out); 251 | // debug::print(&100000000000000002); 252 | router::sell_exact_quote( 253 | user, 254 | quote_in, 255 | amt_out 256 | ); 257 | } 258 | 259 | #[test( 260 | user1 = @user_1, 261 | user2 = @user_2, 262 | user3 = @user_3, 263 | user4 = @user_4 264 | )] 265 | public fun test_amm_swap_exact_quote_e2e( 266 | user1: &signer, 267 | user2: &signer, 268 | user3: &signer, 269 | user4: &signer, 270 | ) { 271 | test_env::create_test_env(user1, user2, user3, user4); 272 | 273 | add_amm_liquidity( 274 | user1, 275 | 1*T_BTC_PRECISION, 276 | 10000*T_USDC_PRECISION, 277 | 10, 278 | ); 279 | 280 | test_sell_exact_quote( 281 | user2, 282 | 100*T_USDC_PRECISION, 283 | 1*T_BTC_PRECISION/100, 284 | ); 285 | } 286 | 287 | #[test_only] 288 | fun test_buy_exact_quote( 289 | user: &signer, 290 | quote_out: u64, 291 | base_in: u64 292 | ) { 293 | let amt_in = router::get_amount_in(quote_out, false); 294 | base_in; 295 | // debug::print(&amt_in); 296 | // debug::print(&base_in); 297 | // debug::print(&100000000000000003); 298 | router::buy_exact_quote( 299 | user, 300 | quote_out, 301 | amt_in 302 | ); 303 | } 304 | 305 | #[test( 306 | user1 = @user_1, 307 | user2 = @user_2, 308 | user3 = @user_3, 309 | user4 = @user_4 310 | )] 311 | public fun test_amm_buy_exact_quote_e2e( 312 | user1: &signer, 313 | user2: &signer, 314 | user3: &signer, 315 | user4: &signer, 316 | ) { 317 | test_env::create_test_env(user1, user2, user3, user4); 318 | 319 | add_amm_liquidity( 320 | user1, 321 | 1*T_BTC_PRECISION, 322 | 10000*T_USDC_PRECISION, 323 | 10, 324 | ); 325 | 326 | test_buy_exact_quote( 327 | user2, 328 | 100*T_USDC_PRECISION, 329 | 1*T_BTC_PRECISION/100, 330 | ); 331 | } 332 | 333 | #[test_only] 334 | fun test_buy_exact_base( 335 | user: &signer, 336 | quote_out: u64, 337 | base_in: u64 338 | ) { 339 | let amt_in = router::get_amount_in(quote_out, true); 340 | base_in; 341 | // debug::print(&amt_in); 342 | // debug::print(&base_in); 343 | // debug::print(&100000000000000003); 344 | router::buy_exact_base( 345 | user, 346 | quote_out, 347 | amt_in 348 | ); 349 | } 350 | 351 | #[test( 352 | user1 = @user_1, 353 | user2 = @user_2, 354 | user3 = @user_3, 355 | user4 = @user_4 356 | )] 357 | public fun test_amm_buy_exact_base_e2e( 358 | user1: &signer, 359 | user2: &signer, 360 | user3: &signer, 361 | user4: &signer, 362 | ) { 363 | test_env::create_test_env(user1, user2, user3, user4); 364 | 365 | add_amm_liquidity( 366 | user1, 367 | 1*T_BTC_PRECISION, 368 | 10000*T_USDC_PRECISION, 369 | 10, 370 | ); 371 | 372 | test_buy_exact_base( 373 | user2, 374 | 1*T_BTC_PRECISION/100, 375 | 100*T_USDC_PRECISION, 376 | ); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/sources/utils.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 pairs 11 | /// 12 | module sea::utils { 13 | use std::signer::address_of; 14 | use aptos_framework::coin; 15 | 16 | /// When provided CoinType is not a coin. 17 | const E_IS_NOT_COIN: u64 = 3000; 18 | 19 | /// Check if provided generic `CoinType` is a coin. 20 | public fun assert_is_coin() { 21 | assert!(coin::is_coin_initialized(), E_IS_NOT_COIN); 22 | } 23 | 24 | public fun register_coin_if_not_exist( 25 | account: &signer 26 | ) { 27 | let account_addr = address_of(account); 28 | if (!coin::is_account_registered(account_addr)) { 29 | coin::register(account); 30 | } 31 | } 32 | 33 | // quote_qty = qty * price / price_ratio 34 | public fun calc_quote_qty( 35 | qty: u64, 36 | price: u64, 37 | price_ratio: u64, 38 | ): u64 { 39 | let quote_qty = ((qty as u128) * (price as u128)/(price_ratio as u128)); 40 | 41 | (quote_qty as u64) 42 | } 43 | 44 | public fun calc_quote_qty_u128( 45 | qty: u128, 46 | price: u128, 47 | price_ratio: u128, 48 | ): u128 { 49 | (qty) * (price)/(price_ratio) 50 | } 51 | 52 | // base_qty = quote_qty * price_ratio / price 53 | public fun calc_base_qty( 54 | quote_qty: u64, 55 | price: u64, 56 | price_ratio: u64, 57 | ): u64 { 58 | (((quote_qty as u128) * (price_ratio as u128) / (price as u128)) as u64) 59 | } 60 | 61 | } 62 | --------------------------------------------------------------------------------