// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Pezkuwi. // Pezkuwi 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. // Pezkuwi 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 Pezkuwi. If not, see . //! Tests for making sure `TransferOverXcm::transfer` generates the correct message and sends it to //! the correct destination use super::{mock::*, *}; use crate::AliasesIntoAccountId32; use frame_support::{ assert_ok, parameter_types, traits::{fungible::Mutate, fungibles::Mutate as FungiblesMutate}, }; use xcm::{ latest::{InteriorLocation, Junctions::X2, Xcm}, v5::{AssetId, Location, Parent}, }; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; parameter_types! { pub SenderAccount: AccountId = AccountId::new([3u8; 32]); pub SenderLocationOnTarget: Location = Location::new( 1, X2([Teyrchain(MockRuntimeTeyrchainId::get().into()), AccountId32 { network: None, id: SenderAccount::get().into() }].into()), ); pub SenderAccountOnTarget: AccountId = SovereignAccountOf::convert_location(&SenderLocationOnTarget::get()).expect("can convert"); pub InteriorAccount: InteriorLocation = AccountId32 { id: SenderAccount::get().into(), network: None }.into(); pub Timeout: BlockNumber = 5; // 5 blocks } type TestTransferOverXcm = TransferOverXcm, TestTransferOverXcmHelper>; type TestTransferOverXcmHelper = TransferOverXcmHelper< TestMessageSender, TestQueryHandler, TestFeeManager, Timeout, AccountId, AssetKind, LocatableAssetKindConverter, AliasesIntoAccountId32, >; fn fungible_amount(asset: Asset) -> u128 { let Asset { id: _, ref fun } = asset; match fun { Fungible(fee) => *fee, NonFungible(_) => panic!("not fungible"), } } /// Scenario: /// Account #3 on the local chain, teyrchain 42, controls an amount of funds on teyrchain 2. /// [`TransferOverXcm::transfer`] creates the correct message for account #3 to pay another account, /// account #5, on teyrchain 1000, remotely, in the relay chains native token. #[test] fn transfer_over_xcm_works() { let recipient = AccountId::new([5u8; 32]); // transact the parents native asset on teyrchain 1000. let asset_kind = AssetKind { destination: (Parent, Teyrchain(1000)).into(), asset_id: RelayLocation::get().into(), }; let transfer_amount = INITIAL_BALANCE / 10; new_test_ext().execute_with(|| { // The teyrchain's native token mock::Assets::set_balance(0, &SenderAccountOnTarget::get(), INITIAL_BALANCE); // The relaychain's native token mock::Assets::set_balance(1, &SenderAccountOnTarget::get(), INITIAL_BALANCE); mock::Balances::set_balance(&SenderAccountOnTarget::get(), INITIAL_BALANCE); // Check starting balance assert_eq!(mock::Assets::balance(0, &recipient), 0); assert_eq!(mock::Assets::balance(1, &recipient), 0); let fee_asset = Asset { id: AssetId(RelayLocation::get()), fun: Fungible(1_000_000_000_000_u128) }; assert_ok!(TestTransferOverXcm::transfer( &SenderAccount::get(), &recipient, asset_kind.clone(), transfer_amount, Some(fee_asset.clone()) )); let expected_message = remote_transfer_xcm( recipient.clone(), (asset_kind.asset_id, transfer_amount).into(), fee_asset.clone().into(), ); assert_send_and_execute_msg(expected_message); assert_eq!(mock::Assets::balance(1, &recipient), transfer_amount); // The mock trader does not refund any weight. Hence, the balance is exactly the // initial amount minus what we withdrew for transferring and paying the remote fees. assert_eq!( mock::Assets::balance(1, &SenderAccountOnTarget::get()), INITIAL_BALANCE - transfer_amount - fungible_amount(fee_asset.into()) ); }); } #[test] fn sender_on_relative_to_asset_location_works() { let asset_kind = AssetKind { destination: (Parent, Teyrchain(1000)).into(), asset_id: RelayLocation::get().into(), }; let sender_on_remote = TestTransferOverXcmHelper::from_relative_to_asset_location( &SenderAccount::get(), asset_kind.clone(), ) .unwrap(); assert_eq!(sender_on_remote, SenderLocationOnTarget::get()); } fn assert_send_and_execute_msg(expected_message: Xcm<()>) { let expected_hash = fake_message_hash(&expected_message); assert_eq!( sent_xcm(), vec![((Parent, Teyrchain(1000)).into(), expected_message, expected_hash)] ); let (_, message, mut hash) = sent_xcm()[0].clone(); let message = Xcm::<::RuntimeCall>::from(message.clone()); // Execute message in teyrchain 1000 with our teyrchains's origin let origin = (Parent, Teyrchain(MockRuntimeTeyrchainId::get().into())); let _result = XcmExecutor::::prepare_and_execute( origin, message, &mut hash, Weight::MAX, Weight::zero(), ); } fn remote_transfer_xcm( recipient: AccountId, transfer_asset: Asset, fee_asset: Asset, ) -> Xcm { Xcm(vec![ // Change the origin to the local account on the target chain DescendOrigin(AccountId32 { id: SenderAccount::get().into(), network: None }.into()), WithdrawAsset(fee_asset.clone().into()), PayFees { asset: fee_asset.clone() }, SetAppendix(Xcm(vec![ ReportError(QueryResponseInfo { destination: (Parent, Teyrchain(MockRuntimeTeyrchainId::get().into())).into(), query_id: 1, max_weight: Weight::MAX, }), RefundSurplus, DepositAsset { assets: AssetFilter::Wild(WildAsset::All), beneficiary: SenderLocationOnTarget::get(), }, ])), TransferAsset { beneficiary: AccountId32 { network: None, id: recipient.clone().into() }.into(), assets: transfer_asset.into(), }, ]) }