diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index eef3081f5a8c2..1a88257219437 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -13,8 +13,19 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - imports::*, - tests::{penpal_emulated_chain::penpal_runtime, snowbridge_common::snowbridge_sovereign}, + imports::{ + penpal_emulated_chain::penpal_runtime::xcm_config::{ + CheckingAccount, TELEPORTABLE_ASSET_ID, + }, + *, + }, + tests::{ + penpal_emulated_chain::penpal_runtime, + snowbridge_common::{ + bridged_roc_at_ah_westend, ethereum, register_roc_on_bh, snowbridge_sovereign, + }, + snowbridge_v2_outbound_from_rococo::create_foreign_on_ah_westend, + }, }; use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; use bridge_hub_westend_runtime::{ @@ -22,11 +33,11 @@ use bridge_hub_westend_runtime::{ }; use codec::{Decode, Encode}; use emulated_integration_tests_common::{PENPAL_B_ID, RESERVABLE_ASSET_ID}; -use frame_support::pallet_prelude::TypeInfo; +use frame_support::{pallet_prelude::TypeInfo, traits::fungibles::Mutate}; use hex_literal::hex; use rococo_westend_system_emulated_network::{ asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner, - penpal_emulated_chain::PARA_ID_B, + penpal_emulated_chain::PARA_ID_B, westend_emulated_chain::westend_runtime::Dmp, }; use snowbridge_core::{AssetMetadata, TokenIdOf}; use snowbridge_inbound_queue_primitives::{ @@ -211,6 +222,10 @@ fn send_weth_from_ethereum_to_penpal() { ); PenpalB::execute_with(|| { + let key = PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(); + let value = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(); + assert_eq!(key, hex!("770800eb78be69c327d8334d09276072")); + assert_eq!(value, hex!("020109079edaa802")); assert_ok!(::System::set_storage( ::RuntimeOrigin::root(), vec![( @@ -1181,7 +1196,7 @@ fn transfer_ah_token() { [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], )); - assert_ok!(::PolkadotXcm::limited_reserve_transfer_assets( + assert_ok!(::PolkadotXcm::transfer_assets( RuntimeOrigin::signed(AssetHubWestendSender::get()), Box::new(VersionedLocation::from(ethereum_destination)), Box::new(beneficiary), @@ -1269,3 +1284,737 @@ fn transfer_ah_token() { ); }); } + +#[test] +fn transfer_penpal_native_asset() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let pal_at_asset_hub = Location::new(1, [Parachain(PenpalB::para_id().into())]); + + let pal_after_reanchored = Location::new( + 1, + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(PenpalB::para_id().into())], + ); + + let token_id = TokenIdOf::convert_location(&pal_after_reanchored).unwrap(); + + let asset_owner = PenpalAssetOwner::get(); + + AssetHubWestend::force_create_foreign_asset( + pal_at_asset_hub.clone(), + asset_owner.into(), + true, + 1, + vec![], + ); + + let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + AssetHubWestend::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(pal_at_asset_hub.clone())), + AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + }); + + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &PenpalBSender::get(), + INITIAL_FUND, + )); + }); + + // Send PAL to Ethereum + PenpalB::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + // DOT as fee + let assets = vec![ + // Should cover the bridge fee + Asset { id: AssetId(Location::parent()), fun: Fungible(3_000_000_000_000) }, + Asset { id: AssetId(Location::here()), fun: Fungible(TOKEN_AMOUNT) }, + ]; + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let destination = Location::new(1, [Parachain(AssetHubWestend::para_id().into())]); + + let custom_xcm_on_dest = Xcm::<()>(vec![DepositReserveAsset { + assets: Wild(AllOf { + id: AssetId(pal_at_asset_hub.clone()), + fun: WildFungibility::Fungible, + }), + dest: ethereum(), + xcm: vec![ + BuyExecution { + fees: Asset { + id: AssetId(pal_after_reanchored.clone()), + fun: Fungible(TOKEN_AMOUNT), + }, + weight_limit: Unlimited, + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ] + .into(), + }]); + + assert_ok!(::PolkadotXcm::transfer_assets_using_type_and_then( + RuntimeOrigin::signed(PenpalBSender::get()), + Box::new(VersionedLocation::from(destination)), + Box::new(VersionedAssets::from(assets)), + Box::new(TransferType::Teleport), + Box::new(VersionedAssetId::from(AssetId(Location::parent()))), + Box::new(TransferType::DestinationReserve), + Box::new(VersionedXcm::from(custom_xcm_on_dest)), + Unlimited, + )); + + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned{ .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] + ); + }); + + // Send PAL back from Ethereum + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendNativeToken { + token_id, + destination: Destination::AccountId32 { id: AssetHubWestendSender::get().into() }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + // Convert the message to XCM + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + // Send the XCM + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned{..}) => {},] + ); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued{..}) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type RuntimeOrigin = ::RuntimeOrigin; + + let destination = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + + let beneficiary = + Location::new(0, [AccountId32 { network: None, id: PenpalBReceiver::get().into() }]); + + // DOT as fee + let assets = + vec![Asset { id: AssetId(pal_at_asset_hub.clone()), fun: Fungible(TOKEN_AMOUNT) }]; + + assert_ok!( + ::PolkadotXcm::limited_teleport_assets( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(VersionedLocation::from(destination)), + Box::new(VersionedLocation::from(beneficiary)), + Box::new(VersionedAssets::from(assets)), + 0, + Unlimited, + ) + ); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned{..}) => {},] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::Balances(pallet_balances::Event::Minted{..}) => {},] + ); + }) +} + +#[test] +fn transfer_penpal_teleport_enabled_asset() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + + let pal_at_asset_hub = Location::new(1, [Junction::Parachain(PenpalB::para_id().into())]) + .appended_with(asset_location_on_penpal.clone()) + .unwrap(); + + let pal_after_reanchored = Location::new( + 1, + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(PenpalB::para_id().into())], + ) + .appended_with(asset_location_on_penpal.clone()) + .unwrap(); + + let token_id = TokenIdOf::convert_location(&pal_after_reanchored).unwrap(); + + let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + AssetHubWestend::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(snowbridge_sovereign(), INITIAL_FUND)]); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(pal_at_asset_hub.clone())), + AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + }); + + // Fund on Penpal + PenpalB::fund_accounts(vec![(CheckingAccount::get(), INITIAL_FUND)]); + PenpalB::execute_with(|| { + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &PenpalBSender::get(), + INITIAL_FUND, + )); + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &PenpalBSender::get(), + INITIAL_FUND, + )); + }); + + // Send PAL to Ethereum + PenpalB::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + // DOT as fee + let assets = vec![ + // Should cover the bridge fee + Asset { id: AssetId(Location::parent()), fun: Fungible(3_000_000_000_000) }, + Asset { id: AssetId(asset_location_on_penpal.clone()), fun: Fungible(TOKEN_AMOUNT) }, + ]; + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let destination = Location::new(1, [Parachain(AssetHubWestend::para_id().into())]); + + let custom_xcm_on_dest = Xcm::<()>(vec![DepositReserveAsset { + assets: Wild(AllOf { + id: AssetId(pal_at_asset_hub.clone()), + fun: WildFungibility::Fungible, + }), + dest: ethereum(), + xcm: vec![ + BuyExecution { + fees: Asset { + id: AssetId(pal_after_reanchored.clone()), + fun: Fungible(TOKEN_AMOUNT), + }, + weight_limit: Unlimited, + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary }, + ] + .into(), + }]); + + assert_ok!(::PolkadotXcm::transfer_assets_using_type_and_then( + RuntimeOrigin::signed(PenpalBSender::get()), + Box::new(VersionedLocation::from(destination)), + Box::new(VersionedAssets::from(assets)), + Box::new(TransferType::Teleport), + Box::new(VersionedAssetId::from(AssetId(Location::parent()))), + Box::new(TransferType::DestinationReserve), + Box::new(VersionedXcm::from(custom_xcm_on_dest)), + Unlimited, + )); + + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned{ .. }) => {},] + ); + + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::Assets(pallet_assets::Event::Burned{ .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] + ); + }); + + // Send PAL back from Ethereum + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendNativeToken { + token_id, + destination: Destination::AccountId32 { id: AssetHubWestendSender::get().into() }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + // Convert the message to XCM + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + // Send the XCM + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => + {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned{..}) => {},] + ); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued{..}) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type RuntimeOrigin = ::RuntimeOrigin; + + let destination = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + + let beneficiary = + Location::new(0, [AccountId32 { network: None, id: PenpalBReceiver::get().into() }]); + + // DOT as fee + let assets = vec![ + Asset { id: AssetId(Location::parent()), fun: Fungible(XCM_FEE) }, + Asset { id: AssetId(pal_at_asset_hub.clone()), fun: Fungible(TOKEN_AMOUNT) }, + ]; + + let custom_xcm_on_dest = Xcm::<()>(vec![ + BuyExecution { + fees: Asset { id: AssetId(Location::parent()), fun: Fungible(XCM_FEE) }, + weight_limit: Unlimited, + }, + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + + assert_ok!( + ::PolkadotXcm::transfer_assets_using_type_and_then( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(VersionedLocation::from(destination)), + Box::new(VersionedAssets::from(assets)), + Box::new(TransferType::Teleport), + Box::new(VersionedAssetId::from(AssetId(Location::parent()))), + Box::new(TransferType::LocalReserve), + Box::new(VersionedXcm::from(custom_xcm_on_dest)), + Unlimited, + ) + ); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned{..}) => {},] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::Assets(pallet_assets::Event::Issued{..}) => {},] + ); + }) +} + +#[test] +fn mint_native_asset_on_penpal_from_relay_chain() { + // Send XCM message from Relay Chain to Penpal + Westend::execute_with(|| { + Dmp::make_parachain_reachable(PenpalB::para_id()); + // Set balance call + let mint_token_call = hex!("0a0800d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d0f00406352bfc601"); + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Superuser, + fallback_max_weight: None, + call: mint_token_call.to_vec().into(), + }, + ])); + assert_ok!(::XcmPallet::send( + ::RuntimeOrigin::root(), + bx!(VersionedLocation::from(Location::new(0, [Parachain(PenpalB::para_id().into())]))), + bx!(remote_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + // Check that the Transact message was sent + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { + .. + }) => {}, + ] + ); + }); +} + +pub(crate) fn set_up_pool_with_wnd_on_ah_westend( + asset: Location, + is_foreign: bool, + initial_fund: u128, + initial_liquidity: u128, +) { + let wnd: Location = Parent.into(); + AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get(), initial_fund)]); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let owner = AssetHubWestendSender::get(); + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + if is_foreign { + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset.clone().into(), + owner.clone().into(), + initial_fund, + )); + } else { + let asset_id = match asset.interior.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + assert_ok!(::Assets::mint( + signed_owner.clone(), + asset_id.into(), + owner.clone().into(), + initial_fund, + )); + } + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + initial_liquidity, + initial_liquidity, + 1, + 1, + owner.into() + )); + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} + +#[test] +fn transfer_roc_from_ah_with_legacy_api_will_fail() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let ethereum_sovereign: AccountId = snowbridge_sovereign(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); + + create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true); + + let asset_id: Location = bridged_roc_at_asset_hub_westend.clone(); + + let initial_fund: u128 = 200_000_000_000_000; + let initial_liquidity: u128 = initial_fund / 2; + // Setup pool and add liquidity + set_up_pool_with_wnd_on_ah_westend( + bridged_roc_at_asset_hub_westend.clone(), + true, + initial_fund, + initial_liquidity, + ); + + register_roc_on_bh(); + + // Send token to Ethereum + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + // Send partial of the token, will fail if send all + let assets = + vec![Asset { id: AssetId(asset_id.clone()), fun: Fungible(initial_fund / 10) }]; + let versioned_assets = VersionedAssets::from(Assets::from(assets)); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let result = ::PolkadotXcm::transfer_assets( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(VersionedLocation::from(ethereum_destination)), + Box::new(beneficiary), + Box::new(versioned_assets), + 0, + Unlimited, + ); + + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [21, 0, 0, 0], + message: Some("InvalidAssetUnknownReserve") + }) + ); + }); +} + +#[test] +fn transfer_roc_from_ah_with_transfer_and_then() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let ethereum_sovereign: AccountId = snowbridge_sovereign(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); + + create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true); + + let asset_id: Location = bridged_roc_at_asset_hub_westend.clone(); + + let initial_fund: u128 = 200_000_000_000_000; + let initial_liquidity: u128 = initial_fund / 2; + // Setup pool and add liquidity + set_up_pool_with_wnd_on_ah_westend( + bridged_roc_at_asset_hub_westend.clone(), + true, + initial_fund, + initial_liquidity, + ); + + register_roc_on_bh(); + + // Send token to Ethereum + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + // Send partial of the token, will fail if send all + let asset = Asset { id: AssetId(asset_id.clone()), fun: Fungible(initial_fund / 10) }; + let assets = vec![asset.clone()]; + let versioned_assets = VersionedAssets::from(Assets::from(assets.clone())); + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let custom_xcm = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); + + assert_ok!(::PolkadotXcm::transfer_assets_using_type_and_then( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(VersionedLocation::from(ethereum_destination)), + Box::new(versioned_assets), + Box::new(TransferType::LocalReserve), + Box::new(VersionedAssetId::from(asset_id.clone())), + Box::new(TransferType::LocalReserve), + Box::new(VersionedXcm::from(custom_xcm)), + Unlimited, + )); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Transferred{ .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] + ); + }); + + // Send token back from Ethereum + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let asset_id_after_reanchor: Location = + Location::new(1, [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))]); + let token_id = TokenIdOf::convert_location(&asset_id_after_reanchor).unwrap(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendNativeToken { + token_id, + destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, + amount: initial_fund / 10, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued{..}) => {},] + ); + + let events = AssetHubWestend::events(); + + // Check that the native token burnt from reserved account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { owner, .. }) + if *owner == ethereum_sovereign.clone(), + )), + "token burnt from Ethereum sovereign account." + ); + + // Check that the token was minted to beneficiary + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { owner, .. }) + if *owner == AssetHubWestendReceiver::get() + )), + "Token minted to beneficiary." + ); + }); +} diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 78b59562772ee..50ee9cee032dd 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -1088,7 +1088,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 2f41c118d7a89..02e33a8894dd8 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -65,16 +65,16 @@ use testnet_parachains_constants::westend::currency::deposit; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, - AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, - DescribeAllTerminal, DescribeFamily, DescribeTerminus, EnsureXcmOrigin, - ExternalConsensusLocationsConverterFor, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NativeAsset, - NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, - TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + AsPrefixedGeneralIndex, ConvertedConcreteId, DescribeAllTerminal, DescribeFamily, + DescribeTerminus, EnsureXcmOrigin, ExternalConsensusLocationsConverterFor, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, + LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SingleAssetExchangeAdapter, + SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::{traits::JustTry, XcmExecutor}; @@ -248,6 +248,8 @@ pub type Barrier = TrailingSetTopicAsId<( // If the message is one that immediately attempts to pay for execution, then // allow it. AllowTopLevelPaidExecutionFrom, + // Parent and its pluralities (i.e. governance bodies) get free execution. + AllowExplicitUnpaidExecutionFrom<(ParentOrParentsExecutivePlurality,)>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, // HRMP notifications from the relay chain are OK. @@ -316,6 +318,10 @@ parameter_types! { /// /// By default, it is configured as a `SystemAssetHubLocation` and can be modified using `System::set_storage`. pub storage CustomizableAssetFromSystemAssetHub: Location = SystemAssetHubLocation::get(); + + pub const NativeAssetId: AssetId = AssetId(Location::here()); + pub const NativeAssetFilter: AssetFilter = Wild(AllOf { fun: WildFungible, id: NativeAssetId::get() }); + pub AssetHubTrustedTeleporter: (AssetFilter, Location) = (NativeAssetFilter::get(), SystemAssetHubLocation::get()); } /// Accepts asset with ID `AssetLocation` and is coming from `Origin` chain. @@ -336,8 +342,13 @@ pub type TrustedReserves = ( AssetsFrom, AssetPrefixFrom, ); -pub type TrustedTeleporters = - (AssetFromChain,); + +pub type TrustedTeleporters = ( + AssetFromChain, + // This is used in the `IsTeleporter` configuration, meaning it accepts + // native tokens teleported from Asset Hub. + xcm_builder::Case, +); /// Defines origin aliasing rules for this chain. /// @@ -389,6 +400,8 @@ impl xcm_executor::Config for XcmConfig { type Weigher = FixedWeightBounds; type Trader = ( UsingComponents>, + // Allow native asset to pay the execution fee + UsingComponents>, cumulus_primitives_utility::SwapFirstAssetTrader< RelayLocation, crate::AssetConversion, diff --git a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs index 94e90efb877cd..401a872067eae 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs @@ -46,6 +46,7 @@ pub fn get_penpal_chain_spec(id: ParaId, relay_chain: &str) -> GenericChainSpec ], Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), id, + Sr25519Keyring::Alice.to_account_id(), )) .build() } @@ -54,6 +55,7 @@ fn penpal_testnet_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, id: ParaId, + sudo_account: AccountId, ) -> serde_json::Value { serde_json::json!({ "balances": { @@ -86,7 +88,45 @@ fn penpal_testnet_genesis( "safeXcmVersion": Some(SAFE_XCM_VERSION), }, "sudo": { - "key": Some(Sr25519Keyring::Alice.to_account_id()), + "key": Some(sudo_account.clone()), + }, + "assets": { + "assets": vec![( + penpal_runtime::xcm_config::TELEPORTABLE_ASSET_ID, + sudo_account.clone(), // owner + false, // is_sufficient + penpal_runtime::EXISTENTIAL_DEPOSIT, + )], + "metadata": vec![( + penpal_runtime::xcm_config::TELEPORTABLE_ASSET_ID, + "pal-2".as_bytes().to_vec(), + "pal-2".as_bytes().to_vec(), + 12, + )], + "accounts": vec![( + penpal_runtime::xcm_config::TELEPORTABLE_ASSET_ID, + sudo_account.clone(), + penpal_runtime::EXISTENTIAL_DEPOSIT * 4096, + )] + }, + "foreignAssets": { + "assets": vec![( + penpal_runtime::xcm_config::RelayLocation::get(), + sudo_account.clone(), + true, + penpal_runtime::EXISTENTIAL_DEPOSIT + )], + "metadata": vec![( + penpal_runtime::xcm_config::RelayLocation::get(), + "relay".as_bytes().to_vec(), + "relay".as_bytes().to_vec(), + 12 + )], + "accounts": vec![( + penpal_runtime::xcm_config::RelayLocation::get(), + sudo_account.clone(), + penpal_runtime::EXISTENTIAL_DEPOSIT * 4096, + )] }, }) } diff --git a/prdoc/pr_8038.prdoc b/prdoc/pr_8038.prdoc new file mode 100644 index 0000000000000..4af0dbcf70f7f --- /dev/null +++ b/prdoc/pr_8038.prdoc @@ -0,0 +1,12 @@ +title: Fix penpal runtime +doc: +- audience: Runtime Dev + description: |- + Allow using Penpal native asset (PEN) for paying local fees and allow teleporting it from/to AH. + Also allow unpaid execution from relay chain for sudo calls. +crates: +- name: penpal-runtime + bump: patch +- name: polkadot-parachain-bin + bump: patch +