diff --git a/precompile/binaries/stdlib/minitswap.mv b/precompile/binaries/stdlib/minitswap.mv index 65b6f778..69ce2466 100644 Binary files a/precompile/binaries/stdlib/minitswap.mv and b/precompile/binaries/stdlib/minitswap.mv differ diff --git a/precompile/binaries/stdlib/stableswap.mv b/precompile/binaries/stdlib/stableswap.mv index 0ef91d59..3f4b108d 100644 Binary files a/precompile/binaries/stdlib/stableswap.mv and b/precompile/binaries/stdlib/stableswap.mv differ diff --git a/precompile/modules/initia_stdlib/sources/minitswap.move b/precompile/modules/initia_stdlib/sources/minitswap.move index c62b582b..cbdaba93 100644 --- a/precompile/modules/initia_stdlib/sources/minitswap.move +++ b/precompile/modules/initia_stdlib/sources/minitswap.move @@ -8,6 +8,7 @@ module initia_std::minitswap { use initia_std::decimal128::{Self, Decimal128}; use initia_std::table::{Self, Table}; use initia_std::object::{Self, ExtendRef, Object}; + use initia_std::stableswap::{Self, Pool}; use initia_std::string::{Self, String}; use initia_std::fungible_asset::{Self, FungibleAsset, Metadata}; use initia_std::primary_fungible_store; @@ -24,6 +25,7 @@ module initia_std::minitswap { const EMAX_CHANGE: u64 = 8; const EMIN_RETURN: u64 = 9; const EPOOL_SIZE: u64= 10; + const ENOT_L2_INIT: u64 = 11; const A_PRECISION: u256 = 100; const U64_MAX: u128 = 18_446_744_073_709_551_615; @@ -46,6 +48,19 @@ module initia_std::minitswap { burn_cap: coin::BurnCapability, } + // extend pool store + struct StableswapPoolStore has key { + /// List of pools + pools: Table, Object>, + + // initial configs + + /// ANN + ann: u64, + /// swap fee rate + swap_fee_rate: Decimal128, + } + struct VirtualPool has key { /// Extend reference extend_ref: ExtendRef, @@ -73,6 +88,34 @@ module initia_std::minitswap { active: bool, } + #[event] + /// Event emitted when virtual pool created + struct CreatePoolEvent has drop, store { + l2_init_metadata: Object, + recover_velocity: Decimal128, + pool_size: u64, + ann: u64, + max_ratio: Decimal128, + recover_param: Decimal128, + } + + #[event] + /// Event emitted when virtual pool size changed + struct ChangePoolSizeEvent has drop, store { + l2_init_metadata: Object, + pool_size: u64, + } + + #[event] + /// Event emitted when update param of virtual pool + struct UpdatePoolParamsEvent has drop, store { + l2_init_metadata: Object, + recover_velocity: Option, + ann: Option, + max_ratio: Option, + recover_param: Option, + } + #[event] /// Event emitted when provide. struct ProvideEvent has drop, store { @@ -109,6 +152,13 @@ module initia_std::minitswap { fee_amount: u64, // always l1 init } + #[event] + /// Event emitted when stable swap pool created + struct CreateStableswapPoolEvent has drop, store { + l2_init_metadata: Object, + pool: Object, + } + fun init_module(chain: &signer) { let constructor_ref = object::create_object(@initia_std, false); let extend_ref = object::generate_extend_ref(&constructor_ref); @@ -132,6 +182,12 @@ module initia_std::minitswap { mint_cap, burn_cap, }); + + move_to(chain, StableswapPoolStore { + pools: table::new(), + ann: 3000, // TODO: adjust value + swap_fee_rate: decimal128::from_ratio(1, 1000), // 0.1% + }) } // @@ -192,37 +248,9 @@ module initia_std::minitswap { offer_metadata: Object, return_metadata: Object, offer_amount: u64, - ): (u64, u64) acquires ModuleStore, VirtualPool { - let is_l1_init_offered = is_l1_init_metadata(offer_metadata); - let l2_init_metadata = if(is_l1_init_offered) { - return_metadata - } else { - offer_metadata - }; - - let (_, pool) = borrow_all(l2_init_metadata); - let (peg_keeper_offer_amount, peg_keeper_return_amount) = calc_peg_keeper_swap(pool); - - let (l1_pool_amount, l2_pool_amount) = get_pool_amount(l2_init_metadata, true); - l1_pool_amount = l1_pool_amount + peg_keeper_offer_amount; - l2_pool_amount = l2_pool_amount - peg_keeper_return_amount; - - let (module_store, pool) = borrow_all(l2_init_metadata); - let fee_amount = 0; - let return_amount = if (is_l1_init_offered) { - // 0 fee for L1 > L2 - let return_amount = get_return_amount(offer_amount, l1_pool_amount, l2_pool_amount, pool.pool_size, pool.ann); - assert!( - l2_pool_amount >= pool.pool_size && l1_pool_amount <= pool.pool_size, - error::invalid_state(EL2_PRICE_TOO_LOW), - ); - return_amount - } else { - let return_amount = get_return_amount(offer_amount, l2_pool_amount, l1_pool_amount, pool.pool_size, pool.ann); - fee_amount = decimal128::mul_u64(&module_store.swap_fee_rate, return_amount); - let return_amount = return_amount - fee_amount; - return_amount - }; + ): (u64, u64) acquires ModuleStore, VirtualPool, StableswapPoolStore { + let (return_amount, fee_amount, _use_virtual_pool) + = swap_simulation_internal(offer_metadata, return_metadata, offer_amount); (return_amount, fee_amount) } @@ -232,7 +260,7 @@ module initia_std::minitswap { offer_denom: String, return_denom: String, offer_amount: u64, - ): (u64, u64) acquires ModuleStore, VirtualPool { + ): (u64, u64) acquires ModuleStore, VirtualPool, StableswapPoolStore { let offer_metadata = coin::denom_to_metadata(offer_denom); let return_metadata = coin::denom_to_metadata(return_denom); swap_simulation(offer_metadata, return_metadata, offer_amount) @@ -278,6 +306,15 @@ module initia_std::minitswap { let module_store = borrow_global_mut(@initia_std); table::add(&mut module_store.pools, l2_init_metadata, object::object_from_constructor_ref(&constructor_ref)); + + event::emit(CreatePoolEvent { + l2_init_metadata, + recover_velocity, + pool_size, + ann, + max_ratio, + recover_param, + }) } public entry fun deactivate(chain: &signer, l2_init_metadata: Object) acquires ModuleStore, VirtualPool { @@ -383,7 +420,12 @@ module initia_std::minitswap { } else { pool.virtual_l1_balance = pool.virtual_l1_balance - return_amount; } - } + }; + + event::emit(ChangePoolSizeEvent { + l2_init_metadata, + pool_size: new_pool_size, + }) } public entry fun update_module_params( @@ -432,6 +474,14 @@ module initia_std::minitswap { if (option::is_some(&recover_param)) { pool.recover_param = option::extract(&mut recover_param); }; + + event::emit(UpdatePoolParamsEvent { + l2_init_metadata, + recover_velocity, + ann, + max_ratio, + recover_param, + }) } @@ -456,13 +506,29 @@ module initia_std::minitswap { public entry fun swap( account: &signer, offer_asset_metadata: Object, - return_metadata: Object, + return_asset_metadata: Object, amount: u64, min_return_amount: Option - ) acquires ModuleStore, VirtualPool { + ) acquires ModuleStore, VirtualPool, StableswapPoolStore { let offer_asset = primary_fungible_store::withdraw(account, offer_asset_metadata, amount); - let return_asset = swap_internal(offer_asset, return_metadata); + let (_, _, use_virtual_pool) + = swap_simulation_internal(offer_asset_metadata, return_asset_metadata, amount); + + let return_asset = if (use_virtual_pool) { + swap_internal(offer_asset, return_asset_metadata) + } else { + let l2_init_metadata = if(is_l1_init_metadata(offer_asset_metadata)) { + return_asset_metadata + } else { + offer_asset_metadata + }; + let stableswap_pool_store = borrow_global(@initia_std); + let pool = table::borrow(&stableswap_pool_store.pools, l2_init_metadata); + stableswap::swap(*pool, offer_asset, return_asset_metadata, min_return_amount) + }; + assert_min_amount(&return_asset, min_return_amount); + primary_fungible_store::deposit(signer::address_of(account), return_asset); } @@ -478,6 +544,42 @@ module initia_std::minitswap { primary_fungible_store::deposit(signer::address_of(account), l2_init); } + // stableswap + + public entry fun create_stableswap_pool( + account: &signer, + l2_init_metadata: Object, + l1_init_amount: u64, + l2_init_amount: u64, + ) acquires ModuleStore, StableswapPoolStore { + let module_store = borrow_global_mut(@initia_std); + let stableswap_pool_store = borrow_global_mut(@initia_std); + + let l2_symbol = coin::symbol(l2_init_metadata); + + assert!(coin::metadata(@initia_std, l2_symbol) == l2_init_metadata, error::invalid_argument(ENOT_L2_INIT)); + let creator = object::generate_signer_for_extending(&module_store.extend_ref); + let symbol = string::utf8(b"INIT - "); + string::append(&mut symbol, l2_symbol); + + let coins: vector = vector[ + coin::withdraw(account, l1_init_metadata(), l1_init_amount), + coin::withdraw(account, l2_init_metadata, l2_init_amount), + ]; + + let liquidity_token = stableswap::create_pair(&creator, symbol, symbol, stableswap_pool_store.swap_fee_rate, coins, stableswap_pool_store.ann); + let metadata = fungible_asset::metadata_from_asset(&liquidity_token); + let pool = object::convert(metadata); + + table::add(&mut stableswap_pool_store.pools, l2_init_metadata, object::convert(metadata)); + + primary_fungible_store::deposit(signer::address_of(account), liquidity_token); + event::emit(CreateStableswapPoolEvent { + l2_init_metadata, + pool, + }); + } + public fun provide_internal(l1_init: FungibleAsset): FungibleAsset acquires ModuleStore { assert!(is_l1_init(&l1_init), error::invalid_argument(ENOT_L1_INIT)); let provide_amount = fungible_asset::amount(&l1_init); @@ -638,6 +740,16 @@ module initia_std::minitswap { (module_store, pool) } + inline fun virtual_pool_exists(metadata: Object): bool acquires ModuleStore { + let module_store = borrow_global(@initia_std); + table::contains(&module_store.pools, metadata) + } + + inline fun stableswap_pool_exists(metadata: Object): bool acquires StableswapPoolStore { + let stableswap_pool_store = borrow_global(@initia_std); + table::contains(&stableswap_pool_store.pools, metadata) + } + inline fun calc_peg_keeper_swap(pool: &VirtualPool): (u64, u64) acquires ModuleStore, VirtualPool { let (_, timestamp) = block::get_block_info(); @@ -877,6 +989,73 @@ module initia_std::minitswap { } } + // TODO: update to optimal swap + fun swap_simulation_internal( + offer_metadata: Object, + return_metadata: Object, + offer_amount: u64, + ): (u64, u64, bool) acquires ModuleStore, VirtualPool, StableswapPoolStore { + let is_l1_init_offered = is_l1_init_metadata(offer_metadata); + let l2_init_metadata = if(is_l1_init_offered) { + return_metadata + } else { + offer_metadata + }; + + let stableswap_pool_exists = stableswap_pool_exists(l2_init_metadata); + let virtual_pool_exists = virtual_pool_exists(l2_init_metadata); + + assert!(stableswap_pool_exists || virtual_pool_exists, error::invalid_argument(EPOOL_NOT_FOUND)); + + let (stableswap_pool_return_amount, stableswap_pool_fee) = if (stableswap_pool_exists) { + let stableswap_pool_store = borrow_global(@initia_std); + let pool = table::borrow(&stableswap_pool_store.pools, l2_init_metadata); + let (return_amount, fee_amount) = stableswap::swap_simulation(*pool, offer_metadata, return_metadata, offer_amount); + (return_amount - fee_amount, fee_amount) + } else { + (0, 0) + }; + + let (virtual_pool_return_amount, virtual_pool_fee) = if (virtual_pool_exists) { + let (l1_pool_amount, l2_pool_amount) = get_pool_amount(l2_init_metadata, true); + + let (module_store, pool) = borrow_all(l2_init_metadata); + let fee_amount = 0; + let return_amount = if (is_l1_init_offered) { + // 0 fee for L1 > L2 + let return_amount = get_return_amount(offer_amount, l1_pool_amount, l2_pool_amount, pool.pool_size, pool.ann); + + if (!stableswap_pool_exists) { + assert!( + l2_pool_amount >= pool.pool_size && l1_pool_amount <= pool.pool_size, + error::invalid_state(EL2_PRICE_TOO_LOW), + ); + }; + + if (l2_pool_amount >= pool.pool_size && l1_pool_amount <= pool.pool_size) { + return_amount = 0 + }; + + return_amount + } else { + let return_amount = get_return_amount(offer_amount, l2_pool_amount, l1_pool_amount, pool.pool_size, pool.ann); + fee_amount = decimal128::mul_u64(&module_store.swap_fee_rate, return_amount); + let return_amount = return_amount - fee_amount; + return_amount + }; + + (return_amount, fee_amount) + } else { + (0, 0) + }; + + if (stableswap_pool_return_amount > virtual_pool_return_amount) { + (stableswap_pool_return_amount, stableswap_pool_fee, false) + } else { + (virtual_pool_return_amount, virtual_pool_fee, true) + } + } + #[test_only] fun initialized_coin( account: &signer, @@ -898,9 +1077,11 @@ module initia_std::minitswap { #[test(chain = @0x1)] fun end_to_end( chain: signer, - ) acquires ModuleStore, VirtualPool { + ) acquires ModuleStore, VirtualPool, StableswapPoolStore { initia_std::primary_fungible_store::init_module_for_test(&chain); init_module(&chain); + stableswap::init_module_for_test(&chain); + block::set_block_info(0, 100); let chain_addr = signer::address_of(&chain); @@ -938,7 +1119,15 @@ module initia_std::minitswap { decimal128::from_ratio(2, 1), ); + create_stableswap_pool( + &chain, + l2_1_metadata, + 10000000, + 10000000 + ); + let (return_amount, _) = swap_simulation(l2_1_metadata, init_metadata, 1000000); + let balance_before = coin::balance(chain_addr, init_metadata); swap(&chain, l2_1_metadata, init_metadata, 1000000, option::none()); let balance_after = coin::balance(chain_addr, init_metadata); @@ -956,7 +1145,7 @@ module initia_std::minitswap { block::set_block_info(0, 141); swap(&chain, l2_1_metadata, init_metadata, 100, option::none()); swap(&chain, init_metadata, l2_1_metadata, 10000, option::none()); - rebalance(&chain, l2_1_metadata, 4100000, option::none()); + rebalance(&chain, l2_1_metadata, 100000, option::none()); change_pool_size(&chain, l2_1_metadata, 9000000); } } diff --git a/precompile/modules/initia_stdlib/sources/stableswap.move b/precompile/modules/initia_stdlib/sources/stableswap.move index 5abf5862..ed67cfd8 100644 --- a/precompile/modules/initia_stdlib/sources/stableswap.move +++ b/precompile/modules/initia_stdlib/sources/stableswap.move @@ -758,7 +758,7 @@ module initia_std::stableswap { (y as u64) } - fun swap_simulation( + public fun swap_simulation( pair: Object, offer_coin_metadata: Object, return_coin_metadata: Object, @@ -808,6 +808,11 @@ module initia_std::stableswap { assert!(signer::address_of(chain) == @initia_std, error::permission_denied(EUNAUTHORIZED)); } + #[test_only] + public fun init_module_for_test(chain: &signer) { + init_module(chain) + } + #[test_only] fun initialized_coin( account: &signer,