Skip to content

Commit 7cc79e7

Browse files
committed
fix cases when asset reserve is remote, add test remote-asset and teleported fee
1 parent 0be3ff7 commit 7cc79e7

2 files changed

Lines changed: 244 additions & 50 deletions

File tree

polkadot/xcm/pallet-xcm/src/lib.rs

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ impl<T: Config> Pallet<T> {
12951295
if fee_asset_item as usize >= assets.len() {
12961296
return Err(Error::<T>::Empty.into())
12971297
}
1298-
let fees = assets.swap_remove(fee_asset_item as usize);
1298+
let mut fees = assets.swap_remove(fee_asset_item as usize);
12991299
let fees_transfer_type = TransferType::determine_for::<T>(&fees, &dest)?;
13001300
let assets_transfer_type = if assets.is_empty() {
13011301
// Single asset to transfer (also used for fees).
@@ -1328,34 +1328,31 @@ impl<T: Config> Pallet<T> {
13281328
// BuyExecution on both chains. Split fees, and deposit half at assets-reserve chain
13291329
// and half at destination.
13301330
if let TransferType::RemoteReserve(assets_reserve) = assets_transfer_type {
1331-
let (fees_for_reserve, fees_for_dest) = Self::equal_split_asset(&fees)?;
1331+
// Halve amount of fees, each half will be sent to one chain.
1332+
Self::halve_fungible_asset(&mut fees)?;
13321333
// Halve weight limit again to be used for the two fees transfers.
13331334
let quarter_weight_limit = Self::halve_weight_limit(&weight_limit);
1334-
let context = T::UniversalLocation::get();
1335-
// Send half the `fees` to the Sovereign Account of `dest` on assets-reserve chain.
1336-
let assets_reserve_beneficiary = dest
1337-
.reanchored(&assets_reserve, context)
1338-
.map_err(|_| Error::<T>::CannotReanchor)?;
1339-
Self::prefund_transfer_fees(
1335+
// Send half the `fees` to `beneficiary` on assets-reserve chain.
1336+
Self::build_and_execute_xcm_transfer_type(
13401337
origin_location,
13411338
assets_reserve,
1342-
assets_reserve_beneficiary,
1343-
fees_for_reserve,
1339+
beneficiary,
1340+
vec![fees.clone()],
1341+
TransferType::determine_for::<T>(&fees, &assets_reserve)?,
1342+
fees.clone(),
13441343
quarter_weight_limit.clone(),
13451344
)?;
1346-
Self::prefund_transfer_fees(
1345+
// Send the other half of the `fees` to `beneficiary` on dest chain.
1346+
Self::build_and_execute_xcm_transfer_type(
13471347
origin_location,
13481348
dest,
13491349
beneficiary,
1350-
fees_for_dest,
1350+
vec![fees.clone()],
1351+
fees_transfer_type,
1352+
fees.clone(),
13511353
quarter_weight_limit,
13521354
)?;
13531355
} else {
1354-
if let TransferType::RemoteReserve(_fees_reserve) = fees_transfer_type {
1355-
// TODO: change beneficiary on `fee_reserve` to be SA-of-Here.
1356-
// But beneficiary on `dest` should stay unchanged...
1357-
// Or maybe not required, fees can be used from Holding register, we'll see :D
1358-
}
13591356
// execute fees transfer - have to do it separately than assets because of the
13601357
// different transfer type (different XCM program required)
13611358
Self::build_and_execute_xcm_transfer_type(
@@ -1418,22 +1415,6 @@ impl<T: Config> Pallet<T> {
14181415
)
14191416
}
14201417

1421-
/// Teleport or reserve transfer `fees` - not to be used by itself, it is a helper function for
1422-
/// prefunding fees for subsequent assets transfer.
1423-
///
1424-
/// Fees are allowed to be either teleported or reserve transferred.
1425-
fn prefund_transfer_fees(
1426-
_origin: impl Into<MultiLocation>,
1427-
_dest: MultiLocation,
1428-
_beneficiary: MultiLocation,
1429-
_asset: MultiAsset,
1430-
_weight_limit: WeightLimit,
1431-
) -> DispatchResult {
1432-
// let fee_transfer_type = TransferType::determine_for::<T>(&fee_asset, &dest)?;
1433-
// Ok(())
1434-
todo!()
1435-
}
1436-
14371418
fn build_and_execute_xcm_transfer_type(
14381419
origin: impl Into<MultiLocation>,
14391420
dest: MultiLocation,
@@ -1919,14 +1900,15 @@ impl<T: Config> Pallet<T> {
19191900
}
19201901
}
19211902

1922-
/// Split fungible `asset` in two equal `MultiAsset`s.
1923-
fn equal_split_asset(asset: &MultiAsset) -> Result<(MultiAsset, MultiAsset), Error<T>> {
1924-
let half_amount = match &asset.fun {
1925-
Fungible(amount) => amount.saturating_div(2),
1926-
NonFungible(_) => return Err(Error::<T>::InvalidAsset),
1927-
};
1928-
let half = MultiAsset { fun: Fungible(half_amount), id: asset.id };
1929-
Ok((half.clone(), half))
1903+
/// Halve `asset`s fungible amount.
1904+
pub(crate) fn halve_fungible_asset(asset: &mut MultiAsset) -> Result<(), Error<T>> {
1905+
match &mut asset.fun {
1906+
Fungible(amount) => {
1907+
*amount = amount.saturating_div(2);
1908+
Ok(())
1909+
},
1910+
NonFungible(_) => Err(Error::<T>::InvalidAsset),
1911+
}
19301912
}
19311913
}
19321914

polkadot/xcm/pallet-xcm/src/tests.rs

Lines changed: 221 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const ALICE: AccountId = AccountId::new([0u8; 32]);
3737
const BOB: AccountId = AccountId::new([1u8; 32]);
3838
const INITIAL_BALANCE: u128 = 100;
3939
const SEND_AMOUNT: u128 = 10;
40-
const FEE_AMOUNT: u128 = 1;
40+
const FEE_AMOUNT: u128 = 2;
4141

4242
#[test]
4343
fn report_outcome_notify_works() {
@@ -1045,9 +1045,9 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_works
10451045
assert_eq!(expected_asset.id, MultiLocation::here().into());
10461046

10471047
// reanchor according to test-case.
1048-
let mut expected_fee_on_reserve = assets.get(fee_index).unwrap().clone();
1049-
expected_fee_on_reserve.reanchor(&fee_reserve_location, context).unwrap();
10501048
let expected_dest_on_reserve = dest.reanchored(&fee_reserve_location, context).unwrap();
1049+
let mut expected_fee_on_reserve = expected_fee.clone();
1050+
expected_fee_on_reserve.reanchor(&fee_reserve_location, context).unwrap();
10511051
expected_fee.reanchor(&dest, context).unwrap();
10521052
expected_asset.reanchor(&dest, context).unwrap();
10531053

@@ -1213,9 +1213,9 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve
12131213
assert_eq!(expected_asset.id, foreign_asset_id_multilocation.into());
12141214

12151215
// reanchor according to test-case.
1216-
let mut expected_fee_on_reserve = assets.get(fee_index).unwrap().clone();
1217-
expected_fee_on_reserve.reanchor(&fee_reserve_location, context).unwrap();
12181216
let expected_dest_on_reserve = dest.reanchored(&fee_reserve_location, context).unwrap();
1217+
let mut expected_fee_on_reserve = expected_fee.clone();
1218+
expected_fee_on_reserve.reanchor(&fee_reserve_location, context).unwrap();
12191219
expected_fee.reanchor(&dest, context).unwrap();
12201220
expected_asset.reanchor(&dest, context).unwrap();
12211221

@@ -1632,7 +1632,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() {
16321632

16331633
/// Test `reserve_transfer_assets` with destination asset reserve and teleported fee.
16341634
///
1635-
/// Transferring native asset (local reserve) to `FOREIGN_ASSET_RESERVE_PARA_ID`. Using
1635+
/// Transferring foreign asset (destination reserve) to `FOREIGN_ASSET_RESERVE_PARA_ID`. Using
16361636
/// teleport-trusted USDT for fees.
16371637
///
16381638
/// ```nocompile
@@ -1816,11 +1816,223 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor
18161816

18171817
/// Test `reserve_transfer_assets` with remote asset reserve and teleported fee.
18181818
///
1819-
/// Asserts that the sender's balance is decreased and the beneficiary's balance
1820-
/// is increased. Verifies the correct message is sent and event is emitted.
1819+
/// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to `USDT_PARA_ID`. Using
1820+
/// teleport-trusted USDT for fees.
1821+
///
1822+
/// ```nocompile
1823+
/// | chain `A` | chain `C` | chain `B`
1824+
/// | Here (source) | FOREIGN_ASSET_RESERVE_PARA_ID | USDT_PARA_ID (destination)
1825+
/// | | `assets` reserve | `fees` (USDT) teleport-trust
1826+
/// |
1827+
/// | 1. `A` executes `InitiateTeleport(fees)` dest `C`
1828+
/// | \----------> `C` executes `ReceiveTeleportedAsset(fees), .., DepositAsset(fees)`
1829+
/// |
1830+
/// | 2. `A` executes `InitiateTeleport(fees)` dest `B`
1831+
/// | \-------------------------------------------------> `B` executes:
1832+
/// | `ReceiveTeleportedAsset(fees), .., DepositAsset(fees)`
1833+
/// |
1834+
/// | 3. `A` executes `InitiateReserveWithdraw(assets)` dest `B`
1835+
/// | --------------------------------------------------> `DepositAsset(assets)`
1836+
/// ```
18211837
#[test]
18221838
fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_works() {
1823-
// TODO
1839+
let usdt_chain = RelayLocation::get().pushed_with_interior(Parachain(USDT_PARA_ID)).unwrap();
1840+
let usdt_chain_sovereign_account = SovereignAccountOf::convert_location(&usdt_chain).unwrap();
1841+
let usdt_id_multilocation = usdt_chain;
1842+
let usdt_initial_local_amount = 42;
1843+
1844+
let reserve_location = RelayLocation::get()
1845+
.pushed_with_interior(Parachain(FOREIGN_ASSET_RESERVE_PARA_ID))
1846+
.unwrap();
1847+
let foreign_asset_id_multilocation =
1848+
reserve_location.pushed_with_interior(FOREIGN_ASSET_INNER_JUNCTION).unwrap();
1849+
let foreign_asset_initial_amount = 142;
1850+
let reserve_sovereign_account =
1851+
SovereignAccountOf::convert_location(&reserve_location).unwrap();
1852+
1853+
// transfer destination is USDT chain (foreign asset needs to go through its reserve chain)
1854+
let dest = usdt_chain;
1855+
let assets: MultiAssets = vec![
1856+
// USDT for fees (is sufficient on local chain too)
1857+
(usdt_id_multilocation, FEE_AMOUNT).into(),
1858+
// foreign asset to transfer (not used for fees) - remote reserve
1859+
(foreign_asset_id_multilocation, SEND_AMOUNT).into(),
1860+
]
1861+
.into();
1862+
let fee_index = 0;
1863+
let asset_index = 1;
1864+
1865+
let context = UniversalLocation::get();
1866+
let mut expected_fee = assets.get(fee_index).unwrap().clone();
1867+
let mut expected_asset = assets.get(asset_index).unwrap().clone();
1868+
1869+
// sanity check indices are still ok after sort() done by `vec![MultiAsset].into()`.
1870+
assert_eq!(expected_fee.id, usdt_id_multilocation.into());
1871+
assert_eq!(expected_asset.id, foreign_asset_id_multilocation.into());
1872+
1873+
// reanchor according to test-case.
1874+
let expected_dest_on_reserve = dest.reanchored(&reserve_location, context).unwrap();
1875+
let mut expected_fee_on_reserve = expected_fee.clone();
1876+
let mut expected_asset_on_reserve = expected_asset.clone();
1877+
expected_fee_on_reserve.reanchor(&reserve_location, context).unwrap();
1878+
expected_asset_on_reserve.reanchor(&reserve_location, context).unwrap();
1879+
expected_fee.reanchor(&dest, context).unwrap();
1880+
expected_asset.reanchor(&dest, context).unwrap();
1881+
1882+
// fees are split between the asset-reserve chain and the destination chain
1883+
crate::Pallet::<Test>::halve_fungible_asset(&mut expected_fee_on_reserve).unwrap();
1884+
crate::Pallet::<Test>::halve_fungible_asset(&mut expected_fee).unwrap();
1885+
1886+
let balances = vec![
1887+
(ALICE, INITIAL_BALANCE),
1888+
(usdt_chain_sovereign_account.clone(), INITIAL_BALANCE),
1889+
(reserve_sovereign_account.clone(), INITIAL_BALANCE),
1890+
];
1891+
let beneficiary: MultiLocation =
1892+
Junction::AccountId32 { network: None, id: ALICE.into() }.into();
1893+
let expected_beneficiary_on_reserve = beneficiary;
1894+
1895+
new_test_ext_with_balances(balances).execute_with(|| {
1896+
// create sufficient foreign asset USDT (0 total issuance)
1897+
assert_ok!(Assets::force_create(
1898+
RuntimeOrigin::root(),
1899+
usdt_id_multilocation,
1900+
BOB,
1901+
true,
1902+
1
1903+
));
1904+
// USDT should have been teleported/reserve-transferred in, but for this test we just
1905+
// mint it locally.
1906+
assert_ok!(Assets::mint(
1907+
RuntimeOrigin::signed(BOB),
1908+
usdt_id_multilocation,
1909+
ALICE,
1910+
usdt_initial_local_amount
1911+
));
1912+
// create non-sufficient foreign asset BLA (0 total issuance)
1913+
assert_ok!(Assets::force_create(
1914+
RuntimeOrigin::root(),
1915+
foreign_asset_id_multilocation,
1916+
BOB,
1917+
false,
1918+
1
1919+
));
1920+
// foreign asset BLA should have been teleported/reserve-transferred in, but for this test
1921+
// we just mint it locally.
1922+
assert_ok!(Assets::mint(
1923+
RuntimeOrigin::signed(BOB),
1924+
foreign_asset_id_multilocation,
1925+
ALICE,
1926+
foreign_asset_initial_amount
1927+
));
1928+
assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount);
1929+
assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE);
1930+
1931+
// do the transfer
1932+
assert_ok!(XcmPallet::limited_reserve_transfer_assets(
1933+
RuntimeOrigin::signed(ALICE),
1934+
Box::new(dest.into()),
1935+
Box::new(beneficiary.into()),
1936+
Box::new(assets.into()),
1937+
fee_index as u32,
1938+
Unlimited,
1939+
));
1940+
assert!(matches!(
1941+
last_event(),
1942+
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(_) })
1943+
));
1944+
// Alice native asset untouched
1945+
assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE);
1946+
// Alice spent USDT for fees
1947+
assert_eq!(
1948+
Assets::balance(usdt_id_multilocation, ALICE),
1949+
usdt_initial_local_amount - FEE_AMOUNT
1950+
);
1951+
// Alice transferred BLA
1952+
assert_eq!(
1953+
Assets::balance(foreign_asset_id_multilocation, ALICE),
1954+
foreign_asset_initial_amount - SEND_AMOUNT
1955+
);
1956+
// Verify balances of USDT reserve parachain
1957+
assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), INITIAL_BALANCE);
1958+
assert_eq!(Assets::balance(usdt_id_multilocation, usdt_chain_sovereign_account), 0);
1959+
// Verify balances of transferred-asset reserve parachain
1960+
assert_eq!(Balances::free_balance(reserve_sovereign_account.clone()), INITIAL_BALANCE);
1961+
assert_eq!(Assets::balance(foreign_asset_id_multilocation, reserve_sovereign_account), 0);
1962+
// Verify total and active issuance of USDT have decreased (teleported)
1963+
assert_eq!(
1964+
Assets::total_issuance(usdt_id_multilocation),
1965+
usdt_initial_local_amount - FEE_AMOUNT
1966+
);
1967+
assert_eq!(
1968+
Assets::active_issuance(usdt_id_multilocation),
1969+
usdt_initial_local_amount - FEE_AMOUNT
1970+
);
1971+
// Verify total and active issuance of foreign BLA asset have decreased (reserve-based
1972+
// (local-instance) asset was burned)
1973+
assert_eq!(
1974+
Assets::total_issuance(foreign_asset_id_multilocation),
1975+
foreign_asset_initial_amount - SEND_AMOUNT
1976+
);
1977+
assert_eq!(
1978+
Assets::active_issuance(foreign_asset_id_multilocation),
1979+
foreign_asset_initial_amount - SEND_AMOUNT
1980+
);
1981+
1982+
// Verify sent XCM program
1983+
assert_eq!(
1984+
sent_xcm(),
1985+
vec![
1986+
(
1987+
// first message is to prefund fees on `reserve`
1988+
reserve_location,
1989+
// fees are teleported to reserve chain
1990+
Xcm(vec![
1991+
ReceiveTeleportedAsset(expected_fee_on_reserve.clone().into()),
1992+
ClearOrigin,
1993+
buy_limited_execution(expected_fee_on_reserve.clone(), Unlimited),
1994+
DepositAsset {
1995+
assets: AllCounted(1).into(),
1996+
beneficiary: expected_beneficiary_on_reserve
1997+
},
1998+
])
1999+
),
2000+
(
2001+
// second message is to prefund fees on `dest`
2002+
dest,
2003+
// fees are teleported to destination chain
2004+
Xcm(vec![
2005+
ReceiveTeleportedAsset(expected_fee.clone().into()),
2006+
ClearOrigin,
2007+
buy_limited_execution(expected_fee.clone(), Unlimited),
2008+
DepositAsset { assets: AllCounted(1).into(), beneficiary },
2009+
])
2010+
),
2011+
(
2012+
// third message is to transfer/deposit foreign assets on `dest` by going
2013+
// through `reserve` while paying using prefunded (teleported above) fees
2014+
reserve_location,
2015+
Xcm(vec![
2016+
WithdrawAsset(expected_asset_on_reserve.into()),
2017+
ClearOrigin,
2018+
buy_limited_execution(expected_fee_on_reserve, Unlimited),
2019+
DepositReserveAsset {
2020+
assets: Wild(AllCounted(1)),
2021+
// final destination is `dest` as seen by `reserve`
2022+
dest: expected_dest_on_reserve,
2023+
// message sent onward to final `dest` to deposit/prefund fees
2024+
xcm: Xcm(vec![
2025+
buy_limited_execution(expected_fee, Unlimited),
2026+
DepositAsset { assets: AllCounted(1).into(), beneficiary }
2027+
])
2028+
}
2029+
])
2030+
)
2031+
]
2032+
);
2033+
let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1);
2034+
let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap();
2035+
});
18242036
}
18252037

18262038
/// Test local execution of XCM

0 commit comments

Comments
 (0)