diff --git a/Cargo.lock b/Cargo.lock index 6727e57a9..2ea06c09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12281,7 +12281,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.30.0" +version = "1.31.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index aae8efa65..4bdbb8041 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.30.0" +version = "1.31.0" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/driver/example.rs b/integration-tests/src/driver/example.rs new file mode 100644 index 000000000..2449c9f82 --- /dev/null +++ b/integration-tests/src/driver/example.rs @@ -0,0 +1,53 @@ +use crate::driver::HydrationTestDriver; +use crate::polkadot_test_net::*; +use frame_support::assert_ok; +use hydradx_runtime::*; + +#[test] +fn driver_test_example() { + HydrationTestDriver::default() + .setup_hydration() + .execute(|| { + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + CHARLIE.into(), + DOT, + 20_000_000_000_000_000_000_000_000, + 0, + )); + + assert_ok!(Omnipool::sell( + hydradx_runtime::RuntimeOrigin::signed(CHARLIE.into()), + DOT, + HDX, + 1_000_000_000_000, + 0u128, + )); + }) + .new_block() + .execute_with_driver(|driver| { + // This is useful, so we can have access to some info stored in the driver itself + // such as list of omnipool assets or stablepools + let stable_pool_id = driver.stablepools[0].0; + let stable_asset_a = driver.stablepools[0].1[0].0; + let stable_asset_b = driver.stablepools[0].1[1].0; + let stable_asset_a_decimals = driver.stablepools[0].1[0].1 as u32; + + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + CHARLIE.into(), + stable_asset_a, + 10 * 10u128.pow(stable_asset_a_decimals), + 0, + )); + + assert_ok!(Stableswap::sell( + hydradx_runtime::RuntimeOrigin::signed(CHARLIE.into()), + stable_pool_id, + stable_asset_a, + stable_asset_b, + 1u128 * 10u128.pow(stable_asset_a_decimals), + 0u128, + )); + }); +} diff --git a/integration-tests/src/driver/mod.rs b/integration-tests/src/driver/mod.rs new file mode 100644 index 000000000..9871b06c9 --- /dev/null +++ b/integration-tests/src/driver/mod.rs @@ -0,0 +1,327 @@ +mod example; + +use crate::polkadot_test_net::*; +use frame_support::assert_ok; +use frame_support::traits::fungible::Mutate; +use frame_support::BoundedVec; +use hydradx_runtime::*; +use hydradx_traits::stableswap::AssetAmount; +use hydradx_traits::AggregatedPriceOracle; +use pallet_asset_registry::AssetType; +use pallet_stableswap::MAX_ASSETS_IN_POOL; +use primitives::constants::chain::{OMNIPOOL_SOURCE, STABLESWAP_SOURCE}; +use primitives::{AccountId, AssetId}; +use sp_runtime::{FixedU128, Permill}; +use xcm_emulator::TestExt; + +pub(crate) struct HydrationTestDriver { + omnipool_assets: Vec, + stablepools: Vec<(AssetId, Vec<(AssetId, u8)>)>, +} + +impl HydrationTestDriver { + pub(crate) fn add_omnipool_assets(self, assets: Vec) -> Self { + let mut driver = self; + driver.omnipool_assets.extend(assets); + driver + } + + pub(crate) fn add_stablepools(self, pools: Vec<(AssetId, Vec<(AssetId, u8)>)>) -> Self { + let mut driver = self; + driver.stablepools.extend(pools); + driver + } +} + +impl HydrationTestDriver { + pub(crate) fn default() -> Self { + TestNet::reset(); + HydrationTestDriver { + omnipool_assets: vec![], + stablepools: vec![], + } + } + + pub(crate) fn execute(&self, f: impl FnOnce()) -> &Self { + Hydra::ext_wrapper(|| { + f(); + }); + self + } + + pub(crate) fn execute_with_driver(&self, f: impl FnOnce(&Self)) -> &Self { + Hydra::ext_wrapper(|| { + f(self); + }); + self + } + + pub(crate) fn setup_hydration(self) -> Self { + self.setup_omnipool() + .setup_stableswap() + .add_stablepools_to_omnipool() + .populate_oracle() + } + + pub(crate) fn setup_omnipool(self) -> Self { + self.execute(|| { + let acc = hydradx_runtime::Omnipool::protocol_account(); + let native_price = FixedU128::from_rational(29903049701668757, 73927734532192294158); + let dot_price = FixedU128::from_rational(103158291366950047, 4566210555614178); + + let dot_amount: primitives::Balance = 4566210555614178u128; + let native_amount: primitives::Balance = 73927734532192294158u128; + let weth_amount: primitives::Balance = 1074271742496220564487u128; + let weth_price = FixedU128::from_rational(67852651072676287, 1074271742496220564487); + + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + acc.clone(), + DOT, + dot_amount, + 0 + )); + Balances::set_balance(&acc, native_amount); + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + acc.clone(), + WETH, + weth_amount, + 0 + )); + + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + HDX, + native_price, + Permill::from_percent(60), + AccountId::from(ALICE), + )); + + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + DOT, + dot_price, + Permill::from_percent(60), + AccountId::from(ALICE), + )); + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + WETH, + weth_price, + Permill::from_percent(60), + AccountId::from(ALICE), + )); + }); + + self.add_omnipool_assets(vec![HDX, DOT, WETH]) + } + + pub(crate) fn setup_stableswap(self) -> Self { + let mut stable_pool_id = 0; + let mut stable_assets = vec![]; + self.execute(|| { + let possible_decimals: Vec = vec![6u8, 10u8, 12u8, 12u8, 18u8]; + let initial_liquidity = 1000u128; + let mut asset_ids: Vec<(AssetId, u8)> = Vec::new(); + let mut initial: Vec> = vec![]; + + let asset_offset = 555u32; + + for idx in 0u32..MAX_ASSETS_IN_POOL { + let name: Vec = idx.to_ne_bytes().to_vec(); + let decimals = possible_decimals[idx as usize % possible_decimals.len() as usize]; + let result = AssetRegistry::register( + RawOrigin::Root.into(), + Some(asset_offset + idx), + Some(name.clone().try_into().unwrap()), + AssetType::Token, + Some(1000u128), + Some(name.try_into().unwrap()), + Some(decimals), + None, + None, + true, + ); + assert_ok!(result); + let asset_id = asset_offset + idx; + asset_ids.push((asset_id, decimals)); + + let liquidity = initial_liquidity * 10u128.pow(decimals as u32); + + assert_ok!(Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + AccountId::from(BOB), + asset_id, + liquidity as i128, + )); + initial.push(AssetAmount::new(asset_id, liquidity)); + } + + let pool_id = 222_222u32; + let result = AssetRegistry::register( + RawOrigin::Root.into(), + Some(pool_id), + Some(b"pool".to_vec().try_into().unwrap()), + AssetType::StableSwap, + Some(1u128), + None, + None, + None, + None, + true, + ); + assert_ok!(result); + let amplification = 100u16; + let fee = Permill::from_percent(1); + + assert_ok!(Stableswap::create_pool( + hydradx_runtime::RuntimeOrigin::root(), + pool_id, + asset_ids.iter().map(|(id, _)| *id).collect(), + amplification, + fee, + )); + + assert_ok!(Stableswap::add_liquidity( + hydradx_runtime::RuntimeOrigin::signed(BOB.into()), + pool_id, + BoundedVec::truncate_from(initial) + )); + stable_pool_id = pool_id; + stable_assets = asset_ids; + }); + + self.add_stablepools(vec![(stable_pool_id, stable_assets)]) + } + + pub(crate) fn add_stablepools_to_omnipool(self) -> Self { + self.execute(|| { + let omnipool_acc = hydradx_runtime::Omnipool::protocol_account(); + for (pool_id, _) in self.stablepools.iter() { + let pool_id_issuance = Tokens::total_issuance(pool_id); + assert_ok!(hydradx_runtime::Currencies::transfer( + hydradx_runtime::RuntimeOrigin::signed(BOB.into()), + omnipool_acc.clone().into(), + *pool_id, + pool_id_issuance, + )); + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + *pool_id, + FixedU128::from_inner(25_650_000_000_000_000), + Permill::from_percent(1), + AccountId::from(BOB), + )); + } + }); + let stableids = self.stablepools.iter().map(|(pool_id, _)| *pool_id).collect(); + self.add_omnipool_assets(stableids) + } + + fn populate_oracle(self) -> Self { + //we need to make trades for each asset in omnipool + //for at least 10 block to ensure SHORT oracle is updated too + for _ in 1..=10 { + self.new_block(); + let assets = self.omnipool_assets.clone(); + let stablepools = self.stablepools.clone(); + self.execute(|| { + for asset_id in assets { + let amount_to_sell = 1_000_000_000_000; + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + CHARLIE.into(), + crate::polkadot_test_net::LRNA, + amount_to_sell, + 0, + )); + + assert_ok!(Omnipool::sell( + RuntimeOrigin::signed(CHARLIE.into()), + crate::polkadot_test_net::LRNA, + asset_id, + amount_to_sell, + 1 + )); + } + + for (pool_id, assets) in stablepools { + for two_assets in assets.windows(2) { + let asset_a = two_assets[0]; + let asset_b = two_assets[1]; + let amount = 1u128 * 10u128.pow(asset_a.1 as u32); + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + CHARLIE.into(), + asset_a.0, + amount, + 0, + )); + + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(CHARLIE.into()), + pool_id, + asset_a.0, + asset_b.0, + amount, + 1 + )); + } + } + }); + } + + self + } + + fn new_block(&self) -> &Self { + self.execute(|| { + hydradx_run_to_next_block(); + }); + self + } +} + +#[test] +fn test_hydration_setup() { + HydrationTestDriver::default() + .setup_hydration() + .execute_with_driver(|driver| { + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + CHARLIE.into(), + DOT, + 20_000_000_000_000_000_000_000_000, + 0, + )); + + assert_ok!(Omnipool::sell( + hydradx_runtime::RuntimeOrigin::signed(CHARLIE.into()), + DOT, + HDX, + 1_000_000_000_000, + 0u128, + )); + + assert_eq!(driver.omnipool_assets, vec![HDX, DOT, WETH, 222_222]); + assert!(driver.stablepools.len() > 0); + + let stablepool_1 = driver.stablepools[0].clone(); + let first_asset_id = stablepool_1.1[0].0; + let pool_id = stablepool_1.0; + + // assert oracle initial values + for supported_period in crate::oracle::SUPPORTED_PERIODS { + assert!( + EmaOracle::get_price(HDX, crate::polkadot_test_net::LRNA, *supported_period, OMNIPOOL_SOURCE) + .is_ok() + ); + assert!( + EmaOracle::get_price(DOT, crate::polkadot_test_net::LRNA, *supported_period, OMNIPOOL_SOURCE) + .is_ok() + ); + assert!(EmaOracle::get_price(first_asset_id, pool_id, *supported_period, STABLESWAP_SOURCE).is_ok()); + } + }); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index ec6d6752f..30898014b 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -8,6 +8,7 @@ mod circuit_breaker; mod contracts; mod cross_chain_transfer; mod dca; +mod driver; mod dust; mod dust_removal_whitelist; mod dynamic_fees; diff --git a/integration-tests/src/oracle.rs b/integration-tests/src/oracle.rs index 08238a1c2..b87849d22 100644 --- a/integration-tests/src/oracle.rs +++ b/integration-tests/src/oracle.rs @@ -37,7 +37,7 @@ pub fn hydradx_run_to_block(to: BlockNumber) { const HDX: AssetId = CORE_ASSET_ID; -const SUPPORTED_PERIODS: &[OraclePeriod] = &[LastBlock, Short, TenMinutes]; +pub(crate) const SUPPORTED_PERIODS: &[OraclePeriod] = &[LastBlock, Short, TenMinutes]; const UNSUPPORTED_PERIODS: &[OraclePeriod] = &[Hour, Day, Week]; #[test]