├── .gitignore ├── .idea ├── .gitignore ├── cetus-amm.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── aptos ├── Move.toml └── sources │ ├── amm_config.move │ ├── amm_math.move │ ├── amm_router.move │ ├── amm_script.move │ ├── amm_swap.move │ ├── amm_utils.move │ └── u256.move └── sui ├── Move.toml └── sources ├── amm_config.move ├── amm_math.move ├── amm_router.move ├── amm_script.move ├── amm_swap.move └── amm_utils.move /.gitignore: -------------------------------------------------------------------------------- 1 | aptos/build/ 2 | sui/build/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/cetus-amm.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cetus Amm 2 | 3 | -------------------------------------------------------------------------------- /aptos/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = 'Cetue-AMM' 3 | version = '1.0.0' 4 | [dependencies.AptosFramework] 5 | git = 'https://github.com/aptos-labs/aptos-core.git' 6 | rev = '8399cd1c7b9662d3a6a09c28363c5f66f0839c41' 7 | subdir = 'aptos-move/framework/aptos-framework' 8 | 9 | [addresses] 10 | cetus_amm = "_" 11 | -------------------------------------------------------------------------------- /aptos/sources/amm_config.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_config { 2 | use std::error; 3 | use std::signer; 4 | // 5 | // Errors 6 | // 7 | const ENOT_HAS_PRIVILEGE: u64 = 1001; 8 | const EPOOL_PAUSE: u64 = 1002; 9 | 10 | const DEFAULT_TRADE_FEE_NUMERATOR: u64 = 2; 11 | const DEFAULT_TRADE_FEE_DENOMINATOR: u64 = 1000; 12 | const DEFAULT_PROTOCOL_FEE_NUMERATOR: u64 = 2; 13 | const DEFAULT_PROTOCOL_FEE_DENOMINATOR: u64 = 10; 14 | 15 | struct PoolFeeConfig has key { 16 | trade_fee_numerator: u64, 17 | trade_fee_denominator: u64, 18 | 19 | protocol_fee_numerator: u64, 20 | protocol_fee_denominator: u64, 21 | } 22 | 23 | struct PoolPauseStatus has key { 24 | pause: bool, 25 | } 26 | 27 | public fun set_pool_fee_config( 28 | account: &signer, 29 | trade_fee_numerator: u64, 30 | trade_fee_denominator: u64, 31 | protocol_fee_numerator: u64, 32 | protocol_fee_denominator: u64) acquires PoolFeeConfig { 33 | 34 | assert_admin(account); 35 | let addr = signer::address_of(account); 36 | if (!exists>(addr)) { 37 | move_to(account, PoolFeeConfig{ 38 | trade_fee_numerator, 39 | trade_fee_denominator, 40 | protocol_fee_numerator, 41 | protocol_fee_denominator, 42 | }); 43 | } else { 44 | let fee_config = borrow_global_mut>(addr); 45 | fee_config.trade_fee_numerator = trade_fee_numerator; 46 | fee_config.trade_fee_denominator = trade_fee_denominator; 47 | fee_config.protocol_fee_numerator = protocol_fee_numerator; 48 | fee_config.protocol_fee_denominator = protocol_fee_denominator; 49 | } 50 | 51 | } 52 | 53 | public fun set_pool_pause( 54 | account: &signer, 55 | pause: bool) acquires PoolPauseStatus { 56 | 57 | assert_admin(account); 58 | let addr = signer::address_of(account); 59 | if (!exists(addr)) { 60 | move_to(account, PoolPauseStatus{ pause }); 61 | } else { 62 | let status = borrow_global_mut(addr); 63 | status.pause = pause; 64 | } 65 | } 66 | 67 | public fun get_trade_fee(): (u64, u64) acquires PoolFeeConfig { 68 | if (exists>(admin_address())) { 69 | let fee_config = borrow_global>(admin_address()); 70 | (fee_config.trade_fee_numerator, fee_config.trade_fee_denominator) 71 | } else { 72 | (DEFAULT_TRADE_FEE_NUMERATOR, DEFAULT_TRADE_FEE_DENOMINATOR) 73 | } 74 | } 75 | 76 | public fun get_protocol_fee(): (u64, u64) acquires PoolFeeConfig { 77 | if (exists>(admin_address())) { 78 | let fee_config = borrow_global>(admin_address()); 79 | (fee_config.protocol_fee_numerator, fee_config.protocol_fee_denominator) 80 | } else { 81 | (DEFAULT_PROTOCOL_FEE_NUMERATOR, DEFAULT_PROTOCOL_FEE_DENOMINATOR) 82 | } 83 | } 84 | 85 | public fun get_pool_pause(): bool acquires PoolPauseStatus { 86 | borrow_global(admin_address()).pause 87 | } 88 | 89 | public fun admin_address(): address { 90 | @cetus_amm 91 | } 92 | 93 | public fun assert_admin(account: &signer) { 94 | assert!( 95 | signer::address_of(account) == admin_address(), 96 | error::permission_denied(ENOT_HAS_PRIVILEGE)); 97 | } 98 | 99 | public fun assert_pause() acquires PoolPauseStatus { 100 | assert!( 101 | !get_pool_pause(), 102 | error::unavailable(EPOOL_PAUSE)); 103 | } 104 | } -------------------------------------------------------------------------------- /aptos/sources/amm_math.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_math { 2 | use cetus_amm::u256::{Self, U256}; 3 | use std::error; 4 | 5 | // 6 | // Errors 7 | // 8 | const EDIVIDE_BY_ZERO: u64 = 2001; 9 | const EPARAMETER_INVALID: u64 = 2001; 10 | const U128_MAX:u128 = 340282366920938463463374607431768211455; 11 | const U64_MAX: u128 = 18446744073709551615; 12 | 13 | public fun safe_compare_mul_u128(a1: u128, b1: u128, a2: u128, b2: u128): u8 { 14 | let left = u256::mul(u256::from_u128(a1), u256::from_u128(b1)); 15 | let right = u256::mul(u256::from_u128(a2), u256::from_u128(b2)); 16 | u256::compare(&left, &right) 17 | } 18 | 19 | public fun safe_mul_div_u128(x: u128, y: u128, z: u128): u128 { 20 | assert!(z != 0, EDIVIDE_BY_ZERO); 21 | assert!(x <= U64_MAX, EPARAMETER_INVALID); 22 | assert!(y <= U64_MAX, EPARAMETER_INVALID); 23 | x * y / z 24 | } 25 | 26 | public fun mul_div_u128(x: u128, y: u128, z: u128): U256 { 27 | if (z == 0) { 28 | abort error::aborted(EDIVIDE_BY_ZERO) 29 | }; 30 | 31 | let x_u256 = u256::from_u128(x); 32 | let y_u256 = u256::from_u128(y); 33 | let z_u256 = u256::from_u128(z); 34 | u256::div(u256::mul(x_u256, y_u256), z_u256) 35 | } 36 | 37 | public fun quote(amount_a: u128, reserve_a: u128, reserve_b: u128): u128 { 38 | assert!(amount_a > 0, error::invalid_argument(EPARAMETER_INVALID)); 39 | assert!(reserve_a > 0 && reserve_b> 0, error::invalid_argument(EPARAMETER_INVALID)); 40 | let amount_b = safe_mul_div_u128(amount_a,reserve_b,reserve_a); 41 | amount_b 42 | } 43 | 44 | public fun sqrt(y: u128): u128 { 45 | if (y > 3) { 46 | let z = y; 47 | let x = y / 2 + 1; 48 | while (x < z) { 49 | z = x; 50 | x = (y / x + x) / 2; 51 | }; 52 | return z 53 | }; 54 | if (y > 0) 1 else 0 55 | } 56 | 57 | public fun min(x: u128, y: u128): u128 { 58 | if (x < y) x else y 59 | } 60 | 61 | #[test] 62 | public entry fun test_safe_mul_div_u128() { 63 | let x: u128 = 9446744073709551615; 64 | let y: u128 = 1009855555; 65 | let z: u128 = 3979; 66 | let _r_expected:u128 = 2397548876476230247541334; 67 | let r = Self::safe_mul_div_u128(x, y, z); 68 | assert!(r == _r_expected, 3001); 69 | } 70 | 71 | #[test] 72 | public entry fun test_sqrt_by_max_u128() { 73 | let _r_expected:u128 = 18446744073709551615; 74 | let r = Self::sqrt(U128_MAX); 75 | assert!(r == _r_expected, 3004); 76 | } 77 | } -------------------------------------------------------------------------------- /aptos/sources/amm_router.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_router { 2 | use std::error; 3 | use std::signer; 4 | use cetus_amm::amm_config; 5 | use cetus_amm::amm_swap::{PoolLiquidityCoin,Self}; 6 | use cetus_amm::amm_utils; 7 | use aptos_framework::coin::{Self, Coin}; 8 | use aptos_std::comparator; 9 | use cetus_amm::amm_math::{Self, quote}; 10 | 11 | 12 | const ESWAP_B_OUT_LESSTHAN_EXPECTED: u64 = 3001; 13 | const ESWAP_A_IN_OVER_LIMIT_MAX: u64 = 3002; 14 | const EINVALID_COIN_PAIR: u64 = 3003; 15 | const ELIQUIDITY_INSUFFICIENT_B_AMOUNT: u64 = 3004; 16 | const ELIQUIDITY_OVERLIMIT_X_DESIRED: u64 = 3005; 17 | const ELIQUIDITY_INSUFFICIENT_A_AMOUNT: u64 = 3006; 18 | const ELIQUIDITY_ADD_LIQUIDITY_FAILED: u64 = 3007; 19 | 20 | public fun set_pool_fee_config( 21 | account: &signer, 22 | trade_fee_numerator: u64, 23 | trade_fee_denominator: u64, 24 | protocol_fee_numerator: u64, 25 | protocol_fee_denominator: u64 26 | ) { 27 | if (amm_swap::get_pool_direction()) { 28 | amm_config::set_pool_fee_config( 29 | account, 30 | trade_fee_numerator, 31 | trade_fee_denominator, 32 | protocol_fee_numerator, 33 | protocol_fee_denominator); 34 | } else { 35 | amm_config::set_pool_fee_config( 36 | account, 37 | trade_fee_numerator, 38 | trade_fee_denominator, 39 | protocol_fee_numerator, 40 | protocol_fee_denominator); 41 | } 42 | } 43 | 44 | public fun set_pause_status(account: &signer, pause:bool) { 45 | amm_config::set_pool_pause(account, pause); 46 | } 47 | 48 | public fun init_pool(account: &signer, protocol_fee_to: address) { 49 | //compare coins 50 | assert!( 51 | !comparator::is_equal(&amm_utils::compare_coin()), 52 | error::internal(EINVALID_COIN_PAIR)); 53 | 54 | amm_swap::init_pool_v2 ( 55 | account, 56 | protocol_fee_to); 57 | } 58 | 59 | /// Add liquidity for user 60 | public fun add_liquidity( 61 | account: &signer, 62 | amount_a_desired: u128, 63 | amount_b_desired: u128, 64 | amount_a_min: u128, 65 | amount_b_min: u128) { 66 | if (amm_swap::get_pool_direction()) { 67 | add_liquidity_internal( 68 | account, 69 | amount_a_desired, 70 | amount_b_desired, 71 | amount_a_min, 72 | amount_b_min, 73 | ); 74 | } else { 75 | add_liquidity_internal( 76 | account, 77 | amount_b_desired, 78 | amount_a_desired, 79 | amount_b_min, 80 | amount_a_min, 81 | ); 82 | } 83 | } 84 | 85 | fun add_liquidity_internal( 86 | account: &signer, 87 | amount_a_desired: u128, 88 | amount_b_desired: u128, 89 | amount_a_min: u128, 90 | amount_b_min: u128) { 91 | let (amount_a, amount_b) = calculate_amount_for_liquidity_internal( 92 | amount_a_desired, 93 | amount_b_desired, 94 | amount_a_min, 95 | amount_b_min); 96 | let coinA = coin::withdraw(account,(amount_a as u64)); 97 | let coinB = coin::withdraw(account,(amount_b as u64)); 98 | let sender = signer::address_of(account); 99 | let liquidity_coin = amm_swap::mint_and_emit_event_v2( 100 | sender, 101 | coinA, 102 | coinB); 103 | assert!(coin::value(&liquidity_coin) > 0, error::invalid_argument(ELIQUIDITY_ADD_LIQUIDITY_FAILED)); 104 | if (!coin::is_account_registered>(sender)) coin::register>(account); 105 | coin::deposit(sender,liquidity_coin); 106 | } 107 | 108 | fun calculate_amount_for_liquidity_internal( 109 | amount_a_desired: u128, 110 | amount_b_desired: u128, 111 | amount_a_min: u128, 112 | amount_b_min: u128,): (u128, u128) { 113 | let (reserve_a, reserve_b) = amm_swap::get_reserves(); 114 | if (reserve_a == 0 && reserve_b == 0) { 115 | return (amount_a_desired, amount_b_desired) 116 | } else { 117 | let amount_b_optimal = amm_math::quote(amount_a_desired, reserve_a, reserve_b); 118 | if (amount_b_optimal <= amount_b_desired) { 119 | assert!(amount_b_optimal >= amount_b_min, error::internal(ELIQUIDITY_INSUFFICIENT_B_AMOUNT)); 120 | return (amount_a_desired, amount_b_optimal) 121 | } else { 122 | let amount_a_optimal = quote(amount_b_desired, reserve_b, reserve_a); 123 | assert!(amount_a_optimal <= amount_a_desired, error::internal(ELIQUIDITY_OVERLIMIT_X_DESIRED)); 124 | assert!(amount_a_optimal >= amount_a_min, error::internal(ELIQUIDITY_INSUFFICIENT_A_AMOUNT)); 125 | return (amount_a_optimal, amount_b_desired) 126 | } 127 | } 128 | } 129 | 130 | /// Remove liquidity for user 131 | public fun remove_liquidity( 132 | account: &signer, 133 | liquidity: u128, 134 | amount_a_min: u128, 135 | amount_b_min: u128) { 136 | if (amm_swap::get_pool_direction()) { 137 | remove_liquidity_internal( 138 | account, 139 | liquidity, 140 | amount_a_min, 141 | amount_b_min); 142 | } else { 143 | remove_liquidity_internal( 144 | account, 145 | liquidity, 146 | amount_b_min, 147 | amount_a_min); 148 | } 149 | } 150 | 151 | fun remove_liquidity_internal( 152 | account: &signer, 153 | liquidity: u128, 154 | amount_a_min: u128, 155 | amount_b_min: u128) { 156 | let liquidity_coin = coin::withdraw>(account,(liquidity as u64)); 157 | let sender = signer::address_of(account); 158 | let (coin_a, coin_b) = amm_swap::burn_and_emit_event_v2( 159 | sender, 160 | liquidity_coin); 161 | assert!((coin::value(&coin_a) as u128) >= amount_a_min, error::internal(ELIQUIDITY_INSUFFICIENT_A_AMOUNT)); 162 | assert!((coin::value(&coin_b) as u128) >= amount_b_min, error::internal(ELIQUIDITY_INSUFFICIENT_B_AMOUNT)); 163 | coin::deposit(sender,coin_a); 164 | coin::deposit(sender,coin_b); 165 | } 166 | 167 | public fun swap_exact_coin_for_coin( 168 | account: &signer, 169 | amount_a_in: u128, 170 | amount_b_out_min: u128, 171 | ) { 172 | assert!( 173 | !comparator::is_equal(&amm_utils::compare_coin()), 174 | error::invalid_argument(EINVALID_COIN_PAIR)); 175 | 176 | let sender = signer::address_of(account); 177 | if (!coin::is_account_registered(sender)) coin::register(account); 178 | 179 | let (coin_a_out, coin_b_out); 180 | let (coin_a_fee, coin_b_fee); 181 | let is_forward = amm_swap::get_pool_direction(); 182 | if (is_forward) { 183 | //calc b out amount 184 | let b_out = compute_b_out(amount_a_in, true); 185 | assert!(b_out >= amount_b_out_min, error::internal(ESWAP_B_OUT_LESSTHAN_EXPECTED)); 186 | //withdraw coin a 187 | let coin_a = coin::withdraw(account, (amount_a_in as u64)); 188 | // swap 189 | (coin_a_out, coin_b_out, coin_a_fee, coin_b_fee) = amm_swap::swap_and_emit_event_v2(sender, coin_a, b_out, coin::zero(), 0); 190 | } else { 191 | //calc b out amount 192 | let b_out = compute_b_out(amount_a_in, false); 193 | assert!(b_out >= amount_b_out_min, error::internal(ESWAP_B_OUT_LESSTHAN_EXPECTED)); 194 | //withdraw coin a 195 | let coin_a = coin::withdraw(account, (amount_a_in as u64)); 196 | // sawp 197 | (coin_b_out, coin_a_out, coin_b_fee, coin_a_fee) = amm_swap::swap_and_emit_event_v2(sender, coin::zero(), 0, coin_a, b_out); 198 | }; 199 | //destroy 200 | coin::destroy_zero(coin_a_out); 201 | coin::deposit(sender, coin_b_out); 202 | coin::destroy_zero(coin_b_fee); 203 | //swap protocol fee 204 | amm_swap::handle_swap_protocol_fee_v2(sender, coin_a_fee, is_forward); 205 | } 206 | 207 | public fun swap_exact_coin_for_coin_router2( 208 | account: &signer, 209 | amount_a_in: u128, 210 | amount_b_out_min: u128, 211 | ) { 212 | assert!( 213 | !comparator::is_equal(&amm_utils::compare_coin()), 214 | error::invalid_argument(EINVALID_COIN_PAIR)); 215 | 216 | assert!( 217 | !comparator::is_equal(&amm_utils::compare_coin()), 218 | error::invalid_argument(EINVALID_COIN_PAIR)); 219 | 220 | let (x_out, b_out) = get_amount_out_router2(amount_a_in); 221 | assert!(b_out >= amount_b_out_min, error::internal(ESWAP_B_OUT_LESSTHAN_EXPECTED)); 222 | 223 | swap_exact_coin_for_coin(account, amount_a_in, x_out); 224 | swap_exact_coin_for_coin(account, x_out, amount_b_out_min); 225 | } 226 | 227 | public fun swap_exact_coin_for_coin_router3( 228 | account: &signer, 229 | amount_a_in: u128, 230 | amount_b_out_min: u128 231 | ) { 232 | assert!( 233 | !comparator::is_equal(&amm_utils::compare_coin()), 234 | error::invalid_argument(EINVALID_COIN_PAIR)); 235 | 236 | assert!( 237 | !comparator::is_equal(&amm_utils::compare_coin()), 238 | error::invalid_argument(EINVALID_COIN_PAIR)); 239 | assert!( 240 | !comparator::is_equal(&amm_utils::compare_coin()), 241 | error::invalid_argument(EINVALID_COIN_PAIR)); 242 | 243 | let (x_out, y_out, b_out) = get_amount_out_router3(amount_a_in); 244 | assert!(b_out >= amount_b_out_min, error::internal(ESWAP_B_OUT_LESSTHAN_EXPECTED)); 245 | swap_exact_coin_for_coin(account, amount_a_in, x_out); 246 | swap_exact_coin_for_coin(account, x_out, y_out); 247 | swap_exact_coin_for_coin(account, y_out, amount_b_out_min); 248 | } 249 | 250 | public fun swap_coin_for_exact_coin( 251 | account: &signer, 252 | amount_a_in_max: u128, 253 | amount_b_out: u128, 254 | ) { 255 | assert!( 256 | !comparator::is_equal(&amm_utils::compare_coin()), 257 | error::invalid_argument(EINVALID_COIN_PAIR)); 258 | 259 | let sender = signer::address_of(account); 260 | if (!coin::is_account_registered(sender)) coin::register(account); 261 | 262 | let (coin_a_out, coin_b_out); 263 | let (coin_a_fee, coin_b_fee); 264 | let is_forward = amm_swap::get_pool_direction(); 265 | if(is_forward) { 266 | let a_in = compute_a_in(amount_b_out, true); 267 | assert!(a_in <= amount_a_in_max, error::internal(ESWAP_A_IN_OVER_LIMIT_MAX)); 268 | 269 | let coin_a = coin::withdraw(account, (a_in as u64)); 270 | 271 | (coin_a_out, coin_b_out, coin_a_fee, coin_b_fee) = amm_swap::swap_and_emit_event_v2(sender, coin_a, amount_b_out, coin::zero(), 0); 272 | } else { 273 | let a_in = compute_a_in(amount_b_out, false); 274 | assert!(a_in <= amount_a_in_max, error::internal(ESWAP_A_IN_OVER_LIMIT_MAX)); 275 | 276 | let coin_a = coin::withdraw(account, (a_in as u64)); 277 | 278 | (coin_b_out, coin_a_out, coin_b_fee, coin_a_fee) = amm_swap::swap_and_emit_event_v2(sender, coin::zero(), 0, coin_a, amount_b_out); 279 | }; 280 | 281 | coin::destroy_zero(coin_a_out); 282 | coin::deposit(sender, coin_b_out); 283 | coin::destroy_zero(coin_b_fee); 284 | 285 | amm_swap::handle_swap_protocol_fee_v2(sender, coin_a_fee, is_forward); 286 | } 287 | 288 | public fun swap_coin_for_exact_coin_router2( 289 | account: &signer, 290 | amount_a_in_max: u128, 291 | amount_b_out: u128 292 | ) { 293 | assert!( 294 | !comparator::is_equal(&amm_utils::compare_coin()), 295 | error::invalid_argument(EINVALID_COIN_PAIR)); 296 | 297 | assert!( 298 | !comparator::is_equal(&amm_utils::compare_coin()), 299 | error::invalid_argument(EINVALID_COIN_PAIR)); 300 | 301 | let(x_in, a_in) = get_amount_in_router2(amount_b_out); 302 | assert!(a_in <= amount_a_in_max, error::internal(ESWAP_A_IN_OVER_LIMIT_MAX)); 303 | 304 | swap_coin_for_exact_coin (account,amount_a_in_max,x_in); 305 | swap_coin_for_exact_coin (account,x_in,amount_b_out); 306 | } 307 | 308 | public fun swap_coin_for_exact_coin_router3( 309 | account: &signer, 310 | amount_a_in_max: u128, 311 | amount_b_out: u128 312 | ) { 313 | assert!( 314 | !comparator::is_equal(&amm_utils::compare_coin()), 315 | error::invalid_argument(EINVALID_COIN_PAIR)); 316 | 317 | assert!( 318 | !comparator::is_equal(&amm_utils::compare_coin()), 319 | error::invalid_argument(EINVALID_COIN_PAIR)); 320 | assert!( 321 | !comparator::is_equal(&amm_utils::compare_coin()), 322 | error::invalid_argument(EINVALID_COIN_PAIR)); 323 | 324 | let(y_in, x_in, a_in) = get_amount_in_router3(amount_b_out); 325 | assert!(a_in <= amount_a_in_max, error::internal(ESWAP_A_IN_OVER_LIMIT_MAX)); 326 | 327 | swap_coin_for_exact_coin (account,amount_a_in_max,x_in); 328 | swap_coin_for_exact_coin (account,x_in,y_in); 329 | swap_coin_for_exact_coin (account,y_in,amount_b_out); 330 | } 331 | 332 | public fun compute_b_out(amount_a_in: u128, is_forward: bool): u128 { 333 | if (is_forward) { 334 | let (fee_numerator, fee_denominator) = amm_config::get_trade_fee(); 335 | let (reserve_a, reserve_b) = amm_swap::get_reserves(); 336 | amm_utils::get_amount_out(amount_a_in, (reserve_a as u128), (reserve_b as u128), fee_numerator, fee_denominator) 337 | } else { 338 | let (fee_numerator, fee_denominator) = amm_config::get_trade_fee(); 339 | let (reserve_b, reserve_a) = amm_swap::get_reserves(); 340 | amm_utils::get_amount_out(amount_a_in, (reserve_a as u128), (reserve_b as u128), fee_numerator, fee_denominator) 341 | } 342 | } 343 | 344 | public fun compute_a_in(amount_b_out: u128, is_forward: bool): u128 { 345 | if (is_forward) { 346 | let (fee_numerator, fee_denominator) = amm_config::get_trade_fee(); 347 | let (reserve_a, reserve_b) = amm_swap::get_reserves(); 348 | amm_utils::get_amount_in(amount_b_out, reserve_a, reserve_b, fee_numerator, fee_denominator) 349 | } else { 350 | let (fee_numerator, fee_denominator) = amm_config::get_trade_fee(); 351 | let (reserve_b, reserve_a) = amm_swap::get_reserves(); 352 | amm_utils::get_amount_in(amount_b_out, reserve_a, reserve_b, fee_numerator, fee_denominator) 353 | } 354 | } 355 | 356 | public fun get_amount_out_router2(amount_a_in: u128): (u128, u128) { 357 | let is_forward = amm_swap::get_pool_direction(); 358 | let x_out = compute_b_out(amount_a_in, is_forward); 359 | let is_forward = amm_swap::get_pool_direction(); 360 | let b_out = compute_b_out(x_out, is_forward); 361 | (x_out, b_out) 362 | } 363 | 364 | public fun get_amount_in_router2(amount_b_out: u128): (u128, u128) { 365 | let is_forward = amm_swap::get_pool_direction(); 366 | let x_in = compute_a_in(amount_b_out, is_forward); 367 | let is_forward = amm_swap::get_pool_direction(); 368 | let a_in = compute_a_in(x_in, is_forward); 369 | (x_in, a_in) 370 | } 371 | 372 | public fun get_amount_out_router3(amount_a_in: u128): (u128, u128, u128) { 373 | let (x_out, y_out) = get_amount_out_router2(amount_a_in); 374 | let is_forward = amm_swap::get_pool_direction(); 375 | let b_out = compute_b_out(y_out, is_forward); 376 | (x_out, y_out, b_out) 377 | } 378 | 379 | public fun get_amount_in_router3(amount_b_out: u128): (u128, u128, u128) { 380 | let (y_in, x_in) = get_amount_in_router2(amount_b_out); 381 | let is_forward = amm_swap::get_pool_direction(); 382 | let a_in = compute_a_in(x_in, is_forward); 383 | (y_in, x_in, a_in) 384 | } 385 | 386 | public fun swap(account: address, coin_in: Coin): Coin { 387 | assert!( 388 | !comparator::is_equal(&amm_utils::compare_coin()), 389 | error::invalid_argument(EINVALID_COIN_PAIR)); 390 | 391 | let coin_in_value = coin::value(&coin_in); 392 | 393 | let (coin_a_out, coin_b_out); 394 | let (coin_a_fee, coin_b_fee); 395 | let is_forward = amm_swap::get_pool_direction(); 396 | if (is_forward) { 397 | //calc b out amount 398 | let b_out = compute_b_out((coin_in_value as u128), true); 399 | // swap 400 | (coin_a_out, coin_b_out, coin_a_fee, coin_b_fee) = amm_swap::swap_and_emit_event_v2(account, coin_in, b_out, coin::zero(), 0); 401 | } else { 402 | //calc b out amount 403 | let b_out = compute_b_out((coin_in_value as u128), false); 404 | // sawp 405 | (coin_b_out, coin_a_out, coin_b_fee, coin_a_fee) = amm_swap::swap_and_emit_event_v2(account, coin::zero(), 0, coin_in, b_out); 406 | }; 407 | //destroy 408 | coin::destroy_zero(coin_a_out); 409 | coin::destroy_zero(coin_b_fee); 410 | //swap protocol fee 411 | amm_swap::handle_swap_protocol_fee_v2(account, coin_a_fee, is_forward); 412 | coin_b_out 413 | } 414 | 415 | public fun swap_into(account: address, coin_in: &mut Coin, amount_out: u128): Coin { 416 | assert!( 417 | !comparator::is_equal(&amm_utils::compare_coin()), 418 | error::invalid_argument(EINVALID_COIN_PAIR)); 419 | 420 | let amount_in_max = coin::value(coin_in); 421 | 422 | let (coin_a_out, coin_b_out); 423 | let (coin_a_fee, coin_b_fee); 424 | let is_forward = amm_swap::get_pool_direction(); 425 | if(is_forward) { 426 | let a_in = compute_a_in(amount_out, true); 427 | assert!(a_in <= (amount_in_max as u128), error::internal(ESWAP_A_IN_OVER_LIMIT_MAX)); 428 | 429 | let coin_a = coin::extract(coin_in, (a_in as u64)); 430 | 431 | (coin_a_out, coin_b_out, coin_a_fee, coin_b_fee) = amm_swap::swap_and_emit_event_v2(account, coin_a, amount_out, coin::zero(), 0); 432 | } else { 433 | let a_in = compute_a_in(amount_out, false); 434 | assert!(a_in <= (amount_in_max as u128), error::internal(ESWAP_A_IN_OVER_LIMIT_MAX)); 435 | 436 | let coin_a = coin::extract(coin_in, (a_in as u64)); 437 | 438 | (coin_b_out, coin_a_out, coin_b_fee, coin_a_fee) = amm_swap::swap_and_emit_event_v2(account, coin::zero(), 0, coin_a, amount_out); 439 | }; 440 | 441 | coin::destroy_zero(coin_a_out); 442 | coin::destroy_zero(coin_b_fee); 443 | 444 | amm_swap::handle_swap_protocol_fee_v2(account, coin_a_fee, is_forward); 445 | coin_b_out 446 | } 447 | } -------------------------------------------------------------------------------- /aptos/sources/amm_script.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_script { 2 | use cetus_amm::amm_router; 3 | 4 | public entry fun set_pool_fee_config( 5 | account: signer, 6 | trade_fee_numerator: u64, 7 | trade_fee_denominator: u64, 8 | protocol_fee_numerator: u64, 9 | protocol_fee_denominator: u64 10 | ) { 11 | amm_router::set_pool_fee_config( 12 | &account, 13 | trade_fee_numerator, 14 | trade_fee_denominator, 15 | protocol_fee_numerator, 16 | protocol_fee_denominator); 17 | } 18 | 19 | public entry fun init_pool(account: signer, protocol_fee_to: address) { 20 | amm_router::init_pool ( 21 | &account, 22 | protocol_fee_to); 23 | } 24 | 25 | public entry fun add_liquidity( 26 | account: signer, 27 | amount_a_desired: u128, 28 | amount_b_desired: u128, 29 | amount_a_min: u128, 30 | amount_b_min: u128) { 31 | amm_router::add_liquidity( 32 | &account, 33 | amount_a_desired, 34 | amount_b_desired, 35 | amount_a_min, 36 | amount_b_min); 37 | } 38 | 39 | public entry fun remove_liquidity( 40 | account: signer, 41 | liquidity: u128, 42 | amount_a_min: u128, 43 | amount_b_min: u128) { 44 | amm_router::remove_liquidity( 45 | &account, 46 | liquidity, 47 | amount_a_min, 48 | amount_b_min); 49 | } 50 | 51 | public entry fun swap_exact_coin_for_coin( 52 | account: signer, 53 | amount_a_in: u128, 54 | amount_b_out_min: u128, 55 | ) { 56 | amm_router::swap_exact_coin_for_coin ( 57 | &account, 58 | amount_a_in, 59 | amount_b_out_min); 60 | } 61 | 62 | public entry fun swap_exact_coin_for_coin_router2( 63 | account: signer, 64 | amount_a_in: u128, 65 | amount_b_out_min: u128 66 | ) { 67 | amm_router::swap_exact_coin_for_coin_router2 ( 68 | &account, 69 | amount_a_in, 70 | amount_b_out_min); 71 | } 72 | 73 | public entry fun swap_exact_coin_for_coin_router3( 74 | account: signer, 75 | amount_a_in: u128, 76 | amount_b_out_min: u128 77 | ) { 78 | amm_router::swap_exact_coin_for_coin_router3 ( 79 | &account, 80 | amount_a_in, 81 | amount_b_out_min); 82 | } 83 | 84 | public entry fun swap_coin_for_exact_coin( 85 | account: signer, 86 | amount_a_in_max: u128, 87 | amount_b_out: u128, 88 | ) { 89 | amm_router::swap_coin_for_exact_coin ( 90 | &account, 91 | amount_a_in_max, 92 | amount_b_out); 93 | } 94 | 95 | public entry fun swap_coin_for_exact_coin_router2( 96 | account: signer, 97 | amount_a_in_max: u128, 98 | amount_b_out: u128, 99 | ) { 100 | amm_router::swap_coin_for_exact_coin_router2 ( 101 | &account, 102 | amount_a_in_max, 103 | amount_b_out); 104 | } 105 | 106 | public entry fun swap_coin_for_exact_coin_router3( 107 | account: signer, 108 | amount_a_in_max: u128, 109 | amount_b_out: u128 110 | ) { 111 | amm_router::swap_coin_for_exact_coin_router3 ( 112 | &account, 113 | amount_a_in_max, 114 | amount_b_out); 115 | } 116 | 117 | public entry fun set_pause_status(account: signer, pause:bool) { 118 | amm_router::set_pause_status(&account, pause); 119 | } 120 | } -------------------------------------------------------------------------------- /aptos/sources/amm_swap.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_swap { 2 | use std::string; 3 | use std::error; 4 | use std::signer; 5 | use std::option; 6 | 7 | use aptos_framework::event::{Self, EventHandle}; 8 | use aptos_framework::account::{Self, new_event_handle}; 9 | use aptos_framework::coin::{Self, Coin, BurnCapability, MintCapability}; 10 | use cetus_amm::amm_utils; 11 | use cetus_amm::amm_config::{Self, assert_admin}; 12 | use cetus_amm::amm_math::{Self, sqrt, min}; 13 | use aptos_std::type_info; 14 | 15 | friend cetus_amm::amm_router; 16 | 17 | const MINIMUM_LIQUIDITY: u128 = 10; 18 | 19 | // 20 | // Errors 21 | // 22 | 23 | const EINVALID_COIN_PAIR: u64 = 4001; 24 | const EACCOUNT_NOT_EXISTED: u64 = 4002; 25 | const ELIQUIDITY_INSUFFICIENT_MINTED: u64 = 4003; 26 | const ELIQUIDITY_SWAP_BURN_CALC_INVALID: u64 = 4004; 27 | const ECOIN_INSUFFICIENT: u64 = 4005; 28 | const ESWAPOUT_CALC_INVALID: u64 = 4006; 29 | const EPOOL_DOSE_NOT_EXIST: u64 = 4007; 30 | const EPOOL_ALREADY_EXISTS: u64 = 4008; 31 | const ELIQUIDITY_CALC_INVALID: u64 = 4009; 32 | const EFUNCTION_DEPRECATED: u64 = 4010; 33 | 34 | const EQUAL: u8 = 0; 35 | const LESS_THAN: u8 = 1; 36 | const GREATER_THAN: u8 = 2; 37 | 38 | struct PoolLiquidityCoin {} 39 | 40 | struct Pool has key { 41 | coin_a: Coin, 42 | coin_b: Coin, 43 | 44 | mint_capability: MintCapability>, 45 | burn_capability: BurnCapability>, 46 | 47 | locked_liquidity: Coin>, 48 | 49 | protocol_fee_to: address 50 | } 51 | 52 | struct InitPoolEvent has store, drop { 53 | coin_a_info: type_info::TypeInfo, 54 | coin_b_info: type_info::TypeInfo, 55 | account: address, 56 | protocol_fee_to: address, 57 | } 58 | 59 | struct AddLiquidityEvent has store, drop { 60 | liquidity: u128, 61 | account: address, 62 | coin_a_info: type_info::TypeInfo, 63 | coin_b_info: type_info::TypeInfo, 64 | amount_a: u128, 65 | amount_b: u128, 66 | } 67 | 68 | struct RemoveLiquidityEvent has store, drop { 69 | liquidity: u128, 70 | account: address, 71 | coin_a_info: type_info::TypeInfo, 72 | coin_b_info: type_info::TypeInfo, 73 | amount_a: u128, 74 | amount_b: u128, 75 | } 76 | 77 | struct SwapEvent has store, drop { 78 | coin_a_info: type_info::TypeInfo, 79 | coin_b_info: type_info::TypeInfo, 80 | account: address, 81 | a_in: u128, 82 | a_out: u128, 83 | b_in: u128, 84 | b_out: u128, 85 | } 86 | 87 | struct SwapFeeEvent has store, drop { 88 | coin_a_info: type_info::TypeInfo, 89 | coin_b_info: type_info::TypeInfo, 90 | account: address, 91 | fee_address: address, 92 | fee_a_out: u128, 93 | fee_b_out: u128, 94 | } 95 | 96 | struct PoolSwapEventHandle has key { 97 | init_pool_events: EventHandle, 98 | add_liquidity_events: EventHandle, 99 | remove_liquidity_events: EventHandle, 100 | swap_events: EventHandle, 101 | swap_fee_events: EventHandle, 102 | } 103 | 104 | public fun init_pool(_account: &signer, _protocol_fee_to: address){ 105 | abort EFUNCTION_DEPRECATED 106 | } 107 | 108 | public(friend) fun init_pool_v2(account: &signer, protocol_fee_to: address) acquires PoolSwapEventHandle { 109 | //check coin type 110 | amm_utils::assert_is_coin(); 111 | amm_utils::assert_is_coin(); 112 | 113 | assert!(!exists>(amm_config::admin_address()), EPOOL_ALREADY_EXISTS); 114 | assert!(!exists>(amm_config::admin_address()), EPOOL_ALREADY_EXISTS); 115 | 116 | //check admin 117 | assert_admin(account); 118 | 119 | //check protocol_fee_to existed 120 | assert!( 121 | account::exists_at(protocol_fee_to), 122 | error::not_found(EACCOUNT_NOT_EXISTED)); 123 | 124 | //reigister lp coin 125 | let(burn_capability, mint_capability) = register_liquidity_coin(account); 126 | 127 | //make pool 128 | let pool = make_pool(protocol_fee_to, burn_capability, mint_capability); 129 | move_to(account, pool); 130 | 131 | //init event handle 132 | init_event_handle(account); 133 | 134 | //emit init pool event 135 | emit_init_pool_event(signer::address_of(account), protocol_fee_to); 136 | } 137 | 138 | public fun mint_and_emit_event( 139 | _account: &signer, 140 | _coinA: Coin, 141 | _coinB: Coin): Coin>{ 142 | abort EFUNCTION_DEPRECATED 143 | } 144 | 145 | public(friend) fun mint_and_emit_event_v2( 146 | account: address, 147 | coinA: Coin, 148 | coinB: Coin): Coin> acquires Pool, PoolSwapEventHandle { 149 | let amount_a = (coin::value(&coinA) as u128); 150 | let amount_b = (coin::value(&coinB) as u128); 151 | let liquidity_coin = mint(coinA,coinB); 152 | let event_handle = borrow_global_mut(amm_config::admin_address()); 153 | event::emit_event(&mut event_handle.add_liquidity_events,AddLiquidityEvent{ 154 | liquidity: (coin::value>(&liquidity_coin) as u128), 155 | account: account, 156 | coin_a_info:type_info::type_of(), 157 | coin_b_info:type_info::type_of(), 158 | amount_a, 159 | amount_b 160 | }); 161 | liquidity_coin 162 | } 163 | 164 | fun mint( 165 | coinA: Coin, 166 | coinB: Coin): Coin> acquires Pool { 167 | amm_config::assert_pause(); 168 | 169 | let (reserve_a, reserve_b) = get_reserves(); 170 | 171 | let pool = borrow_global_mut>(amm_config::admin_address()); 172 | 173 | // get deposited amounts 174 | let amountA = (coin::value(&coinA) as u128); 175 | let amountB = (coin::value(&coinB) as u128); 176 | 177 | let total_supply = (*option::borrow(&coin::supply>()) as u128); 178 | let liquidity : u128; 179 | if (total_supply == 0) { 180 | liquidity = sqrt(amountA * amountB) - MINIMUM_LIQUIDITY; 181 | let locked_liquidity = coin::mint>((MINIMUM_LIQUIDITY as u64), &pool.mint_capability); // permanently lock the first MINIMUM_LIQUIDITY tokens 182 | coin::merge(&mut pool.locked_liquidity, locked_liquidity); 183 | } else { 184 | assert!(amountB == amm_math::quote(amountA, reserve_a, reserve_b) 185 | || amountA == amm_math::quote(amountB, reserve_b, reserve_a), error::internal(ELIQUIDITY_CALC_INVALID)); 186 | liquidity = min(amm_math::safe_mul_div_u128(amountA,total_supply,reserve_a), 187 | amm_math::safe_mul_div_u128(amountB,total_supply,reserve_b)); 188 | }; 189 | 190 | assert!(liquidity > 0, error::invalid_argument(ELIQUIDITY_INSUFFICIENT_MINTED)); 191 | 192 | coin::merge(&mut pool.coin_a, coinA); 193 | coin::merge(&mut pool.coin_b, coinB); 194 | 195 | coin::mint>((liquidity as u64), &pool.mint_capability) 196 | } 197 | 198 | public fun burn_and_emit_event( 199 | _account: &signer, 200 | _to_burn: Coin>) : (Coin, Coin) { 201 | abort EFUNCTION_DEPRECATED 202 | } 203 | 204 | public(friend) fun burn_and_emit_event_v2( 205 | account: address, 206 | to_burn: Coin>) : (Coin, Coin) acquires Pool, PoolSwapEventHandle { 207 | let liquidity = (coin::value>(&to_burn) as u128); 208 | let (a_token, b_token) = burn(to_burn); 209 | let event_handle = borrow_global_mut(amm_config::admin_address()); 210 | let amount_a = (coin::value(&a_token) as u128); 211 | let amount_b = (coin::value(&b_token) as u128); 212 | event::emit_event(&mut event_handle.remove_liquidity_events, RemoveLiquidityEvent { 213 | liquidity, 214 | account: account, 215 | coin_a_info:type_info::type_of(), 216 | coin_b_info:type_info::type_of(), 217 | amount_a, 218 | amount_b, 219 | }); 220 | (a_token, b_token) 221 | } 222 | 223 | fun burn(to_burn: Coin>): (Coin, Coin) acquires Pool { 224 | amm_config::assert_pause(); 225 | 226 | let to_burn_value = (coin::value(&to_burn) as u128); 227 | let pool = borrow_global_mut>(amm_config::admin_address()); 228 | let reserve_a = (coin::value(&pool.coin_a) as u128); 229 | let reserve_b = (coin::value(&pool.coin_b) as u128); 230 | let total_supply = *option::borrow(&coin::supply>()); 231 | let amount0 = (amm_math::safe_mul_div_u128(to_burn_value,reserve_a,total_supply) as u64); 232 | let amount1 = (amm_math::safe_mul_div_u128(to_burn_value,reserve_b,total_supply) as u64); 233 | assert!(amount0 > 0 && amount1 > 0, error::internal(ELIQUIDITY_SWAP_BURN_CALC_INVALID)); 234 | 235 | coin::burn(to_burn, &pool.burn_capability); 236 | 237 | (coin::extract(&mut pool.coin_a , amount0), coin::extract(&mut pool.coin_b, amount1)) 238 | } 239 | 240 | public fun swap_and_emit_event( 241 | _account: &signer, 242 | _coin_a_in: Coin, 243 | _coin_b_out: u128, 244 | _coin_b_in: Coin, 245 | _coin_a_out: u128 246 | ) :(Coin, Coin, Coin, Coin) { 247 | abort EFUNCTION_DEPRECATED 248 | } 249 | 250 | public(friend) fun swap_and_emit_event_v2( 251 | account: address, 252 | coin_a_in: Coin, 253 | coin_b_out: u128, 254 | coin_b_in: Coin, 255 | coin_a_out: u128 256 | ) :(Coin, Coin, Coin, Coin) acquires Pool, PoolSwapEventHandle { 257 | let coin_a_in_value = (coin::value(&coin_a_in) as u128); 258 | let coin_b_in_value = (coin::value(&coin_b_in) as u128); 259 | let (coin_a_out, coin_b_out, coin_a_fee, coin_b_fee) = swap_v2(coin_a_in, coin_b_out, coin_b_in, coin_a_out); 260 | let event_handle = borrow_global_mut(amm_config::admin_address()); 261 | event::emit_event( 262 | &mut event_handle.swap_events, 263 | SwapEvent { 264 | coin_a_info: type_info::type_of(), 265 | coin_b_info: type_info::type_of(), 266 | account: account, 267 | a_in: coin_a_in_value, 268 | a_out: (coin::value(&coin_a_out) as u128), 269 | b_in: coin_b_in_value, 270 | b_out: (coin::value(&coin_b_out) as u128) 271 | } 272 | ); 273 | (coin_a_out, coin_b_out, coin_a_fee, coin_b_fee) 274 | } 275 | 276 | public fun swap( 277 | _coin_a_in: Coin, 278 | _coin_b_out: u128, 279 | _coin_b_in: Coin, 280 | _coin_a_out: u128, 281 | ): (Coin, Coin, Coin, Coin) { 282 | abort EFUNCTION_DEPRECATED 283 | } 284 | 285 | fun swap_v2( 286 | coin_a_in: Coin, 287 | coin_b_out: u128, 288 | coin_b_in: Coin, 289 | coin_a_out: u128, 290 | ): (Coin, Coin, Coin, Coin) acquires Pool{ 291 | amm_config::assert_pause(); 292 | 293 | let a_in_value = coin::value(&coin_a_in); 294 | let b_in_value = coin::value(&coin_b_in); 295 | assert!( 296 | a_in_value > 0 || b_in_value > 0, 297 | error::internal(ECOIN_INSUFFICIENT)); 298 | 299 | let (a_reserve, b_reserve) = get_reserves(); 300 | let pool = borrow_global_mut>(amm_config::admin_address()); 301 | coin::merge(&mut pool.coin_a, coin_a_in); 302 | coin::merge(&mut pool.coin_b, coin_b_in); 303 | 304 | let coin_a_swapped = coin::extract(&mut pool.coin_a, (coin_a_out as u64)); 305 | let coin_b_swapped = coin::extract(&mut pool.coin_b, (coin_b_out as u64)); 306 | { 307 | let a_reserve_new = coin::value(&pool.coin_a); 308 | let b_reserve_new = coin::value(&pool.coin_b); 309 | let (fee_numerator, fee_denominator) = amm_config::get_trade_fee(); 310 | 311 | let (a_adjusted, b_adjusted) = new_reserves_adjusted( 312 | a_reserve_new, 313 | b_reserve_new, 314 | a_in_value, 315 | b_in_value, 316 | fee_numerator, 317 | fee_denominator); 318 | 319 | 320 | assert_lp_value_incr( 321 | a_reserve, 322 | b_reserve, 323 | a_adjusted, 324 | b_adjusted, 325 | (fee_denominator as u128) 326 | ); 327 | }; 328 | 329 | let (protocol_fee_numberator, protocol_fee_denominator) = calc_swap_protocol_fee_rate(); 330 | let a_swap_fee = coin::extract(&mut pool.coin_a, (amm_math::safe_mul_div_u128((a_in_value as u128), protocol_fee_numberator, protocol_fee_denominator) as u64)); 331 | let b_swap_fee = coin::extract(&mut pool.coin_b, (amm_math::safe_mul_div_u128((b_in_value as u128), protocol_fee_numberator, protocol_fee_denominator) as u64)); 332 | 333 | (coin_a_swapped, coin_b_swapped, a_swap_fee, b_swap_fee) 334 | } 335 | 336 | fun make_pool( 337 | protocol_fee_to: address, 338 | burn_capability: BurnCapability>, 339 | mint_capability: MintCapability>, 340 | ): Pool { 341 | Pool { 342 | coin_a: coin::zero(), 343 | coin_b: coin::zero(), 344 | mint_capability: mint_capability, 345 | burn_capability: burn_capability, 346 | locked_liquidity: coin::zero>(), 347 | protocol_fee_to: protocol_fee_to, 348 | } 349 | } 350 | 351 | fun register_liquidity_coin( 352 | account: &signer 353 | ) :(BurnCapability>, MintCapability>){ 354 | let name = string::utf8(b"LP-"); 355 | string::append(&mut name, coin::name()); 356 | string::append_utf8(&mut name, b"-"); 357 | string::append(&mut name, coin::name()); 358 | 359 | let (burn_capability, freeze_capability, mint_capability) = coin::initialize>( 360 | account, 361 | name, 362 | string::utf8(b"CALP"), 363 | 6, 364 | true, 365 | ); 366 | 367 | coin::destroy_freeze_cap(freeze_capability); 368 | (burn_capability, mint_capability) 369 | } 370 | 371 | fun init_event_handle(account: &signer) { 372 | if (!exists(signer::address_of(account))) { 373 | move_to(account, PoolSwapEventHandle { 374 | init_pool_events: new_event_handle(account), 375 | add_liquidity_events: new_event_handle(account), 376 | remove_liquidity_events: new_event_handle(account), 377 | swap_events: new_event_handle(account), 378 | swap_fee_events: new_event_handle(account), 379 | }); 380 | } 381 | } 382 | 383 | fun emit_init_pool_event( 384 | account: address, 385 | protocol_fee_to: address 386 | ) acquires PoolSwapEventHandle { 387 | let event_handle = borrow_global_mut(amm_config::admin_address()); 388 | event::emit_event( 389 | &mut event_handle.init_pool_events, 390 | InitPoolEvent { 391 | coin_a_info: type_info::type_of(), 392 | coin_b_info: type_info::type_of(), 393 | account: account, 394 | protocol_fee_to: protocol_fee_to, 395 | } 396 | ); 397 | } 398 | 399 | public fun get_reserves(): (u128, u128) acquires Pool { 400 | let pool = borrow_global>(amm_config::admin_address()); 401 | let a_reserve = (coin::value(&pool.coin_a) as u128); 402 | let b_reserve = (coin::value(&pool.coin_b) as u128); 403 | (a_reserve, b_reserve) 404 | } 405 | 406 | public fun calc_swap_protocol_fee_rate() : (u128, u128) { 407 | let (fee_numerator, fee_denominator) = amm_config::get_trade_fee(); 408 | let (protocol_fee_numberator, protocol_fee_denominator) = amm_config::get_protocol_fee(); 409 | ((fee_numerator * protocol_fee_numberator as u128), (fee_denominator * protocol_fee_denominator as u128)) 410 | } 411 | 412 | public fun handle_swap_protocol_fee( 413 | _signer_address: address, 414 | _token_a: Coin, 415 | _is_forward: bool) { 416 | abort EFUNCTION_DEPRECATED 417 | } 418 | 419 | public(friend) fun handle_swap_protocol_fee_v2(signer_address: address, token_a: Coin, is_forward: bool) acquires PoolSwapEventHandle, Pool { 420 | let protocol_fee_to: address; 421 | if(is_forward) { 422 | protocol_fee_to = borrow_global>(amm_config::admin_address()).protocol_fee_to; 423 | } else { 424 | protocol_fee_to = borrow_global>(amm_config::admin_address()).protocol_fee_to; 425 | }; 426 | 427 | handle_swap_protocol_fee_internal(signer_address, protocol_fee_to, token_a, is_forward); 428 | } 429 | 430 | fun handle_swap_protocol_fee_internal( 431 | signer_address: address, 432 | fee_address: address, 433 | coin_a: Coin, 434 | is_forward: bool 435 | ) acquires PoolSwapEventHandle, Pool { 436 | let (fee_handle, fee_out) = swap_fee_direct_deposit(fee_address, coin_a, is_forward); 437 | if (fee_handle) { 438 | if (is_forward) { 439 | emit_swap_fee_event(signer_address, fee_address, fee_out, 0); 440 | } else { 441 | emit_swap_fee_event(signer_address, fee_address, 0, fee_out); 442 | }; 443 | } 444 | } 445 | 446 | fun swap_fee_direct_deposit( 447 | fee_address: address, 448 | coin_a: Coin, 449 | is_forward: bool): (bool, u128) acquires Pool { 450 | if (coin::is_account_registered(fee_address)) { 451 | let a_value = coin::value(&coin_a); 452 | coin::deposit(fee_address, coin_a); 453 | (true, (a_value as u128)) 454 | } else { 455 | if (is_forward) { 456 | return_back_to_lp_pool(coin_a, coin::zero()); 457 | } else { 458 | return_back_to_lp_pool(coin::zero(), coin_a); 459 | }; 460 | (false, (0 as u128)) 461 | } 462 | } 463 | 464 | fun return_back_to_lp_pool( 465 | a_in: coin::Coin, 466 | b_in: coin::Coin, 467 | ) acquires Pool { 468 | let pool = borrow_global_mut>(amm_config::admin_address()); 469 | coin::merge(&mut pool.coin_a, a_in); 470 | coin::merge(&mut pool.coin_b, b_in); 471 | } 472 | 473 | fun emit_swap_fee_event ( 474 | signer_address: address, 475 | fee_address: address, 476 | fee_a_out: u128, 477 | fee_b_out: u128 478 | ) acquires PoolSwapEventHandle { 479 | let event_handle = borrow_global_mut(amm_config::admin_address()); 480 | event::emit_event( 481 | &mut event_handle.swap_fee_events, 482 | SwapFeeEvent { 483 | coin_a_info: type_info::type_of(), 484 | coin_b_info: type_info::type_of(), 485 | account: signer_address, 486 | fee_address: fee_address, 487 | fee_a_out: fee_a_out, 488 | fee_b_out: fee_b_out, 489 | } 490 | ); 491 | } 492 | 493 | public fun get_pool_direction(): bool { 494 | if(exists>(amm_config::admin_address())) { 495 | true 496 | } else { 497 | assert!(exists>(amm_config::admin_address()), EPOOL_DOSE_NOT_EXIST); 498 | false 499 | } 500 | } 501 | 502 | fun new_reserves_adjusted( 503 | a_reserve: u64, 504 | b_reserve: u64, 505 | a_in_val: u64, 506 | b_in_val: u64, 507 | fee_numerator: u64, 508 | fee_denominator: u64 509 | ) : (u128, u128) { 510 | let a_adjusted = (a_reserve as u128) * (fee_denominator as u128) - (a_in_val as u128) * (fee_numerator as u128); 511 | let b_adjusted = (b_reserve as u128) * (fee_denominator as u128) - (b_in_val as u128) * (fee_numerator as u128); 512 | (a_adjusted, b_adjusted) 513 | } 514 | 515 | fun assert_lp_value_incr( 516 | a_reserve: u128, 517 | b_reserve: u128, 518 | a_adjusted: u128, 519 | b_adjusted: u128, 520 | fee_denominator: u128 521 | ) { 522 | let cmp_order = amm_math::safe_compare_mul_u128(a_adjusted, b_adjusted, (a_reserve as u128) * (fee_denominator as u128), (b_reserve as u128) * (fee_denominator as u128)); 523 | assert!( 524 | (EQUAL == cmp_order || GREATER_THAN == cmp_order), 525 | error::internal(ESWAPOUT_CALC_INVALID)); 526 | } 527 | } -------------------------------------------------------------------------------- /aptos/sources/amm_utils.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_utils { 2 | use std::error; 3 | use aptos_framework::coin; 4 | use aptos_std::type_info; 5 | use aptos_std::comparator; 6 | use cetus_amm::amm_math; 7 | 8 | // 9 | // Errors 10 | // 11 | 12 | const ESWAP_COIN_NOT_EXISTS: u64 = 5001; 13 | const EPARAMETER_INVALID: u64 = 5002; 14 | 15 | public fun assert_is_coin() : bool { 16 | assert!(coin::is_coin_initialized(), error::internal(ESWAP_COIN_NOT_EXISTS)); 17 | true 18 | } 19 | 20 | public fun compare_coin(): comparator::Result { 21 | let type_info_a = type_info::type_of(); 22 | let type_info_b = type_info::type_of(); 23 | 24 | comparator::compare(&type_info_a, &type_info_b) 25 | } 26 | 27 | public fun get_amount_in( 28 | amount_out: u128, 29 | reserve_in: u128, 30 | reserve_out: u128, 31 | fee_numerator: u64, 32 | fee_denumerator: u64): u128 { 33 | assert!(amount_out > 0, error::internal(EPARAMETER_INVALID)); 34 | assert!(reserve_in > 0 && reserve_out > 0, error::internal(EPARAMETER_INVALID)); 35 | assert!(fee_denumerator > 0 && fee_numerator > 0, error::internal(EPARAMETER_INVALID)); 36 | assert!(fee_denumerator > fee_numerator, error::internal(EPARAMETER_INVALID)); 37 | assert!(reserve_out > amount_out, error::internal(EPARAMETER_INVALID)); 38 | 39 | let denominator = (reserve_out - amount_out) * ((fee_denumerator - fee_numerator) as u128); 40 | amm_math::safe_mul_div_u128(amount_out * (fee_denumerator as u128), reserve_in, denominator) + 1 41 | } 42 | 43 | public fun get_amount_out( 44 | amount_in: u128, 45 | reserve_in: u128, 46 | reserve_out: u128, 47 | fee_numerator: u64, 48 | fee_denumerator: u64 49 | ): u128 { 50 | assert!(amount_in > 0, error::internal(EPARAMETER_INVALID)); 51 | assert!(reserve_in > 0 && reserve_out > 0, error::internal(EPARAMETER_INVALID)); 52 | 53 | assert!(fee_denumerator > 0 && fee_numerator > 0, error::internal(EPARAMETER_INVALID)); 54 | assert!(fee_denumerator > fee_numerator, error::internal(EPARAMETER_INVALID)); 55 | 56 | let amount_in_with_fee = amount_in * ((fee_denumerator - fee_numerator) as u128); 57 | let denominator = reserve_in * (fee_denumerator as u128) + amount_in_with_fee; 58 | amm_math::safe_mul_div_u128(amount_in_with_fee, reserve_out, denominator) 59 | } 60 | 61 | #[test] 62 | public entry fun test_get_amount_out() { 63 | let amount_in:u128 = 999999; 64 | let reserve_in: u128 = 10000010001; 65 | let reserve_out: u128 = 1971498032450; 66 | let out = get_amount_out(amount_in, reserve_in, reserve_out, 2, 1000); 67 | debug::print(&out); 68 | assert!(out == 196735475, 3004); 69 | } 70 | } -------------------------------------------------------------------------------- /aptos/sources/u256.move: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: Apache-2.0 3 | // Copied from: https://github.com/pontem-network/U256/blob/main/sources/u256.move 4 | 5 | /// @title u256 6 | /// @dev The implementation of large numbers written in Move language. 7 | /// Code derived from original work by Andrew Poelstra 8 | /// 9 | /// Rust Bitcoin Library 10 | /// Written in 2014 by 11 | /// Andrew Poelstra 12 | /// 13 | /// To the extent possible under law, the author(s) have dedicated all 14 | /// copyright and related and neighboring rights to this software to 15 | /// the public domain worldwide. This software is distributed without 16 | /// any warranty. 17 | /// 18 | /// Simplified impl by Parity Team - https://github.com/paritytech/parity-common/blob/master/uint/src/uint.rs 19 | /// 20 | /// Features: 21 | /// * mul 22 | /// * div 23 | /// * add 24 | /// * sub 25 | /// * shift left 26 | /// * shift right 27 | /// * compare 28 | /// * if math overflows the contract crashes. 29 | /// 30 | /// Would be nice to help with the following TODO list: 31 | /// * pow() , sqrt(). 32 | /// * math funcs that don't abort on overflows, but just returns reminders. 33 | /// * Export of low_u128 (see original implementation). 34 | /// * Export of low_u64 (see original implementation). 35 | /// * Gas Optimisation: 36 | /// * We can optimize by replacing bytecode, as far as we know Move VM itself support slices, so probably 37 | /// we can try to replace parts works with (`v0`,`v1`,`v2`,`v3` etc) works. 38 | /// * More? 39 | /// * More tests (see current tests and TODOs i left): 40 | /// * u256_arithmetic_test - https://github.com/paritytech/bigint/blob/master/src/uint.rs#L1338 41 | /// * More from - https://github.com/paritytech/bigint/blob/master/src/uint.rs 42 | /// * Division: 43 | /// * Could be improved with div_mod_small (current version probably would took a lot of resources for small numbers). 44 | /// * Also could be improved with Knuth, TAOCP, Volume 2, section 4.3.1, Algorithm D (see link to Parity above). 45 | module cetus_amm::u256 { 46 | use std::bcs; 47 | use std::vector; 48 | 49 | // Errors. 50 | /// When can't cast `U256` to `u128` (e.g. number too large). 51 | const ECAST_OVERFLOW: u64 = 0; 52 | 53 | /// When trying to get or put word into U256 but it's out of index. 54 | const EWORDS_OVERFLOW: u64 = 1; 55 | 56 | /// When math overflows. 57 | const EOVERFLOW: u64 = 2; 58 | 59 | /// When attempted to divide by zero. 60 | const EDIV_BY_ZERO: u64 = 3; 61 | 62 | /// When trying to call `from_bytes` on a vector of length != 32. 63 | const EVECTOR_LENGTH_NOT_32_BYTES: u64 = 4; 64 | 65 | // Constants. 66 | 67 | /// Max `u64` value. 68 | const U64_MAX: u128 = 18446744073709551615; 69 | 70 | /// Max `u128` value. 71 | const U128_MAX: u128 = 340282366920938463463374607431768211455; 72 | 73 | /// Total words in `U256` (64 * 4 = 256). 74 | const WORDS: u64 = 4; 75 | 76 | /// When both `U256` equal. 77 | const EQUAL: u8 = 0; 78 | 79 | /// When `a` is less than `b`. 80 | const LESS_THAN: u8 = 1; 81 | 82 | /// When `b` is greater than `b`. 83 | const GREATER_THAN: u8 = 2; 84 | 85 | // Data structs. 86 | 87 | /// The `U256` resource. 88 | /// Contains 4 u64 numbers. 89 | struct U256 has copy, drop, store { 90 | v0: u64, 91 | v1: u64, 92 | v2: u64, 93 | v3: u64, 94 | } 95 | 96 | /// Double `U256` used for multiple (to store overflow). 97 | struct DU256 has copy, drop, store { 98 | v0: u64, 99 | v1: u64, 100 | v2: u64, 101 | v3: u64, 102 | v4: u64, 103 | v5: u64, 104 | v6: u64, 105 | v7: u64, 106 | } 107 | 108 | // Public functions. 109 | /// Adds two `U256` and returns sum. 110 | public fun add(a: U256, b: U256): U256 { 111 | let ret = zero(); 112 | let carry = 0u64; 113 | 114 | let i = 0; 115 | while (i < WORDS) { 116 | let a1 = get(&a, i); 117 | let b1 = get(&b, i); 118 | 119 | if (carry != 0) { 120 | let (res1, is_overflow1) = overflowing_add(a1, b1); 121 | let (res2, is_overflow2) = overflowing_add(res1, carry); 122 | put(&mut ret, i, res2); 123 | 124 | carry = 0; 125 | if (is_overflow1) { 126 | carry = carry + 1; 127 | }; 128 | 129 | if (is_overflow2) { 130 | carry = carry + 1; 131 | } 132 | } else { 133 | let (res, is_overflow) = overflowing_add(a1, b1); 134 | put(&mut ret, i, res); 135 | 136 | carry = 0; 137 | if (is_overflow) { 138 | carry = 1; 139 | }; 140 | }; 141 | 142 | i = i + 1; 143 | }; 144 | 145 | assert!(carry == 0, EOVERFLOW); 146 | 147 | ret 148 | } 149 | 150 | /// Convert `U256` to `u128` value if possible (otherwise it aborts). 151 | public fun as_u128(a: U256): u128 { 152 | assert!(a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW); 153 | ((a.v1 as u128) << 64) + (a.v0 as u128) 154 | } 155 | 156 | /// Convert `U256` to `u64` value if possible (otherwise it aborts). 157 | public fun as_u64(a: U256): u64 { 158 | assert!(a.v1 == 0 && a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW); 159 | a.v0 160 | } 161 | 162 | /// Compares two `U256` numbers. 163 | public fun compare(a: &U256, b: &U256): u8 { 164 | let i = WORDS; 165 | while (i > 0) { 166 | i = i - 1; 167 | let a1 = get(a, i); 168 | let b1 = get(b, i); 169 | 170 | if (a1 != b1) { 171 | if (a1 < b1) { 172 | return LESS_THAN 173 | } else { 174 | return GREATER_THAN 175 | } 176 | } 177 | }; 178 | 179 | EQUAL 180 | } 181 | 182 | /// Returns a `U256` from `u64` value. 183 | public fun from_u64(val: u64): U256 { 184 | from_u128((val as u128)) 185 | } 186 | 187 | /// Returns a `U256` from `u128` value. 188 | public fun from_u128(val: u128): U256 { 189 | let (a2, a1) = split_u128(val); 190 | 191 | U256 { 192 | v0: a1, 193 | v1: a2, 194 | v2: 0, 195 | v3: 0, 196 | } 197 | } 198 | 199 | /// Multiples two `U256`. 200 | public fun mul(a: U256, b: U256): U256 { 201 | let ret = DU256 { 202 | v0: 0, 203 | v1: 0, 204 | v2: 0, 205 | v3: 0, 206 | v4: 0, 207 | v5: 0, 208 | v6: 0, 209 | v7: 0, 210 | }; 211 | 212 | let i = 0; 213 | while (i < WORDS) { 214 | let carry = 0u64; 215 | let b1 = get(&b, i); 216 | 217 | let j = 0; 218 | while (j < WORDS) { 219 | let a1 = get(&a, j); 220 | 221 | if (a1 != 0 || carry != 0) { 222 | let (hi, low) = split_u128((a1 as u128) * (b1 as u128)); 223 | 224 | let overflow = { 225 | let existing_low = get_d(&ret, i + j); 226 | let (low, o) = overflowing_add(low, existing_low); 227 | put_d(&mut ret, i + j, low); 228 | if (o) { 229 | 1 230 | } else { 231 | 0 232 | } 233 | }; 234 | 235 | carry = { 236 | let existing_hi = get_d(&ret, i + j + 1); 237 | let hi = hi + overflow; 238 | let (hi, o0) = overflowing_add(hi, carry); 239 | let (hi, o1) = overflowing_add(hi, existing_hi); 240 | put_d(&mut ret, i + j + 1, hi); 241 | 242 | if (o0 || o1) { 243 | 1 244 | } else { 245 | 0 246 | } 247 | }; 248 | }; 249 | 250 | j = j + 1; 251 | }; 252 | 253 | i = i + 1; 254 | }; 255 | 256 | let (r, overflow) = du256_to_u256(ret); 257 | assert!(!overflow, EOVERFLOW); 258 | r 259 | } 260 | 261 | /// Subtracts two `U256`, returns result. 262 | public fun sub(a: U256, b: U256): U256 { 263 | let ret = zero(); 264 | 265 | let carry = 0u64; 266 | 267 | let i = 0; 268 | while (i < WORDS) { 269 | let a1 = get(&a, i); 270 | let b1 = get(&b, i); 271 | 272 | if (carry != 0) { 273 | let (res1, is_overflow1) = overflowing_sub(a1, b1); 274 | let (res2, is_overflow2) = overflowing_sub(res1, carry); 275 | put(&mut ret, i, res2); 276 | 277 | carry = 0; 278 | if (is_overflow1) { 279 | carry = carry + 1; 280 | }; 281 | 282 | if (is_overflow2) { 283 | carry = carry + 1; 284 | } 285 | } else { 286 | let (res, is_overflow) = overflowing_sub(a1, b1); 287 | put(&mut ret, i, res); 288 | 289 | carry = 0; 290 | if (is_overflow) { 291 | carry = 1; 292 | }; 293 | }; 294 | 295 | i = i + 1; 296 | }; 297 | 298 | assert!(carry == 0, EOVERFLOW); 299 | ret 300 | } 301 | 302 | /// Divide `a` by `b`. 303 | public fun div(a: U256, b: U256): U256 { 304 | let ret = zero(); 305 | 306 | let a_bits = bits(&a); 307 | let b_bits = bits(&b); 308 | 309 | assert!(b_bits != 0, EDIV_BY_ZERO); // DIVIDE BY ZERO. 310 | if (a_bits < b_bits) { 311 | // Immidiatelly return. 312 | return ret 313 | }; 314 | 315 | let shift = a_bits - b_bits; 316 | b = shl(b, (shift as u8)); 317 | 318 | loop { 319 | let cmp = compare(&a, &b); 320 | if (cmp == GREATER_THAN || cmp == EQUAL) { 321 | let index = shift / 64; 322 | let m = get(&ret, index); 323 | let c = m | 1 << ((shift % 64) as u8); 324 | put(&mut ret, index, c); 325 | 326 | a = sub(a, b); 327 | }; 328 | 329 | b = shr(b, 1); 330 | if (shift == 0) { 331 | break 332 | }; 333 | 334 | shift = shift - 1; 335 | }; 336 | 337 | ret 338 | } 339 | 340 | /// Shift right `a` by `shift`. 341 | public fun shr(a: U256, shift: u8): U256 { 342 | let ret = zero(); 343 | 344 | let word_shift = (shift as u64) / 64; 345 | let bit_shift = (shift as u64) % 64; 346 | 347 | let i = word_shift; 348 | while (i < WORDS) { 349 | let m = get(&a, i) >> (bit_shift as u8); 350 | put(&mut ret, i - word_shift, m); 351 | i = i + 1; 352 | }; 353 | 354 | if (bit_shift > 0) { 355 | let j = word_shift + 1; 356 | while (j < WORDS) { 357 | let m = get(&ret, j - word_shift - 1) + (get(&a, j) << (64 - (bit_shift as u8))); 358 | put(&mut ret, j - word_shift - 1, m); 359 | j = j + 1; 360 | }; 361 | }; 362 | 363 | ret 364 | } 365 | 366 | /// Shift left `a` by `shift`. 367 | public fun shl(a: U256, shift: u8): U256 { 368 | let ret = zero(); 369 | 370 | let word_shift = (shift as u64) / 64; 371 | let bit_shift = (shift as u64) % 64; 372 | 373 | let i = word_shift; 374 | while (i < WORDS) { 375 | let m = get(&a, i - word_shift) << (bit_shift as u8); 376 | put(&mut ret, i, m); 377 | i = i + 1; 378 | }; 379 | 380 | if (bit_shift > 0) { 381 | let j = word_shift + 1; 382 | 383 | while (j < WORDS) { 384 | let m = get(&ret, j) + (get(&a, j - 1 - word_shift) >> (64 - (bit_shift as u8))); 385 | put(&mut ret, j, m); 386 | j = j + 1; 387 | }; 388 | }; 389 | 390 | ret 391 | } 392 | 393 | /// Returns `a` AND `b`. 394 | public fun and(a: &U256, b: &U256): U256 { 395 | let ret = zero(); 396 | 397 | let i = 0; 398 | while (i < WORDS) { 399 | let m = get(a, i) & get(b, i); 400 | put(&mut ret, i, m); 401 | i = i + 1; 402 | }; 403 | 404 | ret 405 | } 406 | 407 | /// Returns `a` OR `b`. 408 | public fun or(a: &U256, b: &U256): U256 { 409 | let ret = zero(); 410 | 411 | let i = 0; 412 | while (i < WORDS) { 413 | let m = get(a, i) | get(b, i); 414 | put(&mut ret, i, m); 415 | i = i + 1; 416 | }; 417 | 418 | ret 419 | } 420 | 421 | /// Returns `a` XOR `b`. 422 | public fun xor(a: &U256, b: &U256): U256 { 423 | let ret = zero(); 424 | 425 | let i = 0; 426 | while (i < WORDS) { 427 | let m = get(a, i) ^ get(b, i); 428 | put(&mut ret, i, m); 429 | i = i + 1; 430 | }; 431 | 432 | ret 433 | } 434 | 435 | /// Returns `U256` equals to zero. 436 | public fun zero(): U256 { 437 | U256 { 438 | v0: 0, 439 | v1: 0, 440 | v2: 0, 441 | v3: 0, 442 | } 443 | } 444 | 445 | // Private functions. 446 | /// Get bits used to store `a`. 447 | fun bits(a: &U256): u64 { 448 | let i = 1; 449 | while (i < WORDS) { 450 | let a1 = get(a, WORDS - i); 451 | if (a1 > 0) { 452 | return ((0x40 * (WORDS - i + 1)) - (leading_zeros_u64(a1) as u64)) 453 | }; 454 | 455 | i = i + 1; 456 | }; 457 | 458 | let a1 = get(a, 0); 459 | 0x40 - (leading_zeros_u64(a1) as u64) 460 | } 461 | 462 | /// Get leading zeros of a binary representation of `a`. 463 | fun leading_zeros_u64(a: u64): u8 { 464 | if (a == 0) { 465 | return 64 466 | }; 467 | 468 | let a1 = a & 0xFFFFFFFF; 469 | let a2 = a >> 32; 470 | 471 | if (a2 == 0) { 472 | let bit = 32; 473 | 474 | while (bit >= 1) { 475 | let b = (a1 >> (bit-1)) & 1; 476 | if (b != 0) { 477 | break 478 | }; 479 | 480 | bit = bit - 1; 481 | }; 482 | 483 | (32 - bit) + 32 484 | } else { 485 | let bit = 64; 486 | while (bit >= 1) { 487 | let b = (a >> (bit-1)) & 1; 488 | if (b != 0) { 489 | break 490 | }; 491 | bit = bit - 1; 492 | }; 493 | 494 | 64 - bit 495 | } 496 | } 497 | 498 | /// Similar to Rust `overflowing_add`. 499 | /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur. 500 | /// If an overflow would have occurred then the wrapped value is returned. 501 | fun overflowing_add(a: u64, b: u64): (u64, bool) { 502 | let a128 = (a as u128); 503 | let b128 = (b as u128); 504 | 505 | let r = a128 + b128; 506 | if (r > U64_MAX) { 507 | // overflow 508 | let overflow = r - U64_MAX - 1; 509 | ((overflow as u64), true) 510 | } else { 511 | (((a128 + b128) as u64), false) 512 | } 513 | } 514 | 515 | /// Similar to Rust `overflowing_sub`. 516 | /// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur. 517 | /// If an overflow would have occurred then the wrapped value is returned. 518 | fun overflowing_sub(a: u64, b: u64): (u64, bool) { 519 | if (a < b) { 520 | let r = b - a; 521 | ((U64_MAX as u64) - r + 1, true) 522 | } else { 523 | (a - b, false) 524 | } 525 | } 526 | 527 | /// Extracts two `u64` from `a` `u128`. 528 | fun split_u128(a: u128): (u64, u64) { 529 | let a1 = ((a >> 64) as u64); 530 | let a2 = ((a & 0xFFFFFFFFFFFFFFFF) as u64); 531 | 532 | (a1, a2) 533 | } 534 | 535 | /// Get word from `a` by index `i`. 536 | public fun get(a: &U256, i: u64): u64 { 537 | if (i == 0) { 538 | a.v0 539 | } else if (i == 1) { 540 | a.v1 541 | } else if (i == 2) { 542 | a.v2 543 | } else if (i == 3) { 544 | a.v3 545 | } else { 546 | abort EWORDS_OVERFLOW 547 | } 548 | } 549 | 550 | /// Get word from `DU256` by index. 551 | fun get_d(a: &DU256, i: u64): u64 { 552 | if (i == 0) { 553 | a.v0 554 | } else if (i == 1) { 555 | a.v1 556 | } else if (i == 2) { 557 | a.v2 558 | } else if (i == 3) { 559 | a.v3 560 | } else if (i == 4) { 561 | a.v4 562 | } else if (i == 5) { 563 | a.v5 564 | } else if (i == 6) { 565 | a.v6 566 | } else if (i == 7) { 567 | a.v7 568 | } else { 569 | abort EWORDS_OVERFLOW 570 | } 571 | } 572 | 573 | /// Put new word `val` into `U256` by index `i`. 574 | fun put(a: &mut U256, i: u64, val: u64) { 575 | if (i == 0) { 576 | a.v0 = val; 577 | } else if (i == 1) { 578 | a.v1 = val; 579 | } else if (i == 2) { 580 | a.v2 = val; 581 | } else if (i == 3) { 582 | a.v3 = val; 583 | } else { 584 | abort EWORDS_OVERFLOW 585 | } 586 | } 587 | 588 | /// Put new word into `DU256` by index `i`. 589 | fun put_d(a: &mut DU256, i: u64, val: u64) { 590 | if (i == 0) { 591 | a.v0 = val; 592 | } else if (i == 1) { 593 | a.v1 = val; 594 | } else if (i == 2) { 595 | a.v2 = val; 596 | } else if (i == 3) { 597 | a.v3 = val; 598 | } else if (i == 4) { 599 | a.v4 = val; 600 | } else if (i == 5) { 601 | a.v5 = val; 602 | } else if (i == 6) { 603 | a.v6 = val; 604 | } else if (i == 7) { 605 | a.v7 = val; 606 | } else { 607 | abort EWORDS_OVERFLOW 608 | } 609 | } 610 | 611 | /// Convert `DU256` to `U256`. 612 | fun du256_to_u256(a: DU256): (U256, bool) { 613 | let b = U256 { 614 | v0: a.v0, 615 | v1: a.v1, 616 | v2: a.v2, 617 | v3: a.v3, 618 | }; 619 | 620 | let overflow = false; 621 | if (a.v4 != 0 || a.v5 != 0 || a.v6 != 0 || a.v7 != 0) { 622 | overflow = true; 623 | }; 624 | 625 | (b, overflow) 626 | } 627 | 628 | /// Converts `vector` `a` to a `U256`. 629 | public fun from_bytes(a: &vector): U256 { 630 | assert!(vector::length(a) == 32, EVECTOR_LENGTH_NOT_32_BYTES); 631 | let ret = zero(); 632 | put(&mut ret, 0, ((*vector::borrow(a, 0) as u64) << 7) + ((*vector::borrow(a, 1) as u64) << 6) 633 | + ((*vector::borrow(a, 2) as u64) << 5) + ((*vector::borrow(a, 3) as u64) << 4) 634 | + ((*vector::borrow(a, 4) as u64) << 3) + ((*vector::borrow(a, 5) as u64) << 2) 635 | + ((*vector::borrow(a, 6) as u64) << 1) + (*vector::borrow(a, 7) as u64)); 636 | put(&mut ret, 1, ((*vector::borrow(a, 8) as u64) << 7) + ((*vector::borrow(a, 9) as u64) << 6) 637 | + ((*vector::borrow(a, 10) as u64) << 5) + ((*vector::borrow(a, 11) as u64) << 4) 638 | + ((*vector::borrow(a, 12) as u64) << 3) + ((*vector::borrow(a, 13) as u64) << 2) 639 | + ((*vector::borrow(a, 14) as u64) << 1) + (*vector::borrow(a, 15) as u64)); 640 | put(&mut ret, 2, ((*vector::borrow(a, 16) as u64) << 7) + ((*vector::borrow(a, 17) as u64) << 6) 641 | + ((*vector::borrow(a, 18) as u64) << 5) + ((*vector::borrow(a, 19) as u64) << 4) 642 | + ((*vector::borrow(a, 20) as u64) << 3) + ((*vector::borrow(a, 21) as u64) << 2) 643 | + ((*vector::borrow(a, 22) as u64) << 1) + (*vector::borrow(a, 23) as u64)); 644 | put(&mut ret, 3, ((*vector::borrow(a, 24) as u64) << 7) + ((*vector::borrow(a, 25) as u64) << 6) 645 | + ((*vector::borrow(a, 26) as u64) << 5) + ((*vector::borrow(a, 27) as u64) << 4) 646 | + ((*vector::borrow(a, 28) as u64) << 3) + ((*vector::borrow(a, 29) as u64) << 2) 647 | + ((*vector::borrow(a, 30) as u64) << 1) + (*vector::borrow(a, 31) as u64)); 648 | ret 649 | } 650 | 651 | /// Converts `U256` `a` to a `vector`. 652 | public fun to_bytes(a: &U256): vector { 653 | let ret = vector::empty(); 654 | vector::append(&mut ret, bcs::to_bytes(&get(a, 0))); 655 | vector::append(&mut ret, bcs::to_bytes(&get(a, 1))); 656 | vector::append(&mut ret, bcs::to_bytes(&get(a, 2))); 657 | vector::append(&mut ret, bcs::to_bytes(&get(a, 3))); 658 | ret 659 | } 660 | 661 | // Tests. 662 | #[test] 663 | fun test_get_d() { 664 | let a = DU256 { 665 | v0: 1, 666 | v1: 2, 667 | v2: 3, 668 | v3: 4, 669 | v4: 5, 670 | v5: 6, 671 | v6: 7, 672 | v7: 8, 673 | }; 674 | 675 | assert!(get_d(&a, 0) == 1, 0); 676 | assert!(get_d(&a, 1) == 2, 1); 677 | assert!(get_d(&a, 2) == 3, 2); 678 | assert!(get_d(&a, 3) == 4, 3); 679 | assert!(get_d(&a, 4) == 5, 4); 680 | assert!(get_d(&a, 5) == 6, 5); 681 | assert!(get_d(&a, 6) == 7, 6); 682 | assert!(get_d(&a, 7) == 8, 7); 683 | } 684 | 685 | #[test] 686 | #[expected_failure(abort_code = 1)] 687 | fun test_get_d_overflow() { 688 | let a = DU256 { 689 | v0: 1, 690 | v1: 2, 691 | v2: 3, 692 | v3: 4, 693 | v4: 5, 694 | v5: 6, 695 | v6: 7, 696 | v7: 8, 697 | }; 698 | 699 | get_d(&a, 8); 700 | } 701 | 702 | #[test] 703 | fun test_put_d() { 704 | let a = DU256 { 705 | v0: 1, 706 | v1: 2, 707 | v2: 3, 708 | v3: 4, 709 | v4: 5, 710 | v5: 6, 711 | v6: 7, 712 | v7: 8, 713 | }; 714 | 715 | put_d(&mut a, 0, 10); 716 | put_d(&mut a, 1, 20); 717 | put_d(&mut a, 2, 30); 718 | put_d(&mut a, 3, 40); 719 | put_d(&mut a, 4, 50); 720 | put_d(&mut a, 5, 60); 721 | put_d(&mut a, 6, 70); 722 | put_d(&mut a, 7, 80); 723 | 724 | assert!(get_d(&a, 0) == 10, 0); 725 | assert!(get_d(&a, 1) == 20, 1); 726 | assert!(get_d(&a, 2) == 30, 2); 727 | assert!(get_d(&a, 3) == 40, 3); 728 | assert!(get_d(&a, 4) == 50, 4); 729 | assert!(get_d(&a, 5) == 60, 5); 730 | assert!(get_d(&a, 6) == 70, 6); 731 | assert!(get_d(&a, 7) == 80, 7); 732 | } 733 | 734 | #[test] 735 | #[expected_failure(abort_code = 1)] 736 | fun test_put_d_overflow() { 737 | let a = DU256 { 738 | v0: 1, 739 | v1: 2, 740 | v2: 3, 741 | v3: 4, 742 | v4: 5, 743 | v5: 6, 744 | v6: 7, 745 | v7: 8, 746 | }; 747 | 748 | put_d(&mut a, 8, 0); 749 | } 750 | 751 | #[test] 752 | fun test_du256_to_u256() { 753 | let a = DU256 { 754 | v0: 255, 755 | v1: 100, 756 | v2: 50, 757 | v3: 300, 758 | v4: 0, 759 | v5: 0, 760 | v6: 0, 761 | v7: 0, 762 | }; 763 | 764 | let (m, overflow) = du256_to_u256(a); 765 | assert!(!overflow, 0); 766 | assert!(m.v0 == a.v0, 1); 767 | assert!(m.v1 == a.v1, 2); 768 | assert!(m.v2 == a.v2, 3); 769 | assert!(m.v3 == a.v3, 4); 770 | 771 | a.v4 = 100; 772 | a.v5 = 5; 773 | 774 | let (m, overflow) = du256_to_u256(a); 775 | assert!(overflow, 5); 776 | assert!(m.v0 == a.v0, 6); 777 | assert!(m.v1 == a.v1, 7); 778 | assert!(m.v2 == a.v2, 8); 779 | assert!(m.v3 == a.v3, 9); 780 | } 781 | 782 | #[test] 783 | fun test_get() { 784 | let a = U256 { 785 | v0: 1, 786 | v1: 2, 787 | v2: 3, 788 | v3: 4, 789 | }; 790 | 791 | assert!(get(&a, 0) == 1, 0); 792 | assert!(get(&a, 1) == 2, 1); 793 | assert!(get(&a, 2) == 3, 2); 794 | assert!(get(&a, 3) == 4, 3); 795 | } 796 | 797 | #[test] 798 | #[expected_failure(abort_code = 1)] 799 | fun test_get_aborts() { 800 | let _ = get(&zero(), 4); 801 | } 802 | 803 | #[test] 804 | fun test_put() { 805 | let a = zero(); 806 | put(&mut a, 0, 255); 807 | assert!(get(&a, 0) == 255, 0); 808 | 809 | put(&mut a, 1, (U64_MAX as u64)); 810 | assert!(get(&a, 1) == (U64_MAX as u64), 1); 811 | 812 | put(&mut a, 2, 100); 813 | assert!(get(&a, 2) == 100, 2); 814 | 815 | put(&mut a, 3, 3); 816 | assert!(get(&a, 3) == 3, 3); 817 | 818 | put(&mut a, 2, 0); 819 | assert!(get(&a, 2) == 0, 4); 820 | } 821 | 822 | #[test] 823 | #[expected_failure(abort_code = 1)] 824 | fun test_put_overflow() { 825 | let a = zero(); 826 | put(&mut a, 6, 255); 827 | } 828 | 829 | #[test] 830 | fun test_from_u128() { 831 | let i = 0; 832 | while (i < 1024) { 833 | let big = from_u128(i); 834 | assert!(as_u128(big) == i, 0); 835 | i = i + 1; 836 | }; 837 | } 838 | 839 | #[test] 840 | fun test_add() { 841 | let a = from_u128(1000); 842 | let b = from_u128(500); 843 | 844 | let s = as_u128(add(a, b)); 845 | assert!(s == 1500, 0); 846 | 847 | a = from_u128(U64_MAX); 848 | b = from_u128(U64_MAX); 849 | 850 | s = as_u128(add(a, b)); 851 | assert!(s == (U64_MAX + U64_MAX), 1); 852 | } 853 | 854 | #[test] 855 | #[expected_failure(abort_code = 2)] 856 | fun test_add_overflow() { 857 | let max = (U64_MAX as u64); 858 | 859 | let a = U256 { 860 | v0: max, 861 | v1: max, 862 | v2: max, 863 | v3: max 864 | }; 865 | 866 | let _ = add(a, from_u128(1)); 867 | } 868 | 869 | #[test] 870 | fun test_sub() { 871 | let a = from_u128(1000); 872 | let b = from_u128(500); 873 | 874 | let s = as_u128(sub(a, b)); 875 | assert!(s == 500, 0); 876 | } 877 | 878 | #[test] 879 | #[expected_failure(abort_code = 2)] 880 | fun test_sub_overflow() { 881 | let a = from_u128(0); 882 | let b = from_u128(1); 883 | 884 | let _ = sub(a, b); 885 | } 886 | 887 | #[test] 888 | #[expected_failure(abort_code = 0)] 889 | fun test_too_big_to_cast_to_u128() { 890 | let a = from_u128(U128_MAX); 891 | let b = from_u128(U128_MAX); 892 | 893 | let _ = as_u128(add(a, b)); 894 | } 895 | 896 | #[test] 897 | fun test_overflowing_add() { 898 | let (n, z) = overflowing_add(10, 10); 899 | assert!(n == 20, 0); 900 | assert!(!z, 1); 901 | 902 | (n, z) = overflowing_add((U64_MAX as u64), 1); 903 | assert!(n == 0, 2); 904 | assert!(z, 3); 905 | 906 | (n, z) = overflowing_add((U64_MAX as u64), 10); 907 | assert!(n == 9, 4); 908 | assert!(z, 5); 909 | 910 | (n, z) = overflowing_add(5, 8); 911 | assert!(n == 13, 6); 912 | assert!(!z, 7); 913 | } 914 | 915 | #[test] 916 | fun test_overflowing_sub() { 917 | let (n, z) = overflowing_sub(10, 5); 918 | assert!(n == 5, 0); 919 | assert!(!z, 1); 920 | 921 | (n, z) = overflowing_sub(0, 1); 922 | assert!(n == (U64_MAX as u64), 2); 923 | assert!(z, 3); 924 | 925 | (n, z) = overflowing_sub(10, 10); 926 | assert!(n == 0, 4); 927 | assert!(!z, 5); 928 | } 929 | 930 | #[test] 931 | fun test_split_u128() { 932 | let (a1, a2) = split_u128(100); 933 | assert!(a1 == 0, 0); 934 | assert!(a2 == 100, 1); 935 | 936 | (a1, a2) = split_u128(U64_MAX + 1); 937 | assert!(a1 == 1, 2); 938 | assert!(a2 == 0, 3); 939 | } 940 | 941 | #[test] 942 | fun test_mul() { 943 | let a = from_u128(285); 944 | let b = from_u128(375); 945 | 946 | let c = as_u128(mul(a, b)); 947 | assert!(c == 106875, 0); 948 | 949 | a = from_u128(0); 950 | b = from_u128(1); 951 | 952 | c = as_u128(mul(a, b)); 953 | 954 | assert!(c == 0, 1); 955 | 956 | a = from_u128(U64_MAX); 957 | b = from_u128(2); 958 | 959 | c = as_u128(mul(a, b)); 960 | 961 | assert!(c == 36893488147419103230, 2); 962 | 963 | a = from_u128(U128_MAX); 964 | b = from_u128(U128_MAX); 965 | 966 | let z = mul(a, b); 967 | assert!(bits(&z) == 256, 3); 968 | } 969 | 970 | #[test] 971 | #[expected_failure(abort_code = 2)] 972 | fun test_mul_overflow() { 973 | let max = (U64_MAX as u64); 974 | 975 | let a = U256 { 976 | v0: max, 977 | v1: max, 978 | v2: max, 979 | v3: max, 980 | }; 981 | 982 | let _ = mul(a, from_u128(2)); 983 | } 984 | 985 | #[test] 986 | fun test_zero() { 987 | let a = as_u128(zero()); 988 | assert!(a == 0, 0); 989 | 990 | let a = zero(); 991 | assert!(a.v0 == 0, 1); 992 | assert!(a.v1 == 0, 2); 993 | assert!(a.v2 == 0, 3); 994 | assert!(a.v3 == 0, 4); 995 | } 996 | 997 | #[test] 998 | fun test_from_u64() { 999 | let a = as_u128(from_u64(100)); 1000 | assert!(a == 100, 0); 1001 | 1002 | // TODO: more tests. 1003 | } 1004 | 1005 | #[test] 1006 | fun test_compare() { 1007 | let a = from_u128(1000); 1008 | let b = from_u128(50); 1009 | 1010 | let cmp = compare(&a, &b); 1011 | assert!(cmp == 2, 0); 1012 | 1013 | a = from_u128(100); 1014 | b = from_u128(100); 1015 | cmp = compare(&a, &b); 1016 | 1017 | assert!(cmp == 0, 1); 1018 | 1019 | a = from_u128(50); 1020 | b = from_u128(75); 1021 | 1022 | cmp = compare(&a, &b); 1023 | assert!(cmp == 1, 2); 1024 | } 1025 | 1026 | #[test] 1027 | fun test_leading_zeros_u64() { 1028 | let a = leading_zeros_u64(0); 1029 | assert!(a == 64, 0); 1030 | 1031 | let a = leading_zeros_u64(1); 1032 | assert!(a == 63, 1); 1033 | 1034 | // TODO: more tests. 1035 | } 1036 | 1037 | #[test] 1038 | fun test_bits() { 1039 | let a = bits(&from_u128(0)); 1040 | assert!(a == 0, 0); 1041 | 1042 | a = bits(&from_u128(255)); 1043 | assert!(a == 8, 1); 1044 | 1045 | a = bits(&from_u128(256)); 1046 | assert!(a == 9, 2); 1047 | 1048 | a = bits(&from_u128(300)); 1049 | assert!(a == 9, 3); 1050 | 1051 | a = bits(&from_u128(60000)); 1052 | assert!(a == 16, 4); 1053 | 1054 | a = bits(&from_u128(70000)); 1055 | assert!(a == 17, 5); 1056 | 1057 | let b = from_u64(70000); 1058 | let sh = shl(b, 100); 1059 | assert!(bits(&sh) == 117, 6); 1060 | 1061 | let sh = shl(sh, 100); 1062 | assert!(bits(&sh) == 217, 7); 1063 | 1064 | let sh = shl(sh, 100); 1065 | assert!(bits(&sh) == 0, 8); 1066 | } 1067 | 1068 | #[test] 1069 | fun test_shift_left() { 1070 | let a = from_u128(100); 1071 | let b = shl(a, 2); 1072 | 1073 | assert!(as_u128(b) == 400, 0); 1074 | 1075 | // TODO: more shift left tests. 1076 | } 1077 | 1078 | #[test] 1079 | fun test_shift_right() { 1080 | let a = from_u128(100); 1081 | let b = shr(a, 2); 1082 | 1083 | assert!(as_u128(b) == 25, 0); 1084 | 1085 | // TODO: more shift right tests. 1086 | } 1087 | 1088 | #[test] 1089 | fun test_div() { 1090 | let a = from_u128(100); 1091 | let b = from_u128(5); 1092 | let d = div(a, b); 1093 | 1094 | assert!(as_u128(d) == 20, 0); 1095 | 1096 | let a = from_u128(U64_MAX); 1097 | let b = from_u128(U128_MAX); 1098 | let d = div(a, b); 1099 | assert!(as_u128(d) == 0, 1); 1100 | 1101 | let a = from_u128(U64_MAX); 1102 | let b = from_u128(U128_MAX); 1103 | let d = div(a, b); 1104 | assert!(as_u128(d) == 0, 2); 1105 | 1106 | let a = from_u128(U128_MAX); 1107 | let b = from_u128(U64_MAX); 1108 | let d = div(a, b); 1109 | assert!(as_u128(d) == 18446744073709551617, 2); 1110 | } 1111 | 1112 | #[test] 1113 | #[expected_failure(abort_code=3)] 1114 | fun test_div_by_zero() { 1115 | let a = from_u128(1); 1116 | let _z = div(a, from_u128(0)); 1117 | } 1118 | 1119 | #[test] 1120 | fun test_as_u64() { 1121 | let _ = as_u64(from_u64((U64_MAX as u64))); 1122 | let _ = as_u64(from_u128(1)); 1123 | } 1124 | 1125 | #[test] 1126 | #[expected_failure(abort_code=0)] 1127 | fun test_as_u64_overflow() { 1128 | let _ = as_u64(from_u128(U128_MAX)); 1129 | } 1130 | } 1131 | -------------------------------------------------------------------------------- /sui/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Cetus-AMM" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | cetus_amm = "0xfe111eb2500241698252189ba4a6a480ab02315b" 10 | sui = "0x2" 11 | -------------------------------------------------------------------------------- /sui/sources/amm_config.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_config { 2 | friend cetus_amm::amm_router; 3 | friend cetus_amm::amm_swap; 4 | 5 | use sui::object::{Self, UID, ID}; 6 | use sui::tx_context::{Self, TxContext}; 7 | use sui::event; 8 | use sui::transfer; 9 | 10 | const EPoolPause: u64 = 1; 11 | 12 | struct GlobalPauseStatus has key { 13 | id: UID, 14 | pause: bool, 15 | } 16 | 17 | struct SetPauseEvent has copy, drop { 18 | sender: address, 19 | status: bool 20 | } 21 | 22 | public(friend) fun new_global_pause_status_and_shared(ctx: &mut TxContext): ID { 23 | let global_paulse_status = GlobalPauseStatus { 24 | id: object::new(ctx), 25 | pause: false 26 | }; 27 | 28 | let id = object::id(&global_paulse_status); 29 | transfer::share_object (global_paulse_status); 30 | id 31 | } 32 | 33 | fun get_pause_status(global_pause_status: &GlobalPauseStatus): bool { 34 | global_pause_status.pause 35 | } 36 | 37 | public(friend) fun set_status_and_emit_event(global_pause_status: &mut GlobalPauseStatus, status: bool, ctx: &mut TxContext) { 38 | global_pause_status.pause = status; 39 | 40 | event::emit(SetPauseEvent{ 41 | sender: tx_context::sender(ctx), 42 | status 43 | }); 44 | } 45 | 46 | public fun assert_pause(global_pause_status: &GlobalPauseStatus) { 47 | assert!( 48 | !get_pause_status(global_pause_status), 49 | EPoolPause); 50 | } 51 | } -------------------------------------------------------------------------------- /sui/sources/amm_math.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_math { 2 | public fun safe_mul_div_u64(x: u64, y: u64, z: u64): u64 { 3 | ((x as u128) * (y as u128) / (z as u128) as u64) 4 | } 5 | 6 | public fun safe_compare_mul_u64(a1: u64, b1: u64, a2: u64, b2: u64): bool { 7 | let left = (a1 as u128) * (b1 as u128); 8 | let right = (a2 as u128) * (b2 as u128); 9 | left >= right 10 | } 11 | 12 | public fun safe_mul_u64(x: u64, y: u64): u64 { 13 | ((x as u128) * (y as u128) as u64) 14 | } 15 | } -------------------------------------------------------------------------------- /sui/sources/amm_router.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_router { 2 | use cetus_amm::amm_swap::{Self, Pool, PoolLiquidityCoin, AdminCap, FlashSwapReceipt}; 3 | use cetus_amm::amm_config::{Self, GlobalPauseStatus}; 4 | use cetus_amm::amm_utils; 5 | use sui::coin::{Self,Coin}; 6 | use sui::tx_context::{Self, TxContext}; 7 | use sui::balance::{Self, Balance}; 8 | use sui::transfer; 9 | use sui::pay; 10 | 11 | const ENotEnough: u64 = 1; 12 | const ESwapOutLessthanExpected: u64 = 2; 13 | const ESwapInOverLimitMax: u64 = 3; 14 | const EWrongFee: u64 = 4; 15 | const ELiquidityInsufficientBAmount: u64 = 5; 16 | const ELiquidityInsufficientAAmount: u64 = 6; 17 | const ELiquidityOverLimitADesired: u64 = 7; 18 | const ELiquidityAddLiquidityFailed: u64 = 8; 19 | 20 | public fun add_liquidity( 21 | pool: &mut Pool, 22 | pause_status: &GlobalPauseStatus, 23 | coin_a: Coin, 24 | coin_b: Coin, 25 | amount_a_desired: u64, 26 | amount_b_desired: u64, 27 | amount_a_min: u64, 28 | amount_b_min: u64, 29 | ctx: &mut TxContext) { 30 | 31 | assert!(coin::value(&coin_a) >= amount_a_desired, ENotEnough); 32 | assert!(coin::value(&coin_b) >= amount_b_desired, ENotEnough); 33 | amm_config::assert_pause(pause_status); 34 | 35 | add_liquidity_internal( 36 | pool, 37 | coin_a, 38 | coin_b, 39 | amount_a_desired, 40 | amount_b_desired, 41 | amount_a_min, 42 | amount_b_min, 43 | ctx 44 | ); 45 | } 46 | 47 | fun add_liquidity_internal( 48 | pool: &mut Pool, 49 | coin_a: Coin, 50 | coin_b: Coin, 51 | amount_a_desired: u64, 52 | amount_b_desired: u64, 53 | amount_a_min: u64, 54 | amount_b_min: u64, 55 | ctx: &mut TxContext) { 56 | let (amount_a, amount_b) = calculate_amount_for_liquidity_internal( 57 | pool, 58 | amount_a_desired, 59 | amount_b_desired, 60 | amount_a_min, 61 | amount_b_min 62 | ); 63 | 64 | let balance_a = coin::into_balance(coin_a); 65 | let to_add_a = balance::split(&mut balance_a, amount_a); 66 | let balance_b = coin::into_balance(coin_b); 67 | let to_add_b = balance::split(&mut balance_b, amount_b); 68 | let coin_liquidity = amm_swap::mint_and_emit_event( 69 | pool, 70 | to_add_a, 71 | to_add_b, 72 | amount_a, 73 | amount_b, 74 | ctx 75 | ); 76 | assert!(coin::value(&coin_liquidity) > 0, ELiquidityAddLiquidityFailed); 77 | pay::keep(coin_liquidity, ctx); 78 | 79 | reture_back_or_delete(balance_a, ctx); 80 | reture_back_or_delete(balance_b, ctx); 81 | } 82 | 83 | fun calculate_amount_for_liquidity_internal( 84 | pool: &mut Pool, 85 | amount_a_desired: u64, 86 | amount_b_desired: u64, 87 | amount_a_min: u64, 88 | amount_b_min: u64): (u64, u64) { 89 | let (reserve_a, reserve_b) = amm_swap::get_reserves(pool); 90 | if(reserve_a == 0 && reserve_b == 0) { 91 | (amount_a_desired, amount_b_desired) 92 | } else { 93 | let amount_b_optimal = amm_utils::quote(amount_a_desired, reserve_a, reserve_b); 94 | if (amount_b_optimal <= amount_b_desired) { 95 | assert!(amount_b_optimal >= amount_b_min, ELiquidityInsufficientBAmount); 96 | (amount_a_desired, amount_b_optimal) 97 | } else { 98 | let amount_a_optimal = amm_utils::quote(amount_b_desired, reserve_b, reserve_a); 99 | assert!(amount_a_optimal <= amount_a_desired, ELiquidityOverLimitADesired); 100 | assert!(amount_a_optimal >= amount_a_min, ELiquidityInsufficientAAmount); 101 | (amount_a_optimal, amount_b_desired) 102 | } 103 | } 104 | } 105 | 106 | public fun remove_liquidity( 107 | pool: &mut Pool, 108 | pause_status: &GlobalPauseStatus, 109 | coin_lp: Coin>, 110 | amount_lp: u64, 111 | amount_a_min: u64, 112 | amount_b_min: u64, 113 | ctx: &mut TxContext) { 114 | assert!(coin::value(&coin_lp) >= amount_lp, ENotEnough); 115 | amm_config::assert_pause(pause_status); 116 | 117 | remove_liquidity_internal( 118 | pool, 119 | coin_lp, 120 | amount_lp, 121 | amount_a_min, 122 | amount_b_min, 123 | ctx 124 | ); 125 | } 126 | 127 | fun remove_liquidity_internal( 128 | pool: &mut Pool, 129 | coin_lp: Coin>, 130 | amount_lp: u64, 131 | amount_a_min: u64, 132 | amount_b_min: u64, 133 | ctx: &mut TxContext) { 134 | let balance_lp = coin::into_balance(coin_lp); 135 | let to_burn = balance::split(&mut balance_lp, amount_lp); 136 | let (coin_a, coin_b) = amm_swap::burn_and_emit_event( 137 | pool, 138 | to_burn, 139 | ctx 140 | ); 141 | 142 | assert!(coin::value(&coin_a) >= amount_a_min, ELiquidityInsufficientAAmount); 143 | assert!(coin::value(&coin_b) >= amount_b_min, ELiquidityInsufficientBAmount); 144 | pay::keep(coin_a, ctx); 145 | pay::keep(coin_b, ctx); 146 | reture_back_or_delete(balance_lp, ctx); 147 | } 148 | 149 | public fun init_pool( 150 | _: &AdminCap, 151 | trade_fee_numerator: u64, 152 | trade_fee_denominator: u64, 153 | protocol_fee_numerator: u64, 154 | protocol_fee_denominator: u64, 155 | ctx: &mut TxContext 156 | ) { 157 | assert!(trade_fee_numerator > 0 && trade_fee_denominator > 0, EWrongFee); 158 | assert!(protocol_fee_numerator > 0 && protocol_fee_denominator > 0, EWrongFee); 159 | 160 | amm_swap::init_pool( 161 | trade_fee_numerator, 162 | trade_fee_denominator, 163 | protocol_fee_numerator, 164 | protocol_fee_denominator, 165 | ctx 166 | ); 167 | } 168 | 169 | public fun swap_exact_coinA_for_coinB( 170 | pool: &mut Pool, 171 | pause_status: &GlobalPauseStatus, 172 | coin_a: Coin, 173 | amount_a_in: u64, 174 | amount_b_out_min: u64, 175 | ctx: &mut TxContext 176 | ) { 177 | assert!(coin::value(&coin_a) >= amount_a_in, ENotEnough); 178 | amm_config::assert_pause(pause_status); 179 | 180 | let b_out = compute_out(pool, amount_a_in, true); 181 | assert!(b_out >= amount_b_out_min, ESwapOutLessthanExpected); 182 | let (balance_a_out, balance_b_out, swap_receipt) = 183 | amm_swap::flash_swap_and_emit_event( 184 | pool, 185 | amount_a_in, 186 | b_out, 187 | true, 188 | ctx 189 | ); 190 | balance::destroy_zero(balance_a_out); 191 | 192 | let pay_amount = amm_swap::swap_pay_amount(&swap_receipt); 193 | 194 | let balance_a = coin::into_balance(coin_a); 195 | let balance_pay = balance::split(&mut balance_a, pay_amount); 196 | amm_swap::repay_flash_swap(pool, balance_pay, balance::zero(), swap_receipt); 197 | 198 | pay::keep(coin::from_balance(balance_b_out, ctx), ctx); 199 | reture_back_or_delete(balance_a, ctx); 200 | } 201 | 202 | public fun swap_exact_coinB_for_coinA( 203 | pool: &mut Pool, 204 | pause_status: &GlobalPauseStatus, 205 | coin_b: Coin, 206 | amount_b_in: u64, 207 | amount_a_out_min: u64, 208 | ctx: &mut TxContext 209 | ) { 210 | assert!(coin::value(&coin_b) >= amount_b_in, ENotEnough); 211 | amm_config::assert_pause(pause_status); 212 | 213 | let a_out = compute_out(pool, amount_b_in, false); 214 | assert!(a_out >= amount_a_out_min, ESwapOutLessthanExpected); 215 | let (balance_a_out, balance_b_out, swap_receipt) = 216 | amm_swap::flash_swap_and_emit_event( 217 | pool, 218 | amount_b_in, 219 | a_out, 220 | false, 221 | ctx 222 | ); 223 | balance::destroy_zero(balance_b_out); 224 | 225 | let pay_amount = amm_swap::swap_pay_amount(&swap_receipt); 226 | 227 | let balance_b = coin::into_balance(coin_b); 228 | let balance_pay = balance::split(&mut balance_b, pay_amount); 229 | amm_swap::repay_flash_swap(pool, balance::zero(), balance_pay, swap_receipt); 230 | 231 | pay::keep(coin::from_balance(balance_a_out, ctx), ctx); 232 | reture_back_or_delete(balance_b, ctx); 233 | } 234 | 235 | public fun swap_coinA_for_exact_coinB( 236 | pool: &mut Pool, 237 | pause_status: &GlobalPauseStatus, 238 | coin_a: Coin, 239 | amount_a_max: u64, 240 | amount_b_out: u64, 241 | ctx: &mut TxContext 242 | ) { 243 | assert!(coin::value(&coin_a) >= amount_a_max, ENotEnough); 244 | amm_config::assert_pause(pause_status); 245 | 246 | let a_in = compute_in(pool, amount_b_out, true); 247 | assert!(a_in <= amount_a_max, ESwapInOverLimitMax); 248 | 249 | let(balance_a_out, balance_b_out, swap_receipt) = 250 | amm_swap::flash_swap_and_emit_event( 251 | pool, 252 | a_in, 253 | amount_b_out, 254 | true, 255 | ctx 256 | ); 257 | balance::destroy_zero(balance_a_out); 258 | 259 | let pay_amount = amm_swap::swap_pay_amount(&swap_receipt); 260 | 261 | let balance_a = coin::into_balance(coin_a); 262 | let balance_pay = balance::split(&mut balance_a, pay_amount); 263 | amm_swap::repay_flash_swap(pool, balance_pay, balance::zero(), swap_receipt); 264 | 265 | pay::keep(coin::from_balance(balance_b_out, ctx), ctx); 266 | reture_back_or_delete(balance_a, ctx); 267 | } 268 | 269 | public fun swap_coinB_for_exact_coinA( 270 | pool: &mut Pool, 271 | pause_status: &GlobalPauseStatus, 272 | coin_b: Coin, 273 | amount_b_max: u64, 274 | amount_a_out: u64, 275 | ctx: &mut TxContext 276 | ) { 277 | assert!(coin::value(&coin_b) >= amount_b_max, ENotEnough); 278 | amm_config::assert_pause(pause_status); 279 | 280 | let b_in = compute_in(pool, amount_a_out, false); 281 | assert!(b_in <= amount_b_max, ESwapInOverLimitMax); 282 | 283 | let(balance_a_out, balance_b_out, swap_receipt) = 284 | amm_swap::flash_swap_and_emit_event( 285 | pool, 286 | b_in, 287 | amount_a_out, 288 | false, 289 | ctx 290 | ); 291 | balance::destroy_zero(balance_b_out); 292 | 293 | let pay_amount = amm_swap::swap_pay_amount(&swap_receipt); 294 | 295 | let balance_b = coin::into_balance(coin_b); 296 | let balance_pay = balance::split(&mut balance_b, pay_amount); 297 | amm_swap::repay_flash_swap(pool, balance::zero(), balance_pay, swap_receipt); 298 | 299 | pay::keep(coin::from_balance(balance_a_out, ctx), ctx); 300 | reture_back_or_delete(balance_b, ctx); 301 | } 302 | 303 | /// Flash_swap. 304 | /// Params 305 | /// - pool 306 | /// - a2b: true --> atob; false --> btoa 307 | /// - by_amount_in: indicate the `amount` parameter is swap in coin amount to be consumed or output amount returned.. 308 | /// - amount 309 | /// - ctx 310 | /// Returns 311 | public fun flash_swap( 312 | pool: &mut Pool, 313 | pause_status: &GlobalPauseStatus, 314 | a2b: bool, 315 | by_amount_in: bool, 316 | amount: u64, 317 | ctx: &mut TxContext 318 | ): (Balance, Balance, FlashSwapReceipt) { 319 | amm_config::assert_pause(pause_status); 320 | 321 | let (amount_in, amount_out) = if (by_amount_in) { 322 | let out = compute_out(pool, amount, a2b); 323 | (amount, out) 324 | } else { 325 | let in = compute_in(pool, amount, a2b); 326 | (in, amount) 327 | }; 328 | 329 | amm_swap::flash_swap_and_emit_event( 330 | pool, 331 | amount_in, 332 | amount_out, 333 | a2b, 334 | ctx 335 | ) 336 | } 337 | 338 | public fun repay_flash_swap( 339 | pool: &mut Pool, 340 | balance_a: Balance, 341 | balance_b: Balance, 342 | receipt: FlashSwapReceipt 343 | ) { 344 | amm_swap::repay_flash_swap( 345 | pool, 346 | balance_a, 347 | balance_b, 348 | receipt 349 | ); 350 | } 351 | 352 | 353 | 354 | public fun set_global_pause_status( 355 | _: &AdminCap, 356 | global_pause_status: &mut GlobalPauseStatus, 357 | status: bool, 358 | ctx: &mut TxContext) { 359 | amm_config::set_status_and_emit_event( 360 | global_pause_status, 361 | status, 362 | ctx 363 | ); 364 | } 365 | 366 | fun compute_out(pool: &Pool, amount_in: u64, is_a_to_b: bool): u64 { 367 | let (fee_numerator, fee_denominator) = amm_swap::get_trade_fee(pool); 368 | let (reserve_a, reserve_b) = amm_swap::get_reserves(pool); 369 | 370 | if (is_a_to_b) { 371 | amm_utils::get_amount_out(amount_in, reserve_a, reserve_b, fee_numerator, fee_denominator) 372 | } else { 373 | amm_utils::get_amount_out(amount_in, reserve_b, reserve_a, fee_numerator, fee_denominator) 374 | } 375 | 376 | } 377 | 378 | fun compute_in(pool: &Pool, amount_out: u64, is_a_to_b: bool): u64 { 379 | let (fee_numerator, fee_denominator) = amm_swap::get_trade_fee(pool); 380 | let (reserve_a, reserve_b) = amm_swap::get_reserves(pool); 381 | 382 | if (is_a_to_b) { 383 | amm_utils::get_amount_in(amount_out, reserve_a, reserve_b, fee_numerator, fee_denominator) 384 | } else { 385 | amm_utils::get_amount_in(amount_out, reserve_b, reserve_a, fee_numerator, fee_denominator) 386 | } 387 | } 388 | 389 | public fun set_fee_config( 390 | _: &AdminCap, 391 | pool: &mut Pool, 392 | trade_fee_numerator: u64, 393 | trade_fee_denominator: u64, 394 | protocol_fee_numerator: u64, 395 | protocol_fee_denominator: u64, 396 | ctx: &mut TxContext 397 | ) { 398 | assert!(trade_fee_numerator > 0 && trade_fee_denominator > 0, EWrongFee); 399 | assert!(protocol_fee_numerator > 0 && protocol_fee_denominator > 0, EWrongFee); 400 | 401 | amm_swap::set_fee_and_emit_event( 402 | pool, 403 | trade_fee_numerator, 404 | trade_fee_denominator, 405 | protocol_fee_numerator, 406 | protocol_fee_denominator, 407 | ctx 408 | ); 409 | } 410 | 411 | public fun claim_fee( 412 | _: &AdminCap, 413 | pool: &mut Pool, 414 | ctx: &mut TxContext 415 | ) { 416 | amm_swap::claim_fee(pool, ctx); 417 | } 418 | 419 | fun reture_back_or_delete( 420 | balance: Balance, 421 | ctx: &mut TxContext 422 | ) { 423 | if(balance::value(&balance) > 0) { 424 | transfer::transfer(coin::from_balance(balance , ctx), tx_context::sender(ctx)); 425 | } else { 426 | balance::destroy_zero(balance); 427 | } 428 | } 429 | 430 | } -------------------------------------------------------------------------------- /sui/sources/amm_script.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_script { 2 | use cetus_amm::amm_swap::{Pool,PoolLiquidityCoin, AdminCap}; 3 | use cetus_amm::amm_config::{GlobalPauseStatus}; 4 | use cetus_amm::amm_router; 5 | use sui::coin::{Coin}; 6 | use sui::tx_context::{TxContext}; 7 | 8 | public entry fun init_pool( 9 | admin_cap: &AdminCap, 10 | trade_fee_numerator: u64, 11 | trade_fee_denominator: u64, 12 | protocol_fee_numerator: u64, 13 | protocol_fee_denominator: u64, 14 | ctx: &mut TxContext 15 | ) { 16 | amm_router::init_pool( 17 | admin_cap, 18 | trade_fee_numerator, 19 | trade_fee_denominator, 20 | protocol_fee_numerator, 21 | protocol_fee_denominator, 22 | ctx 23 | ); 24 | } 25 | 26 | public entry fun swap_exact_coinA_for_coinB( 27 | pool: &mut Pool, 28 | pause_status: &GlobalPauseStatus, 29 | coin_a: Coin, 30 | amount_a_in: u64, 31 | amount_b_out_min: u64, 32 | ctx: &mut TxContext 33 | ) { 34 | amm_router::swap_exact_coinA_for_coinB( 35 | pool, 36 | pause_status, 37 | coin_a, 38 | amount_a_in, 39 | amount_b_out_min, 40 | ctx 41 | ); 42 | } 43 | 44 | public entry fun swap_exact_coinB_for_coinA( 45 | pool: &mut Pool, 46 | pause_status: &GlobalPauseStatus, 47 | coin_b: Coin, 48 | amount_b_in: u64, 49 | amount_a_out_min: u64, 50 | ctx: &mut TxContext 51 | ) { 52 | amm_router::swap_exact_coinB_for_coinA( 53 | pool, 54 | pause_status, 55 | coin_b, 56 | amount_b_in, 57 | amount_a_out_min, 58 | ctx 59 | ); 60 | } 61 | 62 | public entry fun swap_coinA_for_exact_coinB( 63 | pool: &mut Pool, 64 | pause_status: &GlobalPauseStatus, 65 | coin_a: Coin, 66 | amount_a_max: u64, 67 | amount_b_out: u64, 68 | ctx: &mut TxContext 69 | ) { 70 | amm_router::swap_coinA_for_exact_coinB( 71 | pool, 72 | pause_status, 73 | coin_a, 74 | amount_a_max, 75 | amount_b_out, 76 | ctx 77 | ); 78 | } 79 | 80 | public entry fun swap_coinB_for_exact_coinA( 81 | pool: &mut Pool, 82 | pause_status: &GlobalPauseStatus, 83 | coin_b: Coin, 84 | amount_b_max: u64, 85 | amount_a_out: u64, 86 | ctx: &mut TxContext 87 | ) { 88 | amm_router::swap_coinB_for_exact_coinA( 89 | pool, 90 | pause_status, 91 | coin_b, 92 | amount_b_max, 93 | amount_a_out, 94 | ctx 95 | ); 96 | } 97 | 98 | public entry fun add_liquidity( 99 | pool: &mut Pool, 100 | pause_status: &GlobalPauseStatus, 101 | coin_a: Coin, 102 | coin_b: Coin, 103 | amount_a_desired: u64, 104 | amount_b_desired: u64, 105 | amount_a_min: u64, 106 | amount_b_min: u64, 107 | ctx: &mut TxContext 108 | ) { 109 | amm_router::add_liquidity( 110 | pool, 111 | pause_status, 112 | coin_a, 113 | coin_b, 114 | amount_a_desired, 115 | amount_b_desired, 116 | amount_a_min, 117 | amount_b_min, 118 | ctx); 119 | } 120 | 121 | public entry fun remove_liquidity( 122 | pool: &mut Pool, 123 | pause_status: &GlobalPauseStatus, 124 | coin_lp: Coin>, 125 | amount_lp: u64, 126 | amount_a_min: u64, 127 | amount_b_min: u64, 128 | ctx: &mut TxContext 129 | ) { 130 | amm_router::remove_liquidity( 131 | pool, 132 | pause_status, 133 | coin_lp, 134 | amount_lp, 135 | amount_a_min, 136 | amount_b_min, 137 | ctx 138 | ); 139 | } 140 | 141 | public entry fun set_global_pause_status( 142 | admin_cap: &AdminCap, 143 | global_pause_status: &mut GlobalPauseStatus, 144 | status: bool, 145 | ctx: &mut TxContext) { 146 | amm_router::set_global_pause_status( 147 | admin_cap, 148 | global_pause_status, 149 | status, 150 | ctx); 151 | } 152 | 153 | public entry fun set_fee_config( 154 | admin_cap: &AdminCap, 155 | pool: &mut Pool, 156 | trade_fee_numerator: u64, 157 | trade_fee_denominator: u64, 158 | protocol_fee_numerator: u64, 159 | protocol_fee_denominator: u64, 160 | ctx: &mut TxContext 161 | ) { 162 | amm_router::set_fee_config( 163 | admin_cap, 164 | pool, 165 | trade_fee_numerator, 166 | trade_fee_denominator, 167 | protocol_fee_numerator, 168 | protocol_fee_denominator, 169 | ctx 170 | ); 171 | } 172 | 173 | public entry fun claim_fee( 174 | admin_cap: &AdminCap, 175 | pool: &mut Pool, 176 | ctx: &mut TxContext 177 | ) { 178 | amm_router::claim_fee( 179 | admin_cap, 180 | pool, 181 | ctx 182 | ); 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /sui/sources/amm_swap.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_swap { 2 | friend cetus_amm::amm_router; 3 | use sui::object::{Self, UID, ID}; 4 | use sui::coin::{Self, Coin}; 5 | use sui::balance::{Self, Supply, Balance}; 6 | use sui::transfer; 7 | use sui::event; 8 | use sui::math; 9 | use sui::tx_context::{Self, TxContext}; 10 | use cetus_amm::amm_config::{new_global_pause_status_and_shared}; 11 | use cetus_amm::amm_math; 12 | use sui::pay; 13 | 14 | const MINIMUM_LIQUIDITY: u64 = 10; 15 | 16 | const ECoinInsufficient: u64 = 0; 17 | const ESwapoutCalcInvalid: u64 = 1; 18 | const ELiquidityInsufficientMinted: u64 = 2; 19 | const ELiquiditySwapBurnCalcInvalid: u64 = 3; 20 | const EPoolInvalid: u64 = 4; 21 | const EAMOUNTINCORRECT: u64 = 5; 22 | 23 | struct AdminCap has key { 24 | id: UID, 25 | } 26 | 27 | struct PoolLiquidityCoin has drop{} 28 | 29 | struct Pool has key { 30 | id: UID, 31 | 32 | coin_a: Balance, 33 | coin_b: Balance, 34 | coin_a_admin: Balance, 35 | coin_b_admin: Balance, 36 | 37 | lp_locked: Balance>, 38 | lp_supply: Supply>, 39 | 40 | trade_fee_numerator: u64, 41 | trade_fee_denominator: u64, 42 | protocol_fee_numerator: u64, 43 | protocol_fee_denominator: u64, 44 | } 45 | 46 | struct InitEvent has copy, drop { 47 | sender: address, 48 | global_paulse_status_id: ID 49 | } 50 | 51 | struct InitPoolEvent has copy, drop { 52 | sender: address, 53 | pool_id: ID, 54 | trade_fee_numerator: u64, 55 | trade_fee_denominator: u64, 56 | protocol_fee_numerator: u64, 57 | protocol_fee_denominator: u64, 58 | } 59 | 60 | struct LiquidityEvent has copy, drop { 61 | sender: address, 62 | pool_id: ID, 63 | is_add_liquidity: bool, 64 | liquidity: u64, 65 | amount_a: u64, 66 | amount_b: u64, 67 | } 68 | 69 | struct SwapEvent has copy, drop { 70 | sender: address, 71 | pool_id: ID, 72 | amount_a_in: u64, 73 | amount_a_out: u64, 74 | amount_b_in: u64, 75 | amount_b_out: u64, 76 | } 77 | 78 | struct SetFeeEvent has copy, drop { 79 | sender: address, 80 | pool_id: ID, 81 | trade_fee_numerator: u64, 82 | trade_fee_denominator: u64, 83 | protocol_fee_numerator: u64, 84 | protocol_fee_denominator: u64, 85 | } 86 | 87 | struct ClaimFeeEvent has copy, drop { 88 | sender: address, 89 | pool_id: ID, 90 | amount_a: u64, 91 | amount_b: u64, 92 | } 93 | 94 | struct FlashSwapReceipt { 95 | pool_id: ID, 96 | a2b: bool, 97 | pay_amount: u64, 98 | protocol_fee_amount: u64, 99 | } 100 | 101 | fun init(ctx: &mut TxContext) { 102 | transfer::transfer( 103 | AdminCap{ 104 | id: object::new(ctx) 105 | }, 106 | tx_context::sender(ctx) 107 | ); 108 | 109 | let id = new_global_pause_status_and_shared(ctx); 110 | event::emit(InitEvent{ 111 | sender: tx_context::sender(ctx), 112 | global_paulse_status_id: id 113 | }); 114 | } 115 | 116 | public(friend) fun init_pool( 117 | trade_fee_numerator: u64, 118 | trade_fee_denominator: u64, 119 | protocol_fee_numerator: u64, 120 | protocol_fee_denominator: u64, 121 | ctx: &mut TxContext 122 | ) { 123 | let pool = make_pool( 124 | trade_fee_numerator, 125 | trade_fee_denominator, 126 | protocol_fee_numerator, 127 | protocol_fee_denominator, 128 | ctx); 129 | let pool_id = object::id(&pool); 130 | transfer::share_object(pool); 131 | 132 | event::emit(InitPoolEvent{ 133 | sender: tx_context::sender(ctx), 134 | pool_id, 135 | trade_fee_numerator, 136 | trade_fee_denominator, 137 | protocol_fee_numerator, 138 | protocol_fee_denominator 139 | }); 140 | } 141 | 142 | fun make_pool( 143 | trade_fee_numerator: u64, 144 | trade_fee_denominator: u64, 145 | protocol_fee_numerator: u64, 146 | protocol_fee_denominator: u64, 147 | ctx: &mut TxContext 148 | ): Pool { 149 | 150 | let lp_supply = balance::create_supply(PoolLiquidityCoin{}); 151 | 152 | Pool { 153 | id: object::new(ctx), 154 | coin_a: balance::zero(), 155 | coin_b: balance::zero(), 156 | coin_a_admin: balance::zero(), 157 | coin_b_admin: balance::zero(), 158 | lp_locked: balance::zero>(), 159 | lp_supply, 160 | trade_fee_numerator, 161 | trade_fee_denominator, 162 | protocol_fee_numerator, 163 | protocol_fee_denominator 164 | } 165 | } 166 | 167 | public fun get_trade_fee(pool: &Pool): (u64, u64) { 168 | (pool.trade_fee_numerator, pool.trade_fee_denominator) 169 | } 170 | 171 | public fun get_protocol_fee(pool: &Pool): (u64, u64) { 172 | (pool.protocol_fee_numerator, pool.protocol_fee_denominator) 173 | } 174 | 175 | public fun get_reserves(pool: &Pool): (u64, u64) { 176 | (balance::value(&pool.coin_a), balance::value(&pool.coin_b)) 177 | } 178 | 179 | public(friend) fun flash_swap_and_emit_event( 180 | pool: &mut Pool, 181 | amount_in: u64, 182 | amount_out: u64, 183 | a2b: bool, 184 | ctx: &mut TxContext 185 | ): (Balance, Balance, FlashSwapReceipt) { 186 | let (balance_a_swapped, balance_b_swapped, receipt) = flash_swap(pool, amount_in, amount_out, a2b); 187 | if (a2b) { 188 | event::emit(SwapEvent{ 189 | sender: tx_context::sender(ctx), 190 | pool_id: object::id(pool), 191 | amount_a_in: amount_in, 192 | amount_a_out: 0, 193 | amount_b_in: 0, 194 | amount_b_out: amount_out, 195 | }); 196 | } else { 197 | event::emit(SwapEvent{ 198 | sender: tx_context::sender(ctx), 199 | pool_id: object::id(pool), 200 | amount_a_in: 0, 201 | amount_a_out: amount_out, 202 | amount_b_in: amount_in, 203 | amount_b_out: 0, 204 | }); 205 | }; 206 | (balance_a_swapped, balance_b_swapped, receipt) 207 | } 208 | 209 | public fun swap_pay_amount(receipt: &FlashSwapReceipt): u64 { 210 | receipt.pay_amount 211 | } 212 | 213 | public(friend) fun flash_swap( 214 | pool: &mut Pool, 215 | amount_in: u64, 216 | amount_out: u64, 217 | a2b: bool, 218 | ): (Balance, Balance, FlashSwapReceipt) { 219 | assert!( amount_in > 0, ECoinInsufficient); 220 | let (a_reserve, b_reserve) = get_reserves(pool); 221 | let (fee_numerator, fee_denominator) = get_trade_fee(pool); 222 | 223 | let balance_a_swapped = balance::zero(); 224 | let balance_b_swapped = balance::zero(); 225 | if (a2b) { 226 | balance::join(&mut balance_b_swapped, balance::split(&mut pool.coin_b, amount_out)); 227 | let a_reserve_new = a_reserve + amount_in; 228 | let b_reserve_new = b_reserve - amount_out; 229 | 230 | let (a_adjusted, b_adjusted) = new_reserves_adjusted( 231 | a_reserve_new, 232 | b_reserve_new, 233 | amount_in, 234 | 0, 235 | fee_numerator, 236 | fee_denominator); 237 | 238 | 239 | assert_lp_value_incr( 240 | a_reserve, 241 | b_reserve, 242 | a_adjusted, 243 | b_adjusted, 244 | fee_denominator 245 | ); 246 | } else { 247 | balance::join(&mut balance_a_swapped, balance::split(&mut pool.coin_a, amount_out)); 248 | let a_reserve_new = a_reserve - amount_out; 249 | let b_reserve_new = b_reserve + amount_in; 250 | 251 | let (a_adjusted, b_adjusted) = new_reserves_adjusted( 252 | a_reserve_new, 253 | b_reserve_new, 254 | 0, 255 | amount_in, 256 | fee_numerator, 257 | fee_denominator); 258 | 259 | assert_lp_value_incr( 260 | a_reserve, 261 | b_reserve, 262 | a_adjusted, 263 | b_adjusted, 264 | fee_denominator 265 | ); 266 | }; 267 | 268 | let (protocol_fee_numberator, protocol_fee_denominator) = calc_swap_protocol_fee_rate(pool); 269 | let protocol_swap_fee = amm_math::safe_mul_div_u64(amount_in, protocol_fee_numberator, protocol_fee_denominator); 270 | (balance_a_swapped, balance_b_swapped, FlashSwapReceipt { 271 | pool_id: object::id(pool), 272 | a2b, 273 | pay_amount: amount_in, 274 | protocol_fee_amount: protocol_swap_fee 275 | }) 276 | 277 | } 278 | 279 | public(friend) fun repay_flash_swap( 280 | pool: &mut Pool, 281 | balance_a: Balance, 282 | balance_b: Balance, 283 | receipt: FlashSwapReceipt 284 | ) { 285 | let FlashSwapReceipt { 286 | pool_id, 287 | a2b, 288 | pay_amount, 289 | protocol_fee_amount 290 | } = receipt; 291 | assert!(pool_id == object::id(pool), EPoolInvalid); 292 | 293 | if (a2b) { 294 | assert!(balance::value(&balance_a) == pay_amount, EAMOUNTINCORRECT); 295 | let balance_protocol_fee = balance::split(&mut balance_a, protocol_fee_amount); 296 | balance::join(&mut pool.coin_a, balance_a); 297 | balance::join(&mut pool.coin_a_admin, balance_protocol_fee); 298 | balance::destroy_zero(balance_b); 299 | } else { 300 | assert!(balance::value(&balance_b) == pay_amount, EAMOUNTINCORRECT); 301 | let balance_protocol_fee = balance::split(&mut balance_b, protocol_fee_amount); 302 | balance::join(&mut pool.coin_b, balance_b); 303 | balance::join(&mut pool.coin_b_admin, balance_protocol_fee); 304 | balance::destroy_zero(balance_a); 305 | } 306 | } 307 | 308 | public(friend) fun swap_and_emit_event( 309 | pool: &mut Pool, 310 | balance_a_in: Balance, 311 | b_out: u64, 312 | balance_b_in: Balance, 313 | a_out: u64, 314 | ctx: &mut TxContext 315 | ):(Balance, Balance, Balance, Balance) { 316 | let balance_a_in_value = balance::value(&balance_a_in); 317 | let balance_b_in_value = balance::value(&balance_b_in); 318 | 319 | let (balance_a_out, balance_b_out, balance_a_fee, balance_b_fee) = swap(pool, balance_a_in, b_out, balance_b_in, a_out); 320 | event::emit(SwapEvent{ 321 | sender: tx_context::sender(ctx), 322 | pool_id: object::id(pool), 323 | amount_a_in: balance_a_in_value, 324 | amount_a_out: balance::value(&balance_a_out), 325 | amount_b_in: balance_b_in_value, 326 | amount_b_out: balance::value(&balance_b_out), 327 | }); 328 | (balance_a_out, balance_b_out, balance_a_fee, balance_b_fee) 329 | } 330 | 331 | fun swap( 332 | pool: &mut Pool, 333 | balance_a_in: Balance, 334 | b_out: u64, 335 | balance_b_in: Balance, 336 | a_out: u64 337 | ):(Balance, Balance, Balance, Balance) { 338 | let balance_a_in_value = balance::value(&balance_a_in); 339 | let balance_b_in_value = balance::value(&balance_b_in); 340 | assert!( 341 | balance_a_in_value > 0 || balance_b_in_value > 0, 342 | ECoinInsufficient 343 | ); 344 | 345 | let (a_reserve, b_reserve) = get_reserves(pool); 346 | balance::join(&mut pool.coin_a, balance_a_in); 347 | balance::join(&mut pool.coin_b, balance_b_in); 348 | 349 | 350 | let balance_a_swapped = balance::split(&mut pool.coin_a, a_out); 351 | let balance_b_swapped = balance::split(&mut pool.coin_b, b_out); 352 | 353 | { 354 | let a_reserve_new = balance::value(&pool.coin_a); 355 | let b_reserve_new = balance::value(&pool.coin_b); 356 | let (fee_numerator, fee_denominator) = get_trade_fee(pool); 357 | 358 | let (a_adjusted, b_adjusted) = new_reserves_adjusted( 359 | a_reserve_new, 360 | b_reserve_new, 361 | balance_a_in_value, 362 | balance_b_in_value, 363 | fee_numerator, 364 | fee_denominator); 365 | 366 | 367 | assert_lp_value_incr( 368 | a_reserve, 369 | b_reserve, 370 | a_adjusted, 371 | b_adjusted, 372 | fee_denominator 373 | ); 374 | }; 375 | 376 | let (protocol_fee_numberator, protocol_fee_denominator) = calc_swap_protocol_fee_rate(pool); 377 | let a_swap_fee = balance::split(&mut pool.coin_a, amm_math::safe_mul_div_u64(balance_a_in_value, protocol_fee_numberator, protocol_fee_denominator)); 378 | let b_swap_fee = balance::split(&mut pool.coin_b, amm_math::safe_mul_div_u64(balance_b_in_value, protocol_fee_numberator, protocol_fee_denominator)); 379 | (balance_a_swapped, balance_b_swapped, a_swap_fee, b_swap_fee) 380 | } 381 | 382 | fun calc_swap_protocol_fee_rate(pool: &Pool): (u64, u64) { 383 | let (fee_numerator, fee_denominator) = get_trade_fee(pool); 384 | let (protocol_fee_numerator, protocol_fee_denominator) = get_protocol_fee(pool); 385 | (amm_math::safe_mul_u64(fee_numerator, protocol_fee_numerator), amm_math::safe_mul_u64(fee_denominator, protocol_fee_denominator)) 386 | } 387 | 388 | public(friend) fun handle_swap_protocol_fee(pool: &mut Pool, fee_a: Balance, fee_b: Balance) { 389 | balance::join(&mut pool.coin_a_admin, fee_a); 390 | balance::join(&mut pool.coin_b_admin, fee_b); 391 | } 392 | 393 | public(friend) fun set_fee_and_emit_event( 394 | pool: &mut Pool, 395 | trade_fee_numerator: u64, 396 | trade_fee_denominator: u64, 397 | protocol_fee_numerator: u64, 398 | protocol_fee_denominator: u64, 399 | ctx: &mut TxContext 400 | ) { 401 | pool.trade_fee_numerator = trade_fee_numerator; 402 | pool.trade_fee_denominator = trade_fee_denominator; 403 | pool.protocol_fee_numerator = protocol_fee_numerator; 404 | pool.protocol_fee_denominator = protocol_fee_denominator; 405 | 406 | event::emit(SetFeeEvent{ 407 | sender: tx_context::sender(ctx), 408 | pool_id: object::id(pool), 409 | trade_fee_numerator, 410 | trade_fee_denominator, 411 | protocol_fee_numerator, 412 | protocol_fee_denominator 413 | }); 414 | } 415 | 416 | public(friend) fun claim_fee( 417 | pool: &mut Pool, 418 | ctx: &mut TxContext 419 | ) { 420 | let a_fee_value = balance::value(&pool.coin_a_admin); 421 | let b_fee_value = balance::value(&pool.coin_b_admin); 422 | 423 | assert!( 424 | a_fee_value > 0 || b_fee_value > 0, 425 | ECoinInsufficient 426 | ); 427 | 428 | let balance_a_fee = balance::split(&mut pool.coin_a_admin, a_fee_value); 429 | let balance_b_fee = balance::split(&mut pool.coin_b_admin, b_fee_value); 430 | 431 | pay::keep(coin::from_balance(balance_a_fee, ctx), ctx); 432 | pay::keep(coin::from_balance(balance_b_fee, ctx), ctx); 433 | 434 | event::emit(ClaimFeeEvent{ 435 | sender: tx_context::sender(ctx), 436 | pool_id: object::id(pool), 437 | amount_a: a_fee_value, 438 | amount_b: b_fee_value, 439 | }); 440 | } 441 | 442 | public(friend) fun mint_and_emit_event( 443 | pool: &mut Pool, 444 | balance_a: Balance, 445 | balance_b: Balance, 446 | amount_a_desired: u64, 447 | amount_b_desired: u64, 448 | ctx: &mut TxContext 449 | ): Coin> { 450 | let coin_liquidity = mint(pool, balance_a, balance_b, ctx); 451 | event::emit(LiquidityEvent{ 452 | sender: tx_context::sender(ctx), 453 | pool_id: object::id(pool), 454 | is_add_liquidity: true, 455 | liquidity: coin::value(&coin_liquidity), 456 | amount_a: amount_a_desired, 457 | amount_b: amount_b_desired, 458 | }); 459 | coin_liquidity 460 | } 461 | 462 | fun mint( 463 | pool: &mut Pool, 464 | balance_a: Balance, 465 | balance_b: Balance, 466 | ctx: &mut TxContext 467 | ): Coin> { 468 | let (reserve_a, reserve_b) = get_reserves(pool); 469 | 470 | let amount_a = balance::value(&balance_a); 471 | let amonut_b = balance::value(&balance_b); 472 | 473 | let total_supply = balance::supply_value(&pool.lp_supply); 474 | let liquidity: u64; 475 | if (total_supply == 0) { 476 | liquidity = (math::sqrt_u128((amount_a as u128) * (amonut_b as u128)) as u64) - MINIMUM_LIQUIDITY; 477 | let balance_lp_locked = balance::increase_supply(&mut pool.lp_supply, MINIMUM_LIQUIDITY); 478 | balance::join(&mut pool.lp_locked, balance_lp_locked); 479 | } else { 480 | liquidity = math::min( 481 | amm_math::safe_mul_div_u64(amount_a, total_supply, reserve_a), 482 | amm_math::safe_mul_div_u64(amonut_b, total_supply, reserve_b)); 483 | }; 484 | 485 | assert!(liquidity > 0, ELiquidityInsufficientMinted); 486 | 487 | balance::join(&mut pool.coin_a, balance_a); 488 | balance::join(&mut pool.coin_b, balance_b); 489 | 490 | coin::from_balance( 491 | balance::increase_supply( 492 | &mut pool.lp_supply, 493 | liquidity 494 | ), ctx) 495 | } 496 | 497 | public(friend) fun burn_and_emit_event( 498 | pool: &mut Pool, 499 | to_burn: Balance>, 500 | ctx: &mut TxContext 501 | ): (Coin, Coin) { 502 | let to_burn_value = balance::value(&to_burn); 503 | let (coin_a, coin_b) = burn(pool, to_burn, ctx); 504 | 505 | event::emit(LiquidityEvent{ 506 | sender: tx_context::sender(ctx), 507 | pool_id: object::id(pool), 508 | is_add_liquidity: false, 509 | liquidity: to_burn_value, 510 | amount_a: coin::value(&coin_a), 511 | amount_b: coin::value(&coin_b), 512 | }); 513 | 514 | (coin_a, coin_b) 515 | } 516 | 517 | fun burn( 518 | pool: &mut Pool, 519 | to_burn: Balance>, 520 | ctx: &mut TxContext 521 | ): (Coin, Coin) { 522 | let to_burn_value = balance::value(&to_burn); 523 | 524 | let (reserve_a, reserve_b) = get_reserves(pool); 525 | let total_supply = balance::supply_value(&pool.lp_supply); 526 | 527 | let amount_a = amm_math::safe_mul_div_u64(to_burn_value, reserve_a, total_supply); 528 | let amount_b = amm_math::safe_mul_div_u64(to_burn_value, reserve_b, total_supply); 529 | assert!(amount_a > 0 && amount_b > 0, ELiquiditySwapBurnCalcInvalid); 530 | 531 | balance::decrease_supply(&mut pool.lp_supply, to_burn); 532 | 533 | let coin_a = coin::from_balance(balance::split(&mut pool.coin_a, amount_a), ctx); 534 | let coin_b = coin::from_balance(balance::split(&mut pool.coin_b, amount_b), ctx); 535 | (coin_a, coin_b) 536 | } 537 | 538 | fun new_reserves_adjusted( 539 | a_reserve: u64, 540 | b_reserve: u64, 541 | a_in_val: u64, 542 | b_in_val: u64, 543 | fee_numerator: u64, 544 | fee_denominator: u64 545 | ) : (u64, u64) { 546 | let a_adjusted = a_reserve * fee_denominator - a_in_val * fee_numerator; 547 | let b_adjusted = b_reserve * fee_denominator - b_in_val * fee_numerator; 548 | (a_adjusted, b_adjusted) 549 | } 550 | 551 | fun assert_lp_value_incr( 552 | a_reserve: u64, 553 | b_reserve: u64, 554 | a_adjusted: u64, 555 | b_adjusted: u64, 556 | fee_denominator: u64 557 | ) { 558 | assert!( 559 | amm_math::safe_compare_mul_u64(a_adjusted, b_adjusted, a_reserve * fee_denominator, b_reserve * fee_denominator), 560 | ESwapoutCalcInvalid); 561 | } 562 | 563 | } -------------------------------------------------------------------------------- /sui/sources/amm_utils.move: -------------------------------------------------------------------------------- 1 | module cetus_amm::amm_utils { 2 | use std::debug; 3 | use cetus_amm::amm_math; 4 | 5 | const EParamInvalid: u64 = 1; 6 | public fun get_amount_in( 7 | amount_out: u64, 8 | reserve_in: u64, 9 | reserve_out: u64, 10 | fee_numerator: u64, 11 | fee_denumerator: u64 12 | ): u64 { 13 | assert!(amount_out > 0, EParamInvalid); 14 | assert!(reserve_in > 0 && reserve_out > 0, EParamInvalid); 15 | assert!(fee_numerator > 0 && fee_denumerator > 0, EParamInvalid); 16 | assert!(fee_denumerator > fee_numerator, EParamInvalid); 17 | assert!(reserve_out > amount_out, EParamInvalid); 18 | 19 | let denominator = (reserve_out - amount_out) * (fee_denumerator - fee_numerator); 20 | amm_math::safe_mul_div_u64(amount_out * fee_denumerator, reserve_in, denominator) + 1 21 | } 22 | 23 | public fun get_amount_out( 24 | amount_in: u64, 25 | reserve_in: u64, 26 | reserve_out: u64, 27 | fee_numerator: u64, 28 | fee_denumerator: u64 29 | ): u64 { 30 | assert!(amount_in > 0, EParamInvalid); 31 | assert!(reserve_in > 0 && reserve_out > 0, EParamInvalid); 32 | assert!(fee_numerator > 0 && fee_denumerator > 0, EParamInvalid); 33 | assert!(fee_denumerator > fee_numerator, EParamInvalid); 34 | 35 | let amount_in_with_fee = amount_in * (fee_denumerator - fee_numerator); 36 | let denominator = reserve_in * fee_denumerator + amount_in_with_fee; 37 | amm_math::safe_mul_div_u64(amount_in_with_fee, reserve_out, denominator) 38 | } 39 | 40 | public fun quote(amount_a: u64, reserve_a: u64, reserve_b: u64): u64 { 41 | assert!(amount_a > 0, EParamInvalid); 42 | assert!(reserve_a > 0 && reserve_b > 0, EParamInvalid); 43 | amm_math::safe_mul_div_u64(amount_a, reserve_b, reserve_a) 44 | } 45 | 46 | #[test] 47 | public entry fun test_get_amount_out() { 48 | let amount_in:u64 = 1000000; 49 | let reserve_in: u64 = 200006931; 50 | let reserve_out: u64 = 201999600; 51 | let out = get_amount_out(amount_in, reserve_in, reserve_out, 2, 1000); 52 | debug::print(&out); 53 | //assert!(out == 196735475, 3004); 54 | } 55 | } --------------------------------------------------------------------------------