diff --git a/CHANGELOG.md b/CHANGELOG.md index ff92c5d70f..2470973b2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Upgrade encointer protocol to 6.1.0 ([polkadot-fellows/runtimes#236](https://github.com/polkadot-fellows/runtimes/pull/236)) - Update NFT deposits according to RFC-45 ([polkadot-fellows/runtimes#237](https://github.com/polkadot-fellows/runtimes/pull/237)) - Add Kusama People Chain ([polkadot-fellows/runtimes#217](https://github.com/polkadot-fellows/runtimes/pull/217)) +- Asset Conversion setup for Polkadot Asset Hub, and XCM Swap Weight Trader for both Asset Hubs ([polkadot-fellows/runtimes#218](https://github.com/polkadot-fellows/runtimes/pull/218)) ### Changed diff --git a/Cargo.lock b/Cargo.lock index 946b1e3a09..f0c1cfd92a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,6 +570,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "integration-tests-helpers", + "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-message-queue", @@ -616,7 +617,8 @@ dependencies = [ "hex-literal", "kusama-runtime-constants", "log", - "pallet-asset-tx-payment", + "pallet-asset-conversion", + "pallet-asset-conversion-tx-payment", "pallet-assets", "pallet-aura", "pallet-authorship", @@ -643,6 +645,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-common", "polkadot-runtime-constants", + "primitive-types", "scale-info", "sp-api", "sp-block-builder", @@ -1331,6 +1334,7 @@ dependencies = [ "integration-tests-helpers", "kusama-polkadot-system-emulated-network", "kusama-system-emulated-network", + "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-bridge-messages", @@ -1452,6 +1456,7 @@ dependencies = [ "frame-support", "integration-tests-helpers", "kusama-polkadot-system-emulated-network", + "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-bridge-messages", diff --git a/integration-tests/emulated/chains/parachains/assets/asset-hub-polkadot/src/lib.rs b/integration-tests/emulated/chains/parachains/assets/asset-hub-polkadot/src/lib.rs index b0f35b3a6c..4a91be0a3e 100644 --- a/integration-tests/emulated/chains/parachains/assets/asset-hub-polkadot/src/lib.rs +++ b/integration-tests/emulated/chains/parachains/assets/asset-hub-polkadot/src/lib.rs @@ -45,6 +45,8 @@ decl_test_parachains! { Balances: asset_hub_polkadot_runtime::Balances, Assets: asset_hub_polkadot_runtime::Assets, ForeignAssets: asset_hub_polkadot_runtime::ForeignAssets, + PoolAssets: asset_hub_polkadot_runtime::PoolAssets, + AssetConversion: asset_hub_polkadot_runtime::AssetConversion, } }, } diff --git a/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/swap.rs b/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/swap.rs index 16e7b0d2ae..f0fb361b81 100644 --- a/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/swap.rs +++ b/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/swap.rs @@ -71,7 +71,7 @@ fn swap_locally_on_chain_using_local_assets() { 2_000_000_000_000, 0, 0, - AssetHubKusamaSender::get().into() + AssetHubKusamaSender::get() )); assert_expected_events!( @@ -89,7 +89,7 @@ fn swap_locally_on_chain_using_local_assets() { path, 100, 1, - AssetHubKusamaSender::get().into(), + AssetHubKusamaSender::get(), true ) ); @@ -112,7 +112,7 @@ fn swap_locally_on_chain_using_local_assets() { * retrieved. */ 0, 0, - AssetHubKusamaSender::get().into(), + AssetHubKusamaSender::get(), )); }); } @@ -150,8 +150,9 @@ fn swap_locally_on_chain_using_foreign_assets() { let penpal_as_seen_by_ah = AssetHubKusama::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahk = AssetHubKusama::sovereign_account_id_of(penpal_as_seen_by_ah); AssetHubKusama::fund_accounts(vec![ - (AssetHubKusamaSender::get().into(), 5_000_000 * KUSAMA_ED), /* An account to swap dot - * for something else. */ + (AssetHubKusamaSender::get(), 5_000_000 * ASSET_HUB_KUSAMA_ED), /* An account to swap + * ksm + * for something else. */ ]); AssetHubKusama::execute_with(|| { @@ -163,7 +164,7 @@ fn swap_locally_on_chain_using_foreign_assets() { type RuntimeEvent = ::RuntimeEvent; // 3. Mint foreign asset (in reality this should be a teleport or some such) assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed(sov_penpal_on_ahk.clone().into()), + ::RuntimeOrigin::signed(sov_penpal_on_ahk.clone()), foreign_asset_at_asset_hub_kusama, sov_penpal_on_ahk.clone().into(), 3_000_000_000_000, @@ -199,7 +200,7 @@ fn swap_locally_on_chain_using_foreign_assets() { 2_000_000_000_000, 0, 0, - sov_penpal_on_ahk.clone().into() + sov_penpal_on_ahk.clone() )); assert_expected_events!( @@ -220,7 +221,7 @@ fn swap_locally_on_chain_using_foreign_assets() { path, 100000, 1000, - AssetHubKusamaSender::get().into(), + AssetHubKusamaSender::get(), true ) ); @@ -243,7 +244,7 @@ fn swap_locally_on_chain_using_foreign_assets() { 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, - sov_penpal_on_ahk.clone().into(), + sov_penpal_on_ahk.clone(), )); }); } @@ -264,7 +265,7 @@ fn cannot_create_pool_from_pool_assets() { assert_ok!(::PoolAssets::create( ::RuntimeOrigin::signed(pool_owner_account_id.clone()), - ASSET_ID.into(), + ASSET_ID, pool_owner_account_id.clone().into(), 1000, )); @@ -272,7 +273,7 @@ fn cannot_create_pool_from_pool_assets() { assert_ok!(::PoolAssets::mint( ::RuntimeOrigin::signed(pool_owner_account_id), - ASSET_ID.into(), + ASSET_ID, AssetHubKusamaSender::get().into(), 3_000_000_000_000, )); @@ -287,3 +288,135 @@ fn cannot_create_pool_from_pool_assets() { ); }); } + +#[test] +fn pay_xcm_fee_with_some_asset_swapped_for_native() { + let asset_native: xcm::v3::Location = asset_hub_kusama_runtime::xcm_config::KsmLocation::get() + .try_into() + .expect("conversion works"); + let asset_one = xcm::v3::Location { + parents: 0, + interior: [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(ASSET_ID.into()), + ] + .into(), + }; + let penpal = AssetHubKusama::sovereign_account_id_of(AssetHubKusama::sibling_location_of( + PenpalA::para_id(), + )); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // set up pool with ASSET_ID <> NATIVE pair + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + ASSET_ID.into(), + AssetHubKusamaSender::get().into(), + ASSET_MIN_BALANCE, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + ASSET_ID.into(), + AssetHubKusamaSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + Box::new(asset_native), + Box::new(asset_one), + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + Box::new(asset_native), + Box::new(asset_one), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubKusamaSender::get() + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + // ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID` + assert_eq!( + ::Balances::free_balance(penpal.clone()), + 0 + ); + + assert_ok!(::Assets::touch_other( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + )); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + 10_000_000_000_000, + )); + }); + + PenpalA::execute_with(|| { + // send xcm transact from `penpal` account which has only `ASSET_ID` tokens on + // `AssetHubKusama` + let call = AssetHubKusama::force_create_asset_call( + ASSET_ID + 1000, + penpal.clone(), + true, + ASSET_MIN_BALANCE, + ); + + let penpal_root = ::RuntimeOrigin::root(); + let fee_amount = 4_000_000_000_000u128; + let asset_one = + ([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into(); + let asset_hub_location = PenpalA::sibling_location_of(AssetHubKusama::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + OriginKind::SovereignAccount, + asset_one, + penpal.clone(), + ); + + assert_ok!(::PolkadotXcm::send( + penpal_root, + bx!(asset_hub_location), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true,.. }) => {}, + ] + ); + }); +} diff --git a/integration-tests/emulated/tests/assets/asset-hub-polkadot/Cargo.toml b/integration-tests/emulated/tests/assets/asset-hub-polkadot/Cargo.toml index 706bcfaca4..594237c7fe 100644 --- a/integration-tests/emulated/tests/assets/asset-hub-polkadot/Cargo.toml +++ b/integration-tests/emulated/tests/assets/asset-hub-polkadot/Cargo.toml @@ -16,6 +16,7 @@ sp-runtime = { version = "32.0.0" } frame-support = { version = "29.0.0" } pallet-balances = { version = "29.0.0" } pallet-assets = { version = "30.0.0" } +pallet-asset-conversion = { version = "11.0.0" } pallet-treasury = { version = "28.0.0" } pallet-message-queue = { version = "32.0.0" } diff --git a/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/mod.rs b/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/mod.rs index 4029a58cae..a5dced4fcf 100644 --- a/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/mod.rs +++ b/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/mod.rs @@ -18,6 +18,7 @@ mod fellowship_treasury; mod reserve_transfer; mod send; mod set_xcm_versions; +mod swap; mod teleport; mod treasury; diff --git a/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/swap.rs b/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/swap.rs new file mode 100644 index 0000000000..63a1fa45c7 --- /dev/null +++ b/integration-tests/emulated/tests/assets/asset-hub-polkadot/src/tests/swap.rs @@ -0,0 +1,443 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use polkadot_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; +use sp_runtime::ModuleError; +use system_parachains_constants::polkadot::currency::SYSTEM_PARA_EXISTENTIAL_DEPOSIT; + +#[test] +fn swap_locally_on_chain_using_local_assets() { + use frame_support::traits::fungible::Mutate; + + let asset_native: xcm::v3::Location = + asset_hub_polkadot_runtime::xcm_config::DotLocation::get() + .try_into() + .expect("conversion works"); + let asset_one = v3::Location::new( + 0, + [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ], + ); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ASSET_ID.into(), + AssetHubPolkadotSender::get().into(), + 1000, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ASSET_ID.into(), + AssetHubPolkadotSender::get().into(), + 3_000_000_000_000, + )); + + ::Balances::set_balance( + &AssetHubPolkadotSender::get(), + 3_000_000_000_000, + ); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + bx!(asset_native), + bx!(asset_one), + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + bx!(asset_native), + bx!(asset_one), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubPolkadotSender::get() + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + let path = vec![bx!(asset_native), bx!(asset_one)]; + + assert_ok!( + ::AssetConversion::swap_exact_tokens_for_tokens( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + path, + 100, + 1, + AssetHubPolkadotSender::get(), + true + ) + ); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. }) => { + amount_in: *amount_in == 100, + amount_out: *amount_out == 199, + }, + ] + ); + + assert_ok!( + ::AssetConversion::remove_liquidity( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + bx!(asset_native), + bx!(asset_one), + 1414213562273 - SYSTEM_PARA_EXISTENTIAL_DEPOSIT * 2, /* all but the 2 EDs can't + * be + * retrieved. */ + 0, + 0, + AssetHubPolkadotSender::get(), + ) + ); + }); +} + +#[test] +fn swap_locally_on_chain_using_foreign_assets() { + let asset_native = Box::new( + v3::Location::try_from(asset_hub_polkadot_runtime::xcm_config::DotLocation::get()) + .expect("conversion works"), + ); + + let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubPolkadot::para_id()); + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(v3::Junction::GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalBSender::get(); + let foreign_asset_at_asset_hub_polkadot = + v3::Location::new(1, [v3::Junction::Parachain(PenpalB::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + + // 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_polkadot + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_polkadot, + ah_as_seen_by_penpal, + true, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); + + let penpal_as_seen_by_ah = AssetHubPolkadot::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahk = AssetHubPolkadot::sovereign_account_id_of(penpal_as_seen_by_ah); + AssetHubPolkadot::fund_accounts(vec![ + (AssetHubPolkadotSender::get(), 5_000_000 * POLKADOT_ED), /* An account to swap + * dot + * for something else. */ + ]); + + AssetHubPolkadot::execute_with(|| { + // 3: Mint foreign asset on asset_hub_polkadot: + // + // (While it might be nice to use batch, + // currently that's disabled due to safe call filters.) + + type RuntimeEvent = ::RuntimeEvent; + // 3. Mint foreign asset (in reality this should be a teleport or some such) + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(sov_penpal_on_ahk.clone()), + foreign_asset_at_asset_hub_polkadot, + sov_penpal_on_ahk.clone().into(), + 3_000_000_000_000, + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + + // 4. Create pool: + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_polkadot), + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + // 5. Add liquidity: + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(sov_penpal_on_ahk.clone()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_polkadot), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + sov_penpal_on_ahk.clone() + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { + lp_token_minted: *lp_token_minted == 1414213562273, + }, + ] + ); + + // 6. Swap! + let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_polkadot)]; + + assert_ok!( + ::AssetConversion::swap_exact_tokens_for_tokens( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + path, + 100000, + 1000, + AssetHubPolkadotSender::get(), + true + ) + ); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => { + amount_in: *amount_in == 100000, + amount_out: *amount_out == 199399, + }, + ] + ); + + // 7. Remove liquidity + assert_ok!( + ::AssetConversion::remove_liquidity( + ::RuntimeOrigin::signed(sov_penpal_on_ahk.clone()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_polkadot), + 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. + 0, + 0, + sov_penpal_on_ahk.clone(), + ) + ); + }); +} + +#[test] +fn cannot_create_pool_from_pool_assets() { + use frame_support::traits::fungibles::{Create, Mutate}; + + let asset_native = asset_hub_polkadot_runtime::xcm_config::DotLocation::get() + .try_into() + .expect("conversion works"); + let asset_one = asset_hub_polkadot_runtime::xcm_config::PoolAssetsPalletLocation::get() + .appended_with(GeneralIndex(ASSET_ID.into())) + .expect("valid location") + .try_into() + .expect("conversion works"); + + AssetHubPolkadot::execute_with(|| { + assert_ok!( + <::PoolAssets as Create<_>>::create( + ASSET_ID, + AssetHubPolkadotSender::get(), + false, + 1000, + ) + ); + assert!(::PoolAssets::asset_exists(ASSET_ID)); + + assert_ok!(::PoolAssets::mint_into( + ASSET_ID, + &AssetHubPolkadotSender::get(), + 3_000_000_000_000, + )); + + assert_matches::assert_matches!( + ::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + Box::new(asset_native), + Box::new(asset_one), + ), + Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) + ); + }); +} + +#[test] +fn pay_xcm_fee_with_some_asset_swapped_for_native() { + use frame_support::traits::fungible::Mutate; + + let asset_native: xcm::v3::Location = + asset_hub_polkadot_runtime::xcm_config::DotLocation::get() + .try_into() + .expect("conversion works"); + let asset_one = xcm::v3::Location { + parents: 0, + interior: [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(ASSET_ID.into()), + ] + .into(), + }; + let penpal = AssetHubPolkadot::sovereign_account_id_of(AssetHubPolkadot::sibling_location_of( + PenpalB::para_id(), + )); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // set up pool with ASSET_ID <> NATIVE pair + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ASSET_ID.into(), + AssetHubPolkadotSender::get().into(), + ASSET_MIN_BALANCE, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ASSET_ID.into(), + AssetHubPolkadotSender::get().into(), + 3_000_000_000_000, + )); + + ::Balances::set_balance( + &AssetHubPolkadotSender::get(), + 3_000_000_000_000, + ); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + Box::new(asset_native), + Box::new(asset_one), + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + Box::new(asset_native), + Box::new(asset_one), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubPolkadotSender::get() + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + // ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID` + assert_eq!( + ::Balances::free_balance(penpal.clone()), + 0 + ); + + assert_ok!(::Assets::touch_other( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + )); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + 10_000_000_000_000, + )); + }); + + PenpalB::execute_with(|| { + // send xcm transact from `penpal` account which as only `ASSET_ID` tokens on + // `AssetHubPolkadot` + let call = AssetHubPolkadot::force_create_asset_call( + ASSET_ID + 1000, + penpal.clone(), + true, + ASSET_MIN_BALANCE, + ); + + let penpal_root = ::RuntimeOrigin::root(); + let fee_amount = 4_000_000_000_000u128; + let asset_one = + ([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into(); + let asset_hub_location = PenpalB::sibling_location_of(AssetHubPolkadot::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + OriginKind::SovereignAccount, + asset_one, + penpal.clone(), + ); + + assert_ok!(::PolkadotXcm::send( + penpal_root, + bx!(asset_hub_location), + bx!(xcm), + )); + + PenpalB::assert_xcm_pallet_sent(); + }); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true,.. }) => {}, + ] + ); + }); +} diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/Cargo.toml b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/Cargo.toml index 40b168d264..6db45be9a6 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/Cargo.toml +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/Cargo.toml @@ -13,6 +13,7 @@ publish = false sp-runtime = { version = "32.0.0" } frame-support = { version = "29.0.0" } pallet-balances = { version = "29.0.0" } +pallet-asset-conversion = { version = "11.0.0" } pallet-assets = { version = "30.0.0" } pallet-message-queue = { version = "32.0.0" } diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs index f0a4056cb5..101d2c2b5f 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/lib.rs @@ -32,7 +32,7 @@ pub use bp_messages::LaneId; // Cumulus pub use emulated_integration_tests_common::{ - accounts::ALICE, + accounts::{ALICE, BOB}, impls::Inspect, xcm_emulator::{ assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, @@ -56,9 +56,10 @@ pub use kusama_polkadot_system_emulated_network::{ AssetHubKusamaPara as AssetHubKusama, AssetHubKusamaParaReceiver as AssetHubKusamaReceiver, AssetHubKusamaParaSender as AssetHubKusamaSender, AssetHubPolkadotPara as AssetHubPolkadot, AssetHubPolkadotParaReceiver as AssetHubPolkadotReceiver, - BridgeHubKusamaPara as BridgeHubKusama, BridgeHubKusamaParaSender as BridgeHubKusamaSender, - BridgeHubPolkadotPara as BridgeHubPolkadot, KusamaRelay as Kusama, - KusamaRelayReceiver as KusamaReceiver, KusamaRelaySender as KusamaSender, + AssetHubPolkadotParaSender as AssetHubPolkadotSender, BridgeHubKusamaPara as BridgeHubKusama, + BridgeHubKusamaParaSender as BridgeHubKusamaSender, BridgeHubPolkadotPara as BridgeHubPolkadot, + KusamaRelay as Kusama, KusamaRelayReceiver as KusamaReceiver, + KusamaRelaySender as KusamaSender, }; pub use kusama_system_emulated_network::{ penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet, diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs index 49fb9aba9b..5c94dde5b2 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-kusama/src/tests/asset_transfers.rs @@ -14,6 +14,7 @@ // limitations under the License. use crate::tests::*; +use frame_support::traits::fungible::Mutate; fn send_asset_from_asset_hub_kusama_to_asset_hub_polkadot(id: Location, amount: u128) { let destination = asset_hub_polkadot_location(); @@ -174,3 +175,240 @@ fn send_dots_from_asset_hub_kusama_to_asset_hub_polkadot() { // Reserve balance is reduced by sent amount assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before - amount_to_send); } + +#[test] +fn send_ksms_from_asset_hub_kusama_to_asset_hub_polkadot_fee_from_pool() { + let ksm_at_asset_hub_kusama: v3::Location = v3::Parent.into(); + let ksm_at_asset_hub_polkadot = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Kusama)]); + let owner: AccountId = AssetHubPolkadot::account_id_of(ALICE); + AssetHubPolkadot::force_create_foreign_asset( + ksm_at_asset_hub_polkadot, + owner, + false, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahp_on_ahk = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Polkadot, + AssetHubPolkadot::para_id(), + ); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // setup a pool to pay xcm fees with `ksm_at_asset_hub_polkadot` tokens + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + ksm_at_asset_hub_polkadot, + AssetHubPolkadotSender::get().into(), + 3_000_000_000_000, + )); + + ::Balances::set_balance( + &AssetHubPolkadotSender::get(), + 3_000_000_000_000, + ); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + Box::new(ksm_at_asset_hub_kusama), + Box::new(ksm_at_asset_hub_polkadot), + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get()), + Box::new(ksm_at_asset_hub_kusama), + Box::new(ksm_at_asset_hub_polkadot), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + AssetHubPolkadotSender::get() + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); + + let ksms_in_reserve_on_ahk_before = + ::account_data_of(sov_ahp_on_ahk.clone()).free; + let sender_ksms_before = + ::account_data_of(AssetHubKusamaSender::get()).free; + let receiver_ksms_before = AssetHubPolkadot::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotReceiver::get()) + }); + + let ksm_at_asset_hub_kusama_latest: Location = ksm_at_asset_hub_kusama.try_into().unwrap(); + let amount = ASSET_HUB_KUSAMA_ED * 1_000; + send_asset_from_asset_hub_kusama_to_asset_hub_polkadot(ksm_at_asset_hub_kusama_latest, amount); + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubPolkadot, + vec![ + // issue KSMs on AHP + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == ksm_at_asset_hub_kusama, + owner: *owner == AssetHubPolkadotReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_ksms_after = + ::account_data_of(AssetHubKusamaSender::get()).free; + let receiver_ksms_after = AssetHubPolkadot::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotReceiver::get()) + }); + let ksms_in_reserve_on_ahk_after = + ::account_data_of(sov_ahp_on_ahk.clone()).free; + + // Sender's balance is reduced + assert!(sender_ksms_before >= sender_ksms_after + amount); + // Receiver's balance is increased + assert!(receiver_ksms_after > receiver_ksms_before); + // Reserve balance has increased by sent amount + assert_eq!(ksms_in_reserve_on_ahk_after, ksms_in_reserve_on_ahk_before + amount); +} + +#[test] +fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { + let prefund_amount = 10_000_000_000_000u128; + let dot_at_asset_hub_kusama = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot)]); + let owner: AccountId = AssetHubPolkadot::account_id_of(BOB); + AssetHubKusama::force_create_foreign_asset( + dot_at_asset_hub_kusama, + owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![(AssetHubKusamaSender::get(), prefund_amount)], + ); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // setup a pool to pay xcm fees with `dot_at_asset_hub_kusama` tokens + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(owner.clone()), + dot_at_asset_hub_kusama, + owner.clone().into(), + 3_000_000_000_000, + )); + + ::Balances::set_balance(&owner, 3_000_000_000_000); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(owner.clone()), + Box::new(xcm::v3::Parent.into()), + Box::new(dot_at_asset_hub_kusama), + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(owner.clone()), + Box::new(xcm::v3::Parent.into()), + Box::new(dot_at_asset_hub_kusama), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); + + // fund the AHK's SA on AHP with the DOT tokens held in reserve + let sov_ahk_on_ahp = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Kusama, + AssetHubKusama::para_id(), + ); + AssetHubPolkadot::fund_accounts(vec![(sov_ahk_on_ahp.clone(), prefund_amount)]); + + let dots_in_reserve_on_ahp_before = + ::account_data_of(sov_ahk_on_ahp.clone()).free; + assert_eq!(dots_in_reserve_on_ahp_before, prefund_amount); + let sender_dots_before = AssetHubKusama::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaSender::get()) + }); + assert_eq!(sender_dots_before, prefund_amount); + let receiver_dots_before = + ::account_data_of(AssetHubPolkadotReceiver::get()).free; + + let dot_at_asset_hub_kusama_latest: Location = dot_at_asset_hub_kusama.try_into().unwrap(); + let amount_to_send = ASSET_HUB_POLKADOT_ED * 1_000; + send_asset_from_asset_hub_kusama_to_asset_hub_polkadot( + dot_at_asset_hub_kusama_latest.clone(), + amount_to_send, + ); + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubPolkadot, + vec![ + // DOT is withdrawn from AHK's SA on AHP + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_ahk_on_ahp, + amount: *amount == amount_to_send, + }, + // DOTs deposited to beneficiary + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == AssetHubPolkadotReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_dots_after = AssetHubKusama::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaSender::get()) + }); + let receiver_dots_after = + ::account_data_of(AssetHubPolkadotReceiver::get()).free; + let dots_in_reserve_on_ahp_after = + ::account_data_of(sov_ahk_on_ahp).free; + + // Sender's balance is reduced + assert!(sender_dots_before >= sender_dots_after + amount_to_send); + // Receiver's balance is increased + assert!(receiver_dots_after > receiver_dots_before); + // Reserve balance is reduced by sent amount + assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before - amount_to_send); +} diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/Cargo.toml b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/Cargo.toml index c7e4151cee..3d93211300 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/Cargo.toml +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/Cargo.toml @@ -13,6 +13,7 @@ publish = false sp-runtime = { version = "32.0.0" } frame-support = { version = "29.0.0" } pallet-balances = { version = "29.0.0" } +pallet-asset-conversion = { version = "11.0.0" } pallet-assets = { version = "30.0.0" } pallet-message-queue = { version = "32.0.0" } diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs index 31eac92781..e82ecd4410 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/lib.rs @@ -32,7 +32,7 @@ pub use bp_messages::LaneId; // Cumulus pub use emulated_integration_tests_common::{ - accounts::ALICE, + accounts::{ALICE, BOB}, impls::Inspect, xcm_emulator::{ assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, diff --git a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs index 7d6e8053fa..31bcaa398e 100644 --- a/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs +++ b/integration-tests/emulated/tests/bridges/bridge-hub-polkadot/src/tests/asset_transfers.rs @@ -12,7 +12,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use crate::tests::*; +use frame_support::traits::fungible::Mutate; fn send_asset_from_asset_hub_polkadot_to_asset_hub_kusama(id: Location, amount: u128) { let destination = asset_hub_kusama_location(); @@ -176,3 +178,243 @@ fn send_ksms_from_asset_hub_polkadot_to_asset_hub_kusama() { // Reserve balance is reduced by sent amount assert_eq!(ksms_in_reserve_on_ahk_after, ksms_in_reserve_on_ahk_before - amount_to_send); } + +#[test] +fn send_dots_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { + let dot_at_asset_hub_polkadot: v3::Location = v3::Parent.into(); + let dot_at_asset_hub_kusama = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Polkadot)]); + let owner: AccountId = AssetHubKusama::account_id_of(BOB); + AssetHubKusama::force_create_foreign_asset( + dot_at_asset_hub_kusama, + owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahk_on_ahp = AssetHubPolkadot::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Kusama, + AssetHubKusama::para_id(), + ); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // setup a pool to pay xcm fees with `dot_at_asset_hub_kusama` tokens + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(owner.clone()), + dot_at_asset_hub_kusama, + owner.clone().into(), + 3_000_000_000_000, + )); + + ::Balances::set_balance(&owner, 3_000_000_000_000); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(owner.clone()), + Box::new(dot_at_asset_hub_polkadot), + Box::new(dot_at_asset_hub_kusama), + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(owner.clone()), + Box::new(dot_at_asset_hub_polkadot), + Box::new(dot_at_asset_hub_kusama), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); + + let dots_in_reserve_on_ahp_before = + ::account_data_of(sov_ahk_on_ahp.clone()).free; + let sender_dots_before = + ::account_data_of(AssetHubPolkadotSender::get()).free; + let receiver_dots_before = AssetHubKusama::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaReceiver::get()) + }); + + let dot_at_asset_hub_polkadot_latest: Location = dot_at_asset_hub_polkadot.try_into().unwrap(); + let amount = ASSET_HUB_POLKADOT_ED * 1_000; + send_asset_from_asset_hub_polkadot_to_asset_hub_kusama( + dot_at_asset_hub_polkadot_latest, + amount, + ); + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubKusama, + vec![ + // issue DOTs on AHK + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == dot_at_asset_hub_kusama, + owner: *owner == AssetHubKusamaReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_dots_after = + ::account_data_of(AssetHubPolkadotSender::get()).free; + let receiver_dots_after = AssetHubKusama::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(dot_at_asset_hub_kusama, &AssetHubKusamaReceiver::get()) + }); + let dots_in_reserve_on_ahp_after = + ::account_data_of(sov_ahk_on_ahp).free; + + // Sender's balance is reduced + assert!(sender_dots_before >= sender_dots_after + amount); + // Receiver's balance is increased + assert!(receiver_dots_after > receiver_dots_before); + // Reserve balance is increased by sent amount + assert_eq!(dots_in_reserve_on_ahp_after, dots_in_reserve_on_ahp_before + amount); +} + +#[test] +fn send_ksms_from_asset_hub_polkadot_to_asset_hub_kusama_fee_from_pool() { + let prefund_amount = 10_000_000_000_000u128; + let ksm_at_asset_hub_polkadot = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Kusama)]); + let owner: AccountId = AssetHubPolkadot::account_id_of(BOB); + AssetHubPolkadot::force_create_foreign_asset( + ksm_at_asset_hub_polkadot, + owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![(AssetHubPolkadotSender::get(), prefund_amount)], + ); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // setup a pool to pay xcm fees with `ksm_at_asset_hub_polkadot` tokens + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(owner.clone()), + ksm_at_asset_hub_polkadot, + owner.clone().into(), + 3_000_000_000_000, + )); + + ::Balances::set_balance( + &owner, + 3_000_000_000_000, + ); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(owner.clone()), + Box::new(xcm::v3::Parent.into()), + Box::new(ksm_at_asset_hub_polkadot), + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(owner.clone()), + Box::new(xcm::v3::Parent.into()), + Box::new(ksm_at_asset_hub_polkadot), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.clone() + )); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); + + // fund the AHP's SA on AHK with the KSM tokens held in reserve + let sov_ahp_on_ahk = AssetHubKusama::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Polkadot, + AssetHubPolkadot::para_id(), + ); + AssetHubKusama::fund_accounts(vec![(sov_ahp_on_ahk.clone(), prefund_amount)]); + + let ksms_in_reserve_on_ahk_before = + ::account_data_of(sov_ahp_on_ahk.clone()).free; + assert_eq!(ksms_in_reserve_on_ahk_before, prefund_amount); + let sender_ksms_before = AssetHubPolkadot::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotSender::get()) + }); + assert_eq!(sender_ksms_before, prefund_amount); + let receiver_ksms_before = + ::account_data_of(AssetHubKusamaReceiver::get()).free; + + let ksm_at_asset_hub_polkadot_latest: Location = ksm_at_asset_hub_polkadot.try_into().unwrap(); + let amount_to_send = ASSET_HUB_KUSAMA_ED * 1_000; + send_asset_from_asset_hub_polkadot_to_asset_hub_kusama( + ksm_at_asset_hub_polkadot_latest, + amount_to_send, + ); + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubKusama, + vec![ + // KSM is withdrawn from AHP's SA on AHK + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_ahp_on_ahk, + amount: *amount == amount_to_send, + }, + // KSMs deposited to beneficiary + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == AssetHubKusamaReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_ksms_after = AssetHubPolkadot::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(ksm_at_asset_hub_polkadot, &AssetHubPolkadotSender::get()) + }); + let receiver_ksms_after = + ::account_data_of(AssetHubKusamaReceiver::get()).free; + let ksms_in_reserve_on_ahk_after = + ::account_data_of(sov_ahp_on_ahk.clone()).free; + + // Sender's balance is reduced + assert!(sender_ksms_before >= sender_ksms_after + amount_to_send); + // Receiver's balance is increased + assert!(receiver_ksms_after > receiver_ksms_before); + // Reserve balance is reduced by sent amount + assert_eq!(ksms_in_reserve_on_ahk_after, ksms_in_reserve_on_ahk_before - amount_to_send); +} diff --git a/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs b/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs index 6898330ae5..41b793ab29 100644 --- a/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs @@ -288,6 +288,9 @@ impl pallet_assets::Config for Runtime { parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); + // Storage deposit for pool setup within asset conversion pallet + // and pool's lp token creation within assets pallet. + pub const PoolSetupFee: Balance = system_para_deposit(1, 4) + AssetDeposit::get(); } ord_parameter_types! { @@ -308,7 +311,7 @@ impl pallet_assets::Config for Runtime { type ForceOrigin = AssetsForceOrigin; // Deposits are zero because creation/admin is limited to Asset Conversion pallet. type AssetDeposit = ConstU128<0>; - type AssetAccountDeposit = ConstU128<0>; + type AssetAccountDeposit = AssetAccountDeposit; type MetadataDepositBase = ConstU128<0>; type MetadataDepositPerByte = ConstU128<0>; type ApprovalDeposit = ExistentialDeposit; @@ -354,9 +357,9 @@ impl pallet_asset_conversion::Config for Runtime { pallet_asset_conversion::WithFirstAsset; type PoolAssetId = u32; type PoolAssets = PoolAssets; - type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam + type PoolSetupFee = PoolSetupFee; type PoolSetupFeeAsset = KsmLocationV3; - type PoolSetupFeeTarget = ResolveAssetTo; + type PoolSetupFeeTarget = ResolveAssetTo; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type LPFee = ConstU32<3>; type PalletId = AssetConversionPalletId; diff --git a/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs b/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs index cb4a71067f..0c0eff4b07 100644 --- a/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs +++ b/system-parachains/asset-hubs/asset-hub-kusama/src/xcm_config.rs @@ -14,16 +14,22 @@ // limitations under the License. use super::{ - AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ParachainInfo, - ParachainSystem, PolkadotXcm, PoolAssets, PriceForParentDelivery, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, ToPolkadotXcmRouter, TrustBackedAssetsInstance, WeightToFee, - XcmpQueue, + AccountId, AllPalletsWithSystem, AssetConversion, Assets, Authorship, Balance, Balances, + CollatorSelection, NativeAndAssets, ParachainInfo, ParachainSystem, PolkadotXcm, PoolAssets, + PriceForParentDelivery, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, ToPolkadotXcmRouter, + TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; use crate::{ForeignAssets, ForeignAssetsInstance}; -use assets_common::matching::{FromSiblingParachain, IsForeignConcreteAsset}; +use assets_common::{ + matching::{FromSiblingParachain, IsForeignConcreteAsset}, + TrustBackedAssetsAsLocation, +}; use frame_support::{ parameter_types, - traits::{ConstU32, Contains, Equals, Everything, Nothing, PalletInfoAccess}, + traits::{ + tokens::imbalance::ResolveAssetTo, ConstU32, Contains, Equals, Everything, Nothing, + PalletInfoAccess, + }, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; @@ -76,6 +82,7 @@ parameter_types! { pub const FellowshipLocation: Location = Location::parent(); pub RelayTreasuryLocation: Location = (Parent, PalletInstance(kusama_runtime_constants::TREASURY_PALLET_ID)).into(); pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub StakingPot: AccountId = CollatorSelection::account_id(); // Test [`crate::tests::treasury_pallet_account_not_none`] ensures that the result of location // conversion is not `None`. pub RelayTreasuryPalletAccount: AccountId = @@ -533,6 +540,20 @@ impl xcm_executor::Config for XcmConfig { >; type Trader = ( UsingComponents>, + // This trader allows to pay with any assets exchangeable to KSM with + // [`AssetConversion`]. + cumulus_primitives_utility::SwapFirstAssetTrader< + KsmLocationV3, + AssetConversion, + WeightToFee, + NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + ), + ResolveAssetTo, + AccountId, + >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated // `pallet_assets` instance - `Assets`. cumulus_primitives_utility::TakeFirstAssetTrader< diff --git a/system-parachains/asset-hubs/asset-hub-kusama/tests/tests.rs b/system-parachains/asset-hubs/asset-hub-kusama/tests/tests.rs index 3c8c204a0a..f0a8bdd79e 100644 --- a/system-parachains/asset-hubs/asset-hub-kusama/tests/tests.rs +++ b/system-parachains/asset-hubs/asset-hub-kusama/tests/tests.rs @@ -20,35 +20,31 @@ use asset_hub_kusama_runtime::{ xcm_config::{ bridging::{self, XcmBridgeHubRouterFeeAssetId}, - AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, - ForeignCreatorsSovereignAccountOf, KsmLocation, LocationToAccountId, RelayTreasuryLocation, - RelayTreasuryPalletAccount, TrustBackedAssetsPalletLocation, XcmConfig, + CheckingAccount, ForeignCreatorsSovereignAccountOf, KsmLocation, LocationToAccountId, + RelayTreasuryLocation, RelayTreasuryPalletAccount, StakingPot, + TrustBackedAssetsPalletLocation, XcmConfig, }, - AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, - PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, + AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, ExistentialDeposit, + ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, ToPolkadotXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, SLOT_DURATION, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder, }; use codec::{Decode, Encode}; -use cumulus_primitives_utility::ChargeWeightInFungibles; -use frame_support::{ - assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, - weights::{Weight, WeightToFee as WeightToFeeT}, -}; +use frame_support::{assert_ok, traits::fungibles::InspectEnumerable}; use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use parachains_runtimes_test_utils::SlotDurations; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; +use sp_std::ops::Mul; use system_parachains_constants::kusama::{ consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, }; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; -use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; +use xcm_executor::traits::{ConvertLocation, JustTry}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; @@ -77,303 +73,54 @@ fn slot_durations() -> SlotDurations { } } -#[test] -fn test_ed_is_one_hundredth_of_relay() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - let relay_ed = kusama_runtime_constants::currency::EXISTENTIAL_DEPOSIT; - let asset_hub_ed = ExistentialDeposit::get(); - assert_eq!(relay_ed / 100, asset_hub_ed); - }); -} - -#[test] -fn test_asset_xcm_trader() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - let minimum_asset_balance = 3333333_u128; - let local_asset_id = 1; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - local_asset_id.into(), - AccountId::from(ALICE).into(), - true, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - local_asset_id.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - // get asset id as location - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 4e9 weight - let bought = Weight::from_parts(4_000_000_000u64, 0); - - // Lets calculate amount needed - let asset_amount_needed = - AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( - local_asset_id, - bought, - ) - .expect("failed to compute"); - - // Lets pay with: asset_amount_needed + asset_amount_extra - let asset_amount_extra = 100_u128; - let asset: Asset = - (asset_location.clone(), asset_amount_needed + asset_amount_extra).into(); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Lets buy_weight and make sure buy_weight does not return an error - let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); - // Check whether a correct amount of unused assets is returned - assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into())); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has received the amount - assert_eq!( - Assets::balance(local_asset_id, AccountId::from(ALICE)), - minimum_asset_balance + asset_amount_needed - ); - - // We also need to ensure the total supply increased - assert_eq!( - Assets::total_supply(local_asset_id), - minimum_asset_balance + asset_amount_needed - ); - }); -} - -#[test] -fn test_asset_xcm_trader_with_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 4e9 weight - let bought = Weight::from_parts(4_000_000_000u64, 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - // lets calculate amount needed - let amount_bought = WeightToFee::weight_to_fee(&bought); - - let asset: Asset = (asset_location.clone(), amount_bought).into(); - - // Make sure buy_weight does not return an error - assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); - - // Make sure again buy_weight does return an error - // This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader` - // tuple chain, which cannot be called twice - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // We actually use half of the weight - let weight_used = bought / 2; - - // Make sure refurnd works. - let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); - - assert_eq!( - trader.refund_weight(bought - weight_used, &ctx), - Some((asset_location, amount_refunded).into()) - ); - - // Drop trader - drop(trader); - - // We only should have paid for half of the bought weight - let fees_paid = WeightToFee::weight_to_fee(&weight_used); - - assert_eq!( - Assets::balance(1, AccountId::from(ALICE)), - ExistentialDeposit::get() + fees_paid - ); - - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid); - }); -} - -#[test] -fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy small amount - let bought = Weight::from_parts(50_000_000u64, 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - let asset: Asset = (asset_location, amount_bought).into(); - - // Buy weight should return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // not credited since the ED is higher than this value - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0); - - // We also need to ensure the total supply did not increase - assert_eq!(Assets::total_supply(1), 0); - }); -} - -#[test] -fn test_that_buying_ed_refund_does_not_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are gonna buy ED - let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - // We know we will have to buy at least ED, so lets make sure first it will - // fail with a payment of less than ED - let asset: Asset = (asset_location.clone(), amount_bought).into(); - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Now lets buy ED at least - let asset: Asset = (asset_location, ExistentialDeposit::get()).into(); - - // Buy weight should work - assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); - - // Should return None. We have a specific check making sure we dont go below ED for - // drop payment - assert_eq!(trader.refund_weight(bought, &ctx), None); +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); - // Drop trader - drop(trader); + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); - // Make sure author(Alice) has received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get()); + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2), + ); - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get()); - }); + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location, + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset), + Box::new(foreign_asset_id_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset), + Box::new(foreign_asset_id_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); } #[test] -fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { +fn test_ed_is_one_hundredth_of_relay() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -383,52 +130,9 @@ fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { )]) .build() .execute_with(|| { - // Create a non-sufficient asset with specific existential deposit - let minimum_asset_balance = 1_000_000_u128; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - false, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 4e9 weight - let bought = Weight::from_parts(4_000_000_000u64, 0); - - // lets calculate amount needed - let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - let asset: Asset = (asset_location, asset_amount_needed).into(); - - // Make sure again buy_weight does return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has NOT received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance); - - // We also need to ensure the total supply NOT increased - assert_eq!(Assets::total_supply(1), minimum_asset_balance); + let relay_ed = kusama_runtime_constants::currency::EXISTENTIAL_DEPOSIT; + let asset_hub_ed = ExistentialDeposit::get(); + assert_eq!(relay_ed / 100, asset_hub_ed); }); } @@ -674,7 +378,7 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p ); fn bridging_to_asset_hub_polkadot() -> TestBridgingConfig { - let _ = PolkadotXcm::force_xcm_version( + PolkadotXcm::force_xcm_version( RuntimeOrigin::root(), Box::new(bridging::to_polkadot::AssetHubPolkadot::get()), XCM_VERSION, @@ -721,6 +425,77 @@ fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_polkadot_works( ) } +#[test] +fn receive_reserve_asset_deposited_dot_from_asset_hub_polkadot_fees_paid_by_pool_swap_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Polkadot)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account, + // receiving DOTs + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_polkadot() + }, + ( + [PalletInstance(bp_bridge_hub_kusama::WITH_BRIDGE_KUSAMA_TO_POLKADOT_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Polkadot), + [Parachain(1000)].into() + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location, + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in DOTs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location, + &staking_pot + ), + 0 + ); + } + ) +} + #[test] fn receive_reserve_asset_deposited_dot_from_asset_hub_polkadot_fees_paid_by_sufficient_asset_works() { @@ -761,7 +536,7 @@ fn receive_reserve_asset_deposited_dot_from_asset_hub_polkadot_fees_paid_by_suff // check block author before assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location, &block_author_account ), 0 @@ -771,7 +546,7 @@ fn receive_reserve_asset_deposited_dot_from_asset_hub_polkadot_fees_paid_by_suff // `TakeFirstAssetTrader` puts fees to the block author assert!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location, &block_author_account ) > 0 ); diff --git a/system-parachains/asset-hubs/asset-hub-kusama/tests/weight_trader.rs b/system-parachains/asset-hubs/asset-hub-kusama/tests/weight_trader.rs new file mode 100644 index 0000000000..2bc64b545f --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-kusama/tests/weight_trader.rs @@ -0,0 +1,668 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for `WeighTrader` type of XCM Executor. + +use asset_hub_kusama_runtime::{ + xcm_config::{ + KsmLocation, KsmLocationV3, StakingPot, TrustBackedAssetsPalletLocationV3, XcmConfig, + }, + AllPalletsWithoutSystem, AssetConversion, Assets, Balances, ForeignAssets, Runtime, + SessionKeys, +}; +use asset_test_utils::ExtBuilder; +use assets_common::AssetIdForTrustBackedAssetsConvert; +use frame_support::{ + assert_ok, + traits::{ + fungible::{Inspect, Mutate}, + fungibles::{Create, Inspect as FungiblesInspect, Mutate as FungiblesMutate}, + }, + weights::{Weight, WeightToFee as WeightToFeeT}, +}; + +use asset_hub_kusama_runtime::{ + xcm_config::{ + AssetFeeAsExistentialDepositMultiplierFeeCharger, TrustBackedAssetsPalletLocation, + }, + ExistentialDeposit, +}; +use cumulus_primitives_utility::ChargeWeightInFungibles; +use frame_support::assert_noop; + +use parachains_common::{AccountId, AuraId}; +use sp_runtime::traits::MaybeEquivalence; +use system_parachains_constants::kusama::{currency::*, fee::WeightToFee}; +use xcm::latest::prelude::*; +use xcm_executor::traits::WeightTrader; + +const ALICE: [u8; 32] = [1u8; 32]; +const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; + +type RuntimeHelper = asset_test_utils::RuntimeHelper; + +type AssetIdForTrustBackedAssetsConvertLatest = + assets_common::AssetIdForTrustBackedAssetsConvertLatest; + +#[test] +fn test_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + let minimum_asset_balance = 3333333_u128; + let local_asset_id = 1; + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + local_asset_id.into(), + AccountId::from(ALICE).into(), + true, + minimum_asset_balance + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(Assets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + local_asset_id.into(), + AccountId::from(ALICE).into(), + minimum_asset_balance + )); + + // get asset id as location + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 4e9 weight + let bought = Weight::from_parts(4_000_000_000u64, 0); + + // Lets calculate amount needed + let asset_amount_needed = + AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( + local_asset_id, + bought, + ) + .expect("failed to compute"); + + // Lets pay with: asset_amount_needed + asset_amount_extra + let asset_amount_extra = 100_u128; + let asset: Asset = + (asset_location.clone(), asset_amount_needed + asset_amount_extra).into(); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Lets buy_weight and make sure buy_weight does not return an error + let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); + // Check whether a correct amount of unused assets is returned + assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into())); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has received the amount + assert_eq!( + Assets::balance(local_asset_id, AccountId::from(ALICE)), + minimum_asset_balance + asset_amount_needed + ); + + // We also need to ensure the total supply increased + assert_eq!( + Assets::total_supply(local_asset_id), + minimum_asset_balance + asset_amount_needed + ); + }); +} + +#[test] +fn test_asset_xcm_trader_with_refund() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + // We set existential deposit to be identical to the one for Balances first + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + true, + ExistentialDeposit::get() + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(Assets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + 1.into(), + AccountId::from(ALICE).into(), + ExistentialDeposit::get() + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 4e9 weight + let bought = Weight::from_parts(4_000_000_000u64, 0); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + // lets calculate amount needed + let amount_bought = WeightToFee::weight_to_fee(&bought); + + let asset: Asset = (asset_location.clone(), amount_bought).into(); + + // Make sure buy_weight does not return an error + assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); + + // Make sure again buy_weight does return an error + // This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader` + // tuple chain, which cannot be called twice + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // We actually use half of the weight + let weight_used = bought / 2; + + // Make sure refurnd works. + let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); + + assert_eq!( + trader.refund_weight(bought - weight_used, &ctx), + Some((asset_location, amount_refunded).into()) + ); + + // Drop trader + drop(trader); + + // We only should have paid for half of the bought weight + let fees_paid = WeightToFee::weight_to_fee(&weight_used); + + assert_eq!( + Assets::balance(1, AccountId::from(ALICE)), + ExistentialDeposit::get() + fees_paid + ); + + // We also need to ensure the total supply increased + assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid); + }); +} + +#[test] +fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + // We set existential deposit to be identical to the one for Balances first + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + true, + ExistentialDeposit::get() + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy small amount + let bought = Weight::from_parts(50_000_000u64, 0); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + let amount_bought = WeightToFee::weight_to_fee(&bought); + + assert!( + amount_bought < ExistentialDeposit::get(), + "we are testing what happens when the amount does not exceed ED" + ); + + let asset: Asset = (asset_location, amount_bought).into(); + + // Buy weight should return an error + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // not credited since the ED is higher than this value + assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0); + + // We also need to ensure the total supply did not increase + assert_eq!(Assets::total_supply(1), 0); + }); +} + +#[test] +fn test_that_buying_ed_refund_does_not_refund() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + // We set existential deposit to be identical to the one for Balances first + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + true, + ExistentialDeposit::get() + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are gonna buy ED + let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + let amount_bought = WeightToFee::weight_to_fee(&bought); + + assert!( + amount_bought < ExistentialDeposit::get(), + "we are testing what happens when the amount does not exceed ED" + ); + + // We know we will have to buy at least ED, so lets make sure first it will + // fail with a payment of less than ED + let asset: Asset = (asset_location.clone(), amount_bought).into(); + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // Now lets buy ED at least + let asset: Asset = (asset_location, ExistentialDeposit::get()).into(); + + // Buy weight should work + assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); + + // Should return None. We have a specific check making sure we dont go below ED for + // drop payment + assert_eq!(trader.refund_weight(bought, &ctx), None); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has received the amount + assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get()); + + // We also need to ensure the total supply increased + assert_eq!(Assets::total_supply(1), ExistentialDeposit::get()); + }); +} + +#[test] +fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // Create a non-sufficient asset with specific existential deposit + let minimum_asset_balance = 1_000_000_u128; + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + false, + minimum_asset_balance + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(Assets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + 1.into(), + AccountId::from(ALICE).into(), + minimum_asset_balance + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 4e9 weight + let bought = Weight::from_parts(4_000_000_000u64, 0); + + // lets calculate amount needed + let asset_amount_needed = WeightToFee::weight_to_fee(&bought); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + let asset: Asset = (asset_location, asset_amount_needed).into(); + + // Make sure again buy_weight does return an error + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has NOT received the amount + assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance); + + // We also need to ensure the total supply NOT increased + assert_eq!(Assets::total_supply(1), minimum_asset_balance); + }); +} + +#[test] +fn test_buy_and_refund_weight_with_native() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = StakingPot::get(); + let native_location = KsmLocation::get(); + let initial_balance = 200 * UNITS; + + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + // keep initial total issuance to assert later. + let total_issuance = Balances::total_issuance(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (native_location.clone(), fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = + unused_asset.fungible.get(&native_location.clone().into()).map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Balances::total_issuance(), total_issuance); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (native_location, refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!(Balances::total_issuance(), total_issuance + fee - refund); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = StakingPot::get(); + let asset_1: u32 = 1; + let native_location = KsmLocationV3::get(); + let asset_1_location = AssetIdForTrustBackedAssetsConvert::< + TrustBackedAssetsPalletLocationV3, + >::convert_back(&asset_1) + .unwrap(); + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create(asset_1, bob.clone(), true, 10)); + + assert_ok!(Assets::mint_into(asset_1, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = Assets::total_issuance(asset_1); + let native_total_issuance = Balances::total_issuance(); + + let asset_1_location_latest: Location = asset_1_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&asset_1_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, asset_1_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + Assets::total_issuance(asset_1), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = StakingPot::get(); + let native_location = KsmLocationV3::get(); + let foreign_location = xcm::v3::Location { + parents: 1, + interior: ( + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ) + .into(), + }; + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create( + foreign_location, + bob.clone(), + true, + 10 + )); + + assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = ForeignAssets::total_issuance(foreign_location); + let native_total_issuance = Balances::total_issuance(); + + let foreign_location_latest: Location = foreign_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (foreign_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&foreign_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee + ); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, foreign_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (foreign_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml b/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml index 4f3c38fe3f..9c395703b9 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml +++ b/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml @@ -31,7 +31,8 @@ frame-system = { default-features = false, version = "29.0.0" } frame-system-benchmarking = { default-features = false, optional = true, version = "29.0.0" } frame-system-rpc-runtime-api = { default-features = false, version = "27.0.0" } frame-try-runtime = { default-features = false, optional = true, version = "0.35.0" } -pallet-asset-tx-payment = { default-features = false , version = "29.0.0" } +pallet-asset-conversion-tx-payment = { default-features = false, version = "11.0.0" } +pallet-asset-conversion = { default-features = false, version = "11.0.0" } pallet-assets = { default-features = false, version = "30.0.0" } pallet-aura = { default-features = false, version = "28.0.0", features = ["experimental"] } pallet-authorship = { default-features = false, version = "29.0.0" } @@ -52,6 +53,7 @@ sp-block-builder = { default-features = false, version = "27.0.0" } sp-consensus-aura = { default-features = false, version = "0.33.0" } sp-core = { default-features = false, version = "29.0.0" } sp-genesis-builder = { default-features = false , version = "0.8.0" } +sp-io = { default-features = false , version = "31.0.0" } sp-inherents = { default-features = false, version = "27.0.0" } sp-offchain = { default-features = false, version = "27.0.0" } sp-runtime = { default-features = false, version = "32.0.0" } @@ -61,6 +63,8 @@ sp-storage = { default-features = false, version = "20.0.0" } sp-transaction-pool = { default-features = false, version = "27.0.0" } sp-version = { default-features = false, version = "30.0.0" } sp-weights = { default-features = false, version = "28.0.0" } +# num-traits feature needed for dex integer sq root: +primitive-types = { version = "0.12.2", default-features = false, features = ["codec", "scale-info", "num-traits"] } # Polkadot pallet-xcm = { default-features = false, version = "8.0.2" } @@ -95,7 +99,6 @@ pallet-xcm-bridge-hub-router = { default-features = false , version = "0.6.0" } hex-literal = "0.4.1" asset-test-utils = { version = "8.0.1" } parachains-runtimes-test-utils = { version = "8.0.0" } -sp-io = { version = "31.0.0" } [build-dependencies] substrate-wasm-builder = { optional = true , version = "18.0.0" } @@ -114,7 +117,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "hex-literal", - "pallet-asset-tx-payment/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", @@ -144,7 +147,8 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "frame-try-runtime/try-runtime", - "pallet-asset-tx-payment/try-runtime", + "pallet-asset-conversion-tx-payment/try-runtime", + "pallet-asset-conversion/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", @@ -190,7 +194,8 @@ std = [ "frame-try-runtime?/std", "kusama-runtime-constants/std", "log/std", - "pallet-asset-tx-payment/std", + "pallet-asset-conversion-tx-payment/std", + "pallet-asset-conversion/std", "pallet-assets/std", "pallet-aura/std", "pallet-authorship/std", @@ -223,6 +228,7 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", + "sp-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/impls.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/impls.rs new file mode 100644 index 0000000000..07d682f18a --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/impls.rs @@ -0,0 +1,74 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +// TODO: the types in the module copied from the PR: https://github.com/paritytech/polkadot-sdk/pull/3250 +// and should be removed when changes from the PR will get released. +pub(crate) mod pool { + use super::*; + use core::marker::PhantomData; + use pallet_asset_conversion::PoolLocator; + use sp_core::Get; + use sp_runtime::traits::{TrailingZeroInput, TryConvert}; + + /// Pool locator that mandates the inclusion of the specified `FirstAsset` in every asset pair. + /// + /// The `PoolId` is represented as a tuple of `AssetKind`s with `FirstAsset` always positioned + /// as the first element. + pub struct WithFirstAsset( + PhantomData<(FirstAsset, AccountId, AssetKind, AccountIdConverter)>, + ); + impl + PoolLocator + for WithFirstAsset + where + AssetKind: Eq + Clone + Encode, + AccountId: Decode, + FirstAsset: Get, + AccountIdConverter: for<'a> TryConvert<&'a (AssetKind, AssetKind), AccountId>, + { + fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> { + if asset1 == asset2 { + return Err(()); + } + let first = FirstAsset::get(); + if first == *asset1 { + Ok((first, asset2.clone())) + } else if first == *asset2 { + Ok((first, asset1.clone())) + } else { + Err(()) + } + } + fn address(id: &(AssetKind, AssetKind)) -> Result { + AccountIdConverter::try_convert(id).map_err(|_| ()) + } + } + + /// `PoolId` to `AccountId` conversion. + pub struct AccountIdConverter(PhantomData<(Seed, PoolId)>); + impl TryConvert<&PoolId, AccountId> for AccountIdConverter + where + PoolId: Encode, + AccountId: Decode, + Seed: Get, + { + fn try_convert(id: &PoolId) -> Result { + sp_io::hashing::blake2_256(&Encode::encode(&(Seed::get(), id))[..]) + .using_encoded(|e| Decode::decode(&mut TrailingZeroInput::new(e)).map_err(|_| id)) + } + } +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs index 0629d6cf00..bd6b7fc47b 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs @@ -59,20 +59,27 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +mod impls; mod weights; pub mod xcm_config; -use assets_common::{foreign_creators::ForeignCreators, matching::FromSiblingParachain}; +use assets_common::{ + foreign_creators::ForeignCreators, + local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, + matching::FromSiblingParachain, + AssetIdForTrustBackedAssetsConvert, +}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, ConstU128, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Verify}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, Verify}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, Perbill, + ApplyExtrinsicResult, Perbill, Permill, }; +use xcm_config::TrustBackedAssetsPalletLocationV3; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -86,8 +93,9 @@ use frame_support::{ genesis_builder_helper::{build_config, create_default_config}, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, - InstanceFilter, TransformOrigin, + fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, + ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, NeverEnsureOrigin, + TransformOrigin, }, weights::{ConstantMultiplier, Weight}, PalletId, @@ -98,10 +106,8 @@ use frame_system::{ }; use pallet_nfts::PalletFeatures; use parachains_common::{ - impls::{AssetsToBlockAuthor, DealWithFees}, - message_queue::*, - AccountId, AssetHubPolkadotAuraId as AuraId, AssetIdForTrustBackedAssets, Balance, BlockNumber, - Hash, Header, Nonce, Signature, + impls::DealWithFees, message_queue::*, AccountId, AssetHubPolkadotAuraId as AuraId, + AssetIdForTrustBackedAssets, Balance, BlockNumber, Hash, Header, Nonce, Signature, }; use sp_runtime::RuntimeDebug; @@ -112,9 +118,9 @@ use system_parachains_constants::{ }; use xcm::latest::prelude::{AssetId, BodyId}; use xcm_config::{ - DotLocation, FellowshipLocation, ForeignAssetsConvertedConcreteId, - ForeignCreatorsSovereignAccountOf, GovernanceLocation, TrustBackedAssetsConvertedConcreteId, - XcmOriginToTransactDispatchOrigin, + DotLocation, DotLocationV3, FellowshipLocation, ForeignAssetsConvertedConcreteId, + ForeignCreatorsSovereignAccountOf, GovernanceLocation, PoolAssetsConvertedConcreteId, + TrustBackedAssetsConvertedConcreteId, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -700,17 +706,13 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = weights::pallet_collator_selection::WeightInfo; } -impl pallet_asset_tx_payment::Config for Runtime { +impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Fungibles = Assets; - type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter< - pallet_assets::BalanceToAssetBalance< - Balances, - Runtime, - ConvertInto, - TrustBackedAssetsInstance, - >, - AssetsToBlockAuthor, + type Fungibles = LocalAndForeignAssets; + type OnChargeAssetTransaction = pallet_asset_conversion_tx_payment::AssetConversionAdapter< + Balances, + AssetConversion, + DotLocationV3, >; } @@ -815,6 +817,93 @@ impl pallet_xcm_bridge_hub_router::Config for Runtime type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; } +pub type PoolAssetsInstance = pallet_assets::Instance3; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = NeverEnsureOrigin; + type ForceOrigin = AssetsForceOrigin; + type AssetDeposit = ConstU128<0>; + type AssetAccountDeposit = AssetAccountDeposit; + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type ApprovalDeposit = ExistentialDeposit; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = weights::pallet_assets_pool::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +/// Union fungibles implementation for `Assets`` and `ForeignAssets`. +pub type LocalAndForeignAssets = fungibles::UnionOf< + Assets, + ForeignAssets, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v3::Location, + >, + xcm::v3::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. +pub type NativeAndAssets = fungible::UnionOf< + Balances, + LocalAndForeignAssets, + TargetFromLeft, + xcm::v3::Location, + AccountId, +>; + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); + // Storage deposit for pool setup within asset conversion pallet + // and pool's lp token creation within assets pallet. + pub const PoolSetupFee: Balance = system_para_deposit(1, 4) + AssetDeposit::get(); +} + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = xcm::v3::Location; + type Assets = NativeAndAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = impls::pool::WithFirstAsset< + DotLocationV3, + AccountId, + Self::AssetKind, + impls::pool::AccountIdConverter, + >; + type PoolAssetId = u32; + type PoolAssets = PoolAssets; + type PoolSetupFee = PoolSetupFee; + type PoolSetupFeeAsset = DotLocationV3; + type PoolSetupFeeTarget = ResolveAssetTo; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type LPFee = ConstU32<3>; + type PalletId = AssetConversionPalletId; + type MaxSwapPathLength = ConstU32<3>; + type MintMinLiquidity = ConstU128<100>; + type WeightInfo = weights::pallet_asset_conversion::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< + DotLocationV3, + parachain_info::Pallet, + xcm_config::TrustBackedAssetsPalletIndex, + Self::AssetKind, + >; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -829,7 +918,7 @@ construct_runtime!( // Monetary stuff. Balances: pallet_balances = 10, TransactionPayment: pallet_transaction_payment = 11, - AssetTxPayment: pallet_asset_tx_payment = 12, + AssetTxPayment: pallet_asset_conversion_tx_payment = 13, // Collator support. the order of these 5 are important and shall not change. Authorship: pallet_authorship = 20, @@ -858,6 +947,8 @@ construct_runtime!( Uniques: pallet_uniques = 51, Nfts: pallet_nfts = 52, ForeignAssets: pallet_assets:: = 53, + PoolAssets: pallet_assets:: = 54, + AssetConversion: pallet_asset_conversion = 55, } ); @@ -878,7 +969,7 @@ pub type SignedExtra = ( frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, - pallet_asset_tx_payment::ChargeAssetTxPayment, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -907,6 +998,8 @@ mod benches { [frame_system, SystemBench::] [pallet_assets, Local] [pallet_assets, Foreign] + [pallet_assets, Pool] + [pallet_asset_conversion, AssetConversion] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] @@ -1102,10 +1195,16 @@ impl_runtime_apis! { )?, // collect pallet_assets (ForeignAssets) convert::<_, _, _, _, ForeignAssetsConvertedConcreteId>( - ForeignAssets::account_balances(account) + ForeignAssets::account_balances(account.clone()) .iter() .filter(|(_, balance)| balance > &0) )?, + // collect pallet_assets (PoolAssets) + convert::<_, _, _, _, PoolAssetsConvertedConcreteId>( + PoolAssets::account_balances(account) + .iter() + .filter(|(_, balance)| balance > &0) + )?, // collect ... e.g. other tokens ].concat().into()) } @@ -1127,6 +1226,43 @@ impl_runtime_apis! { } } + impl pallet_asset_conversion::AssetConversionApi for Runtime { + fn quote_price_exact_tokens_for_tokens( + asset1: xcm::v3::Location, + asset2: xcm::v3::Location, + amount: Balance, + include_fee: bool, + ) -> Option { + AssetConversion::quote_price_exact_tokens_for_tokens( + asset1, + asset2, + amount, + include_fee, + ) + } + + fn quote_price_tokens_for_exact_tokens( + asset1: xcm::v3::Location, + asset2: xcm::v3::Location, + amount: Balance, + include_fee: bool, + ) -> Option { + AssetConversion::quote_price_tokens_for_exact_tokens( + asset1, + asset2, + amount, + include_fee, + ) + } + + fn get_reserves( + asset1: xcm::v3::Location, + asset2: xcm::v3::Location, + ) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(asset1, asset2).ok() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { @@ -1171,6 +1307,7 @@ impl_runtime_apis! { // `pallet_assets_local.rs / pallet_assets_foreign.rs`. type Local = pallet_assets::Pallet::; type Foreign = pallet_assets::Pallet::; + type Pool = pallet_assets::Pallet::; type ToKusama = XcmBridgeHubRouterBench; @@ -1478,6 +1615,7 @@ impl_runtime_apis! { type Local = pallet_assets::Pallet::; type Foreign = pallet_assets::Pallet::; + type Pool = pallet_assets::Pallet::; type ToKusama = XcmBridgeHubRouterBench; diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/mod.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/mod.rs index 58477b320a..e64eef645d 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/mod.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/mod.rs @@ -20,8 +20,10 @@ pub mod cumulus_pallet_parachain_system; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; +pub mod pallet_asset_conversion; pub mod pallet_assets_foreign; pub mod pallet_assets_local; +pub mod pallet_assets_pool; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_message_queue; diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/pallet_asset_conversion.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/pallet_asset_conversion.rs new file mode 100644 index 0000000000..97b959858f --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/pallet_asset_conversion.rs @@ -0,0 +1,156 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Autogenerated weights for `pallet_asset_conversion` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `ggwpez-ref-hw`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("../asset-hub-kusama-chain-spec.json")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot +// benchmark +// pallet +// --chain=../asset-hub-kusama-chain-spec.json +// --steps=50 +// --repeat=20 +// --pallet=pallet_asset_conversion +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./asset-hub-kusama-weights +// --header=./file_header.txt + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_conversion`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_conversion::WeightInfo for WeightInfo { + /// Storage: `AssetConversion::Pools` (r:1 w:1) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:1 w:1) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1) + /// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `499` + // Estimated: `6196` + // Minimum execution time: 74_492_000 picoseconds. + Weight::from_parts(77_141_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) + } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:2 w:2) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn add_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `1188` + // Estimated: `7404` + // Minimum execution time: 136_659_000 picoseconds. + Weight::from_parts(138_677_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) + } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:2 w:2) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn remove_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `1177` + // Estimated: `7404` + // Minimum execution time: 126_575_000 picoseconds. + Weight::from_parts(128_510_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(6)) + } + /// Storage: `ForeignAssets::Asset` (r:2 w:2) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:4 w:4) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + n * (557 ±0)` + // Estimated: `7404 + n * (393 ±92)` + // Minimum execution time: 930_000_000 picoseconds. + Weight::from_parts(960_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + // Standard Error: 17_993_720 + .saturating_add(Weight::from_parts(41_959_183, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) + } + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:2 w:2) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:4 w:4) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + n * (557 ±0)` + // Estimated: `7404 + n * (393 ±92)` + // Minimum execution time: 940_000_000 picoseconds. + Weight::from_parts(956_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + // Standard Error: 15_746_647 + .saturating_add(Weight::from_parts(39_193_877, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) + } +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/pallet_assets_pool.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/pallet_assets_pool.rs new file mode 100644 index 0000000000..922137b547 --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/weights/pallet_assets_pool.rs @@ -0,0 +1,530 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Autogenerated weights for `pallet_assets` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `ggwpez-ref-hw`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("../asset-hub-kusama-chain-spec.json")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot +// benchmark +// pallet +// --chain=../asset-hub-kusama-chain-spec.json +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./asset-hub-kusama-weights +// --header=./file_header.txt + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_assets`. +pub struct WeightInfo(PhantomData); +impl pallet_assets::WeightInfo for WeightInfo { + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3675` + // Minimum execution time: 10_327_000 picoseconds. + Weight::from_parts(10_767_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3675` + // Minimum execution time: 9_859_000 picoseconds. + Weight::from_parts(10_313_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn start_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `314` + // Estimated: `3675` + // Minimum execution time: 10_349_000 picoseconds. + Weight::from_parts(10_780_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1001 w:1000) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1000 w:1000) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 1000]`. + /// The range of component `c` is `[0, 1000]`. + /// The range of component `c` is `[0, 1000]`. + fn destroy_accounts(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `122 + c * (208 ±0)` + // Estimated: `3675 + c * (2609 ±0)` + // Minimum execution time: 14_416_000 picoseconds. + Weight::from_parts(14_691_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + // Standard Error: 7_257 + .saturating_add(Weight::from_parts(13_166_740, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2609).saturating_mul(c.into())) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Approvals` (r:1001 w:1000) + /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. + fn destroy_approvals(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `451 + a * (86 ±0)` + // Estimated: `3675 + a * (2623 ±0)` + // Minimum execution time: 14_333_000 picoseconds. + Weight::from_parts(14_651_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + // Standard Error: 4_812 + .saturating_add(Weight::from_parts(13_784_978, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2623).saturating_mul(a.into())) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Metadata` (r:1 w:0) + /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) + fn finish_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 11_770_000 picoseconds. + Weight::from_parts(12_178_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 20_274_000 picoseconds. + Weight::from_parts(21_083_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3675` + // Minimum execution time: 26_271_000 picoseconds. + Weight::from_parts(27_277_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `6208` + // Minimum execution time: 37_162_000 picoseconds. + Weight::from_parts(38_134_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `6208` + // Minimum execution time: 32_987_000 picoseconds. + Weight::from_parts(33_868_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `6208` + // Minimum execution time: 37_562_000 picoseconds. + Weight::from_parts(38_201_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn freeze() -> Weight { + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3675` + // Minimum execution time: 14_035_000 picoseconds. + Weight::from_parts(14_629_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn thaw() -> Weight { + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3675` + // Minimum execution time: 14_047_000 picoseconds. + Weight::from_parts(14_523_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn freeze_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `314` + // Estimated: `3675` + // Minimum execution time: 10_020_000 picoseconds. + Weight::from_parts(10_489_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn thaw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `314` + // Estimated: `3675` + // Minimum execution time: 10_016_000 picoseconds. + Weight::from_parts(10_344_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Metadata` (r:1 w:0) + /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 12_335_000 picoseconds. + Weight::from_parts(12_920_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 11_099_000 picoseconds. + Weight::from_parts(11_545_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Metadata` (r:1 w:1) + /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 11_847_000 picoseconds. + Weight::from_parts(12_587_711, 0) + .saturating_add(Weight::from_parts(0, 3675)) + // Standard Error: 202 + .saturating_add(Weight::from_parts(2_693, 0).saturating_mul(n.into())) + // Standard Error: 202 + .saturating_add(Weight::from_parts(2_968, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Metadata` (r:1 w:1) + /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `444` + // Estimated: `3675` + // Minimum execution time: 12_969_000 picoseconds. + Weight::from_parts(13_612_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Metadata` (r:1 w:1) + /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `119` + // Estimated: `3675` + // Minimum execution time: 11_159_000 picoseconds. + Weight::from_parts(11_752_476, 0) + .saturating_add(Weight::from_parts(0, 3675)) + // Standard Error: 192 + .saturating_add(Weight::from_parts(1_592, 0).saturating_mul(n.into())) + // Standard Error: 192 + .saturating_add(Weight::from_parts(2_277, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Metadata` (r:1 w:1) + /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) + fn force_clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `444` + // Estimated: `3675` + // Minimum execution time: 12_375_000 picoseconds. + Weight::from_parts(13_011_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn force_asset_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 10_409_000 picoseconds. + Weight::from_parts(10_977_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Approvals` (r:1 w:1) + /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `314` + // Estimated: `3675` + // Minimum execution time: 26_135_000 picoseconds. + Weight::from_parts(26_736_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Approvals` (r:1 w:1) + /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_approved() -> Weight { + // Proof Size summary in bytes: + // Measured: `597` + // Estimated: `6208` + // Minimum execution time: 55_219_000 picoseconds. + Weight::from_parts(56_400_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Approvals` (r:1 w:1) + /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `484` + // Estimated: `3675` + // Minimum execution time: 28_475_000 picoseconds. + Weight::from_parts(29_194_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Approvals` (r:1 w:1) + /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) + fn force_cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `484` + // Estimated: `3675` + // Minimum execution time: 28_943_000 picoseconds. + Weight::from_parts(29_674_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 11_309_000 picoseconds. + Weight::from_parts(11_601_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 15_731_000 picoseconds. + Weight::from_parts(16_413_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `280` + // Estimated: `3675` + // Minimum execution time: 15_119_000 picoseconds. + Weight::from_parts(15_668_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `406` + // Estimated: `3675` + // Minimum execution time: 14_162_000 picoseconds. + Weight::from_parts(14_911_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `439` + // Estimated: `3675` + // Minimum execution time: 13_897_000 picoseconds. + Weight::from_parts(14_365_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PoolAssets::Asset` (r:1 w:0) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3675` + // Minimum execution time: 14_060_000 picoseconds. + Weight::from_parts(14_496_000, 0) + .saturating_add(Weight::from_parts(0, 3675)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs index 5bd37b32db..a01d95ecca 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/xcm_config.rs @@ -14,16 +14,22 @@ // limitations under the License. use super::{ - AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ForeignAssets, - ParachainInfo, ParachainSystem, PolkadotXcm, PriceForParentDelivery, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, ToKusamaXcmRouter, TrustBackedAssetsInstance, WeightToFee, - XcmpQueue, + AccountId, AllPalletsWithSystem, AssetConversion, Assets, Authorship, Balance, Balances, + CollatorSelection, ForeignAssets, NativeAndAssets, ParachainInfo, ParachainSystem, PolkadotXcm, + PoolAssets, PriceForParentDelivery, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + ToKusamaXcmRouter, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; use crate::ForeignAssetsInstance; -use assets_common::matching::{FromSiblingParachain, IsForeignConcreteAsset}; +use assets_common::{ + matching::{FromSiblingParachain, IsForeignConcreteAsset}, + TrustBackedAssetsAsLocation, +}; use frame_support::{ parameter_types, - traits::{ConstU32, Contains, Equals, Everything, Nothing, PalletInfoAccess}, + traits::{ + tokens::imbalance::ResolveAssetTo, ConstU32, Contains, Equals, Everything, Nothing, + PalletInfoAccess, + }, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; @@ -57,18 +63,25 @@ use xcm_executor::{ parameter_types! { pub const DotLocation: Location = Location::parent(); + pub const DotLocationV3: xcm::v3::Location = xcm::v3::Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::Polkadot); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); + pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; pub TrustBackedAssetsPalletLocation: Location = - PalletInstance(::index() as u8).into(); + PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); pub FellowshipLocation: Location = Location::new(1, Parachain(system_parachain::COLLECTIVES_ID)); pub const GovernanceLocation: Location = Location::parent(); pub RelayTreasuryLocation: Location = (Parent, PalletInstance(polkadot_runtime_constants::TREASURY_PALLET_ID)).into(); pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub PoolAssetsPalletLocation: Location = + PalletInstance(::index() as u8).into(); + pub StakingPot: AccountId = CollatorSelection::account_id(); // Test [`crate::tests::treasury_pallet_account_not_none`] ensures that the result of location // conversion is not `None`. pub RelayTreasuryPalletAccount: AccountId = @@ -159,8 +172,30 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< CheckingAccount, >; +/// `AssetId`/`Balance` converter for `PoolAssets`. +pub type PoolAssetsConvertedConcreteId = + assets_common::PoolAssetsConvertedConcreteId; + +/// Means for transacting asset conversion pool assets on this chain. +pub type PoolFungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + PoolAssets, + // Use this currency when it is a fungible asset matching the given location or name: + PoolAssetsConvertedConcreteId, + // Convert an XCM `Location` into a local account ID: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We only want to allow teleports of known assets. We use non-zero issuance as an indication + // that this asset is known. + LocalMint>, + // The account to use for tracking teleports. + CheckingAccount, +>; + /// Means for transacting assets on this chain. -pub type AssetTransactors = (FungibleTransactor, FungiblesTransactor, ForeignFungiblesTransactor); +pub type AssetTransactors = + (FungibleTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor); /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can @@ -406,7 +441,8 @@ impl Contains for SafeCallFilter { pallet_uniques::Call::buy_item { .. } ) | RuntimeCall::ToKusamaXcmRouter( pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } - ) + ) | RuntimeCall::PoolAssets(..) | + RuntimeCall::AssetConversion(..) ) } } @@ -498,6 +534,20 @@ impl xcm_executor::Config for XcmConfig { >; type Trader = ( UsingComponents>, + // This trader allows to pay with any assets exchangeable to DOT with + // [`AssetConversion`]. + cumulus_primitives_utility::SwapFirstAssetTrader< + DotLocationV3, + AssetConversion, + WeightToFee, + NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + ), + ResolveAssetTo, + AccountId, + >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated // `pallet_assets` instance - `Assets`. cumulus_primitives_utility::TakeFirstAssetTrader< diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/tests/tests.rs b/system-parachains/asset-hubs/asset-hub-polkadot/tests/tests.rs index 08949a1157..946179403c 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/tests/tests.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/tests/tests.rs @@ -20,37 +20,33 @@ use asset_hub_polkadot_runtime::{ xcm_config::{ bridging::{self, XcmBridgeHubRouterFeeAssetId}, - AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, DotLocation, - ForeignCreatorsSovereignAccountOf, LocationToAccountId, RelayTreasuryLocation, - RelayTreasuryPalletAccount, TrustBackedAssetsPalletLocation, XcmConfig, + CheckingAccount, DotLocation, ForeignCreatorsSovereignAccountOf, LocationToAccountId, + RelayTreasuryLocation, RelayTreasuryPalletAccount, StakingPot, + TrustBackedAssetsPalletLocation, XcmConfig, }, - AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, - PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, + AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, ExistentialDeposit, + ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, ToKusamaXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, SLOT_DURATION, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder, }; use codec::{Decode, Encode}; -use cumulus_primitives_utility::ChargeWeightInFungibles; -use frame_support::{ - assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, - weights::{Weight, WeightToFee as WeightToFeeT}, -}; +use frame_support::{assert_ok, traits::fungibles::InspectEnumerable}; use parachains_common::{ AccountId, AssetHubPolkadotAuraId as AuraId, AssetIdForTrustBackedAssets, Balance, }; use parachains_runtimes_test_utils::SlotDurations; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; +use sp_std::ops::Mul; use system_parachains_constants::{ kusama::consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, polkadot::fee::WeightToFee, }; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; -use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; +use xcm_executor::traits::{ConvertLocation, JustTry}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; @@ -79,312 +75,54 @@ fn slot_durations() -> SlotDurations { } } -#[test] -fn test_ed_is_one_hundredth_of_relay() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - let relay_ed = polkadot_runtime_constants::currency::EXISTENTIAL_DEPOSIT; - let asset_hub_ed = ExistentialDeposit::get(); - assert_eq!(relay_ed / 100, asset_hub_ed); - }); -} - -#[test] -fn test_asset_xcm_trader() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - let minimum_asset_balance = 333333333_u128; - let local_asset_id = 1; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - local_asset_id.into(), - AccountId::from(ALICE).into(), - true, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - local_asset_id.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - // get asset id as location - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 400e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(400_000_000_000u64, 0); - - // Lets calculate amount needed - let asset_amount_needed = - AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( - local_asset_id, - bought, - ) - .expect("failed to compute"); - - // Lets pay with: asset_amount_needed + asset_amount_extra - let asset_amount_extra = 100_u128; - let asset: Asset = - (asset_location.clone(), asset_amount_needed + asset_amount_extra).into(); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Lets buy_weight and make sure buy_weight does not return an error - let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); - // Check whether a correct amount of unused assets is returned - assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into())); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has received the amount - assert_eq!( - Assets::balance(local_asset_id, AccountId::from(ALICE)), - minimum_asset_balance + asset_amount_needed - ); - - // We also need to ensure the total supply increased - assert_eq!( - Assets::total_supply(local_asset_id), - minimum_asset_balance + asset_amount_needed - ); - }); -} - -#[test] -fn test_asset_xcm_trader_with_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 400e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(400_000_000_000u64, 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - // lets calculate amount needed - let amount_bought = WeightToFee::weight_to_fee(&bought); - - let asset: Asset = (asset_location.clone(), amount_bought).into(); - - // Make sure buy_weight does not return an error - assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); - - // Make sure again buy_weight does return an error - // This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader` - // tuple chain, which cannot be called twice - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // We actually use half of the weight - let weight_used = bought / 2; - - // Make sure refurnd works. - let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); - - assert_eq!( - trader.refund_weight(bought - weight_used, &ctx), - Some((asset_location, amount_refunded).into()) - ); - - // Drop trader - drop(trader); - - // We only should have paid for half of the bought weight - let fees_paid = WeightToFee::weight_to_fee(&weight_used); - - assert_eq!( - Assets::balance(1, AccountId::from(ALICE)), - ExistentialDeposit::get() + fees_paid - ); - - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid); - }); -} - -#[test] -fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 50e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(5_000_000_000u64, 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - let asset: Asset = (asset_location, amount_bought).into(); - - // Buy weight should return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // not credited since the ED is higher than this value - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0); - - // We also need to ensure the total supply did not increase - assert_eq!(Assets::total_supply(1), 0); - }); -} - -#[test] -fn test_that_buying_ed_refund_does_not_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are gonna buy ED - let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - // We know we will have to buy at least ED, so lets make sure first it will - // fail with a payment of less than ED - let asset: Asset = (asset_location.clone(), amount_bought).into(); - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Now lets buy ED at least - let asset: Asset = (asset_location, ExistentialDeposit::get()).into(); - - // Buy weight should work - assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); - - // Should return None. We have a specific check making sure we dont go below ED for - // drop payment - assert_eq!(trader.refund_weight(bought, &ctx), None); +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); - // Drop trader - drop(trader); + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); - // Make sure author(Alice) has received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get()); + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2), + ); - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get()); - }); + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location, + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset), + Box::new(foreign_asset_id_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset), + Box::new(foreign_asset_id_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); } #[test] -fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { +fn test_ed_is_one_hundredth_of_relay() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -394,55 +132,9 @@ fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { )]) .build() .execute_with(|| { - // Create a non-sufficient asset - let minimum_asset_balance = 1_000_000_u128; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - false, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 400e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(400_000_000_000u64, 0); - - // lets calculate amount needed - let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - - let asset: Asset = (asset_location, asset_amount_needed).into(); - - // Make sure again buy_weight does return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has NOT received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance); - - // We also need to ensure the total supply NOT increased - assert_eq!(Assets::total_supply(1), minimum_asset_balance); + let relay_ed = polkadot_runtime_constants::currency::EXISTENTIAL_DEPOSIT; + let asset_hub_ed = ExistentialDeposit::get(); + assert_eq!(relay_ed / 100, asset_hub_ed); }); } @@ -688,7 +380,7 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p ); fn bridging_to_asset_hub_kusama() -> TestBridgingConfig { - let _ = PolkadotXcm::force_xcm_version( + PolkadotXcm::force_xcm_version( RuntimeOrigin::root(), Box::new(bridging::to_kusama::AssetHubKusama::get()), XCM_VERSION, @@ -735,6 +427,75 @@ fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_kusama_works() ) } +#[test] +fn receive_reserve_asset_deposited_ksm_from_asset_hub_kusama_fees_paid_by_pool_swap_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Kusama)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account.clone(), + // receiving KSMs + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_kusama() + }, + ( + [PalletInstance(bp_bridge_hub_polkadot::WITH_BRIDGE_POLKADOT_TO_KUSAMA_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Kusama), + [Parachain(1000)].into() + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location, + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in KSMs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location, + &staking_pot + ), + 0 + ); + } + ) +} + #[test] fn receive_reserve_asset_deposited_ksm_from_asset_hub_kusama_fees_paid_by_sufficient_asset_works() { const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; @@ -772,7 +533,7 @@ fn receive_reserve_asset_deposited_ksm_from_asset_hub_kusama_fees_paid_by_suffic // check block author before assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location, &block_author_account ), 0 @@ -782,7 +543,7 @@ fn receive_reserve_asset_deposited_ksm_from_asset_hub_kusama_fees_paid_by_suffic // `TakeFirstAssetTrader` puts fees to the block author assert!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location, &block_author_account ) > 0 ); diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/tests/weight_trader.rs b/system-parachains/asset-hubs/asset-hub-polkadot/tests/weight_trader.rs new file mode 100644 index 0000000000..e29fb78d63 --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/tests/weight_trader.rs @@ -0,0 +1,672 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for `WeighTrader` type of XCM Executor. + +use asset_hub_polkadot_runtime::{ + xcm_config::{ + AssetFeeAsExistentialDepositMultiplierFeeCharger, DotLocation, DotLocationV3, StakingPot, + TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, XcmConfig, + }, + AllPalletsWithoutSystem, AssetConversion, Assets, Balances, ExistentialDeposit, ForeignAssets, + Runtime, SessionKeys, +}; +use asset_test_utils::ExtBuilder; +use assets_common::AssetIdForTrustBackedAssetsConvert; +use cumulus_primitives_utility::ChargeWeightInFungibles; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungible::{Inspect, Mutate}, + fungibles::{Create, Inspect as FungiblesInspect, Mutate as FungiblesMutate}, + }, + weights::{Weight, WeightToFee as WeightToFeeT}, +}; +use parachains_common::{AccountId, AssetHubPolkadotAuraId as AuraId}; +use sp_runtime::traits::MaybeEquivalence; +use system_parachains_constants::polkadot::{currency::*, fee::WeightToFee}; +use xcm::latest::prelude::*; +use xcm_executor::traits::WeightTrader; + +const ALICE: [u8; 32] = [1u8; 32]; +const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; + +type RuntimeHelper = asset_test_utils::RuntimeHelper; + +type AssetIdForTrustBackedAssetsConvertLatest = + assets_common::AssetIdForTrustBackedAssetsConvertLatest; + +#[test] +fn test_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + let minimum_asset_balance = 333333333_u128; + let local_asset_id = 1; + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + local_asset_id.into(), + AccountId::from(ALICE).into(), + true, + minimum_asset_balance + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(Assets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + local_asset_id.into(), + AccountId::from(ALICE).into(), + minimum_asset_balance + )); + + // get asset id as location + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 400e9 weight + // Because of the ED being higher in kusama's asset hub + // and not to complicate things, we use a little + // bit more of weight + let bought = Weight::from_parts(400_000_000_000u64, 0); + + // Lets calculate amount needed + let asset_amount_needed = + AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( + local_asset_id, + bought, + ) + .expect("failed to compute"); + + // Lets pay with: asset_amount_needed + asset_amount_extra + let asset_amount_extra = 100_u128; + let asset: Asset = + (asset_location.clone(), asset_amount_needed + asset_amount_extra).into(); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Lets buy_weight and make sure buy_weight does not return an error + let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); + // Check whether a correct amount of unused assets is returned + assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into())); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has received the amount + assert_eq!( + Assets::balance(local_asset_id, AccountId::from(ALICE)), + minimum_asset_balance + asset_amount_needed + ); + + // We also need to ensure the total supply increased + assert_eq!( + Assets::total_supply(local_asset_id), + minimum_asset_balance + asset_amount_needed + ); + }); +} + +#[test] +fn test_asset_xcm_trader_with_refund() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + // We set existential deposit to be identical to the one for Balances first + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + true, + ExistentialDeposit::get() + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(Assets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + 1.into(), + AccountId::from(ALICE).into(), + ExistentialDeposit::get() + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 400e9 weight + // Because of the ED being higher in kusama's asset hub + // and not to complicate things, we use a little + // bit more of weight + let bought = Weight::from_parts(400_000_000_000u64, 0); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + // lets calculate amount needed + let amount_bought = WeightToFee::weight_to_fee(&bought); + + let asset: Asset = (asset_location.clone(), amount_bought).into(); + + // Make sure buy_weight does not return an error + assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); + + // Make sure again buy_weight does return an error + // This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader` + // tuple chain, which cannot be called twice + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // We actually use half of the weight + let weight_used = bought / 2; + + // Make sure refurnd works. + let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); + + assert_eq!( + trader.refund_weight(bought - weight_used, &ctx), + Some((asset_location, amount_refunded).into()) + ); + + // Drop trader + drop(trader); + + // We only should have paid for half of the bought weight + let fees_paid = WeightToFee::weight_to_fee(&weight_used); + + assert_eq!( + Assets::balance(1, AccountId::from(ALICE)), + ExistentialDeposit::get() + fees_paid + ); + + // We also need to ensure the total supply increased + assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid); + }); +} + +#[test] +fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + // We set existential deposit to be identical to the one for Balances first + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + true, + ExistentialDeposit::get() + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 50e9 weight + // Because of the ED being higher in kusama's asset hub + // and not to complicate things, we use a little + // bit more of weight + let bought = Weight::from_parts(5_000_000_000u64, 0); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + let amount_bought = WeightToFee::weight_to_fee(&bought); + + assert!( + amount_bought < ExistentialDeposit::get(), + "we are testing what happens when the amount does not exceed ED" + ); + + let asset: Asset = (asset_location, amount_bought).into(); + + // Buy weight should return an error + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // not credited since the ED is higher than this value + assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0); + + // We also need to ensure the total supply did not increase + assert_eq!(Assets::total_supply(1), 0); + }); +} + +#[test] +fn test_that_buying_ed_refund_does_not_refund() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + // We set existential deposit to be identical to the one for Balances first + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + true, + ExistentialDeposit::get() + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are gonna buy ED + let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + let amount_bought = WeightToFee::weight_to_fee(&bought); + + assert!( + amount_bought < ExistentialDeposit::get(), + "we are testing what happens when the amount does not exceed ED" + ); + + // We know we will have to buy at least ED, so lets make sure first it will + // fail with a payment of less than ED + let asset: Asset = (asset_location.clone(), amount_bought).into(); + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // Now lets buy ED at least + let asset: Asset = (asset_location, ExistentialDeposit::get()).into(); + + // Buy weight should work + assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); + + // Should return None. We have a specific check making sure we dont go below ED for + // drop payment + assert_eq!(trader.refund_weight(bought, &ctx), None); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has received the amount + assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get()); + + // We also need to ensure the total supply increased + assert_eq!(Assets::total_supply(1), ExistentialDeposit::get()); + }); +} + +#[test] +fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // Create a non-sufficient asset + let minimum_asset_balance = 1_000_000_u128; + assert_ok!(Assets::force_create( + RuntimeHelper::root_origin(), + 1.into(), + AccountId::from(ALICE).into(), + false, + minimum_asset_balance + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(Assets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + 1.into(), + AccountId::from(ALICE).into(), + minimum_asset_balance + )); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 400e9 weight + // Because of the ED being higher in kusama's asset hub + // and not to complicate things, we use a little + // bit more of weight + let bought = Weight::from_parts(400_000_000_000u64, 0); + + // lets calculate amount needed + let asset_amount_needed = WeightToFee::weight_to_fee(&bought); + + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + + let asset: Asset = (asset_location, asset_amount_needed).into(); + + // Make sure again buy_weight does return an error + assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has NOT received the amount + assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance); + + // We also need to ensure the total supply NOT increased + assert_eq!(Assets::total_supply(1), minimum_asset_balance); + }); +} + +#[test] +fn test_buy_and_refund_weight_with_native() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = StakingPot::get(); + let native_location = DotLocation::get(); + let initial_balance = 200 * UNITS; + + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + // keep initial total issuance to assert later. + let total_issuance = Balances::total_issuance(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (native_location.clone(), fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = + unused_asset.fungible.get(&native_location.clone().into()).map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Balances::total_issuance(), total_issuance); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (native_location, refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!(Balances::total_issuance(), total_issuance + fee - refund); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = StakingPot::get(); + let asset_1: u32 = 1; + let native_location = DotLocationV3::get(); + let asset_1_location = AssetIdForTrustBackedAssetsConvert::< + TrustBackedAssetsPalletLocationV3, + >::convert_back(&asset_1) + .unwrap(); + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create(asset_1, bob.clone(), true, 10)); + + assert_ok!(Assets::mint_into(asset_1, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = Assets::total_issuance(asset_1); + let native_total_issuance = Balances::total_issuance(); + + let asset_1_location_latest: Location = asset_1_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&asset_1_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, asset_1_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + Assets::total_issuance(asset_1), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = StakingPot::get(); + let native_location = DotLocationV3::get(); + let foreign_location = xcm::v3::Location { + parents: 1, + interior: ( + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ) + .into(), + }; + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create( + foreign_location, + bob.clone(), + true, + 10 + )); + + assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = ForeignAssets::total_issuance(foreign_location); + let native_total_issuance = Balances::total_issuance(); + + let foreign_location_latest: Location = foreign_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (foreign_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&foreign_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee + ); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, foreign_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (foreign_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +}