1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
use crate::{
18
	xcm_mock::{parachain::PolkadotXcm, *},
19
	xcm_testing::{add_supported_asset, currency_to_asset, helpers::*},
20
};
21
use frame_support::assert_ok;
22
use sp_weights::Weight;
23
use xcm::{
24
	latest::prelude::{AccountId32, AccountKey20, Junction, Location, Parachain, WeightLimit},
25
	VersionedAssets, VersionedLocation,
26
};
27
use xcm_primitives::{split_location_into_chain_part_and_beneficiary, DEFAULT_PROOF_SIZE};
28
use xcm_simulator::TestExt;
29

            
30
// Send a relay asset (like DOT) to a parachain A
31
#[test]
32
1
fn receive_relay_asset_from_relay() {
33
1
	MockNet::reset();
34
1

            
35
1
	// Register relay asset using helper
36
1
	let relay_asset_id = register_relay_asset();
37
1

            
38
1
	// Send relay asset to parachain (keep original transfer logic for now)
39
1
	let dest: Location = AccountKey20 {
40
1
		network: None,
41
1
		key: PARAALICE,
42
1
	}
43
1
	.into();
44
1
	Relay::execute_with(|| {
45
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
46
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
47
1
			Box::new(Parachain(1).into()),
48
1
			Box::new(VersionedLocation::from(dest).clone()),
49
1
			Box::new(([], 123).into()),
50
1
			0,
51
1
			WeightLimit::Unlimited
52
1
		));
53
1
	});
54
1

            
55
1
	// Verify that parachain received the asset using helper
56
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 123);
57
1
}
58

            
59
// Send relay asset (like DOT) back from Parachain A to relaychain
60
#[test]
61
1
fn send_relay_asset_to_relay() {
62
1
	MockNet::reset();
63
1

            
64
1
	// Register relay asset using helper
65
1
	let relay_asset_id = register_relay_asset();
66
1

            
67
1
	// First send relay chain asset to Parachain
68
1
	let dest: Location = Junction::AccountKey20 {
69
1
		network: None,
70
1
		key: PARAALICE,
71
1
	}
72
1
	.into();
73
1
	Relay::execute_with(|| {
74
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
75
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
76
1
			Box::new(Parachain(1).into()),
77
1
			Box::new(VersionedLocation::from(dest).clone()),
78
1
			Box::new(([], 123).into()),
79
1
			0,
80
1
			WeightLimit::Unlimited
81
1
		));
82
1
	});
83
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 123);
84
1

            
85
1
	// Get initial relay balance
86
1
	let balance_before_sending = {
87
1
		let mut balance = 0;
88
1
		Relay::execute_with(|| {
89
1
			balance = RelayBalances::free_balance(&RELAYALICE);
90
1
		});
91
1
		balance
92
1
	};
93
1

            
94
1
	// Send relay asset back to relay using builder for custom params
95
1
	let dest = Location {
96
1
		parents: 1,
97
1
		interior: [AccountId32 {
98
1
			network: None,
99
1
			id: RELAYALICE.into(),
100
1
		}]
101
1
		.into(),
102
1
	};
103
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
104
1

            
105
1
	ParaA::execute_with(|| {
106
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(relay_asset_id), 123);
107
1
		assert_ok!(PolkadotXcm::transfer_assets(
108
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
109
1
			Box::new(VersionedLocation::from(chain_part)),
110
1
			Box::new(VersionedLocation::from(beneficiary)),
111
1
			Box::new(VersionedAssets::from(asset)),
112
1
			0,
113
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
114
1
		));
115
1
	});
116
1

            
117
1
	// Verify balances
118
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 0);
119
1
	Relay::execute_with(|| {
120
1
		assert!(RelayBalances::free_balance(&RELAYALICE) > balance_before_sending);
121
1
	});
122
1
}
123

            
124
#[test]
125
1
fn send_relay_asset_to_para_b() {
126
1
	MockNet::reset();
127
1

            
128
1
	// Register relay asset in both parachains
129
1
	let relay_asset_id = register_relay_asset();
130
1

            
131
1
	// Register relay asset in ParaB
132
1
	let source_location = parachain::AssetType::Xcm(xcm::v3::Location::parent());
133
1
	let asset_metadata = parachain::AssetMetadata {
134
1
		name: b"RelayToken".to_vec(),
135
1
		symbol: b"Relay".to_vec(),
136
1
		decimals: 12,
137
1
	};
138
1
	ParaB::execute_with(|| {
139
1
		assert_ok!(AssetManager::register_foreign_asset(
140
1
			parachain::RuntimeOrigin::root(),
141
1
			source_location.clone(),
142
1
			asset_metadata,
143
1
			1u128,
144
1
			true
145
1
		));
146
1
		assert_ok!(add_supported_asset(source_location.clone(), 0));
147
1
	});
148
1

            
149
1
	// Send relay asset to Para A first
150
1
	let dest: Location = Junction::AccountKey20 {
151
1
		network: None,
152
1
		key: PARAALICE,
153
1
	}
154
1
	.into();
155
1
	Relay::execute_with(|| {
156
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
157
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
158
1
			Box::new(Parachain(1).into()),
159
1
			Box::new(VersionedLocation::from(dest).clone()),
160
1
			Box::new(([], 123).into()),
161
1
			0,
162
1
			WeightLimit::Unlimited
163
1
		));
164
1
	});
165
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 123);
166
1

            
167
1
	// Send relay asset from Para A to Para B
168
1
	let dest = Location {
169
1
		parents: 1,
170
1
		interior: [
171
1
			Parachain(2),
172
1
			AccountKey20 {
173
1
				network: None,
174
1
				key: PARAALICE.into(),
175
1
			},
176
1
		]
177
1
		.into(),
178
1
	};
179
1
	let (chain_part, beneficiary) = split_location_into_chain_part_and_beneficiary(dest).unwrap();
180
1

            
181
1
	ParaA::execute_with(|| {
182
1
		let asset = currency_to_asset(parachain::CurrencyId::ForeignAsset(relay_asset_id), 100);
183
1
		assert_ok!(PolkadotXcm::transfer_assets(
184
1
			parachain::RuntimeOrigin::signed(PARAALICE.into()),
185
1
			Box::new(VersionedLocation::from(chain_part)),
186
1
			Box::new(VersionedLocation::from(beneficiary)),
187
1
			Box::new(VersionedAssets::from(vec![asset])),
188
1
			0,
189
1
			WeightLimit::Limited(Weight::from_parts(40000u64, DEFAULT_PROOF_SIZE))
190
1
		));
191
1
	});
192
1

            
193
1
	// Verify balances
194
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 23);
195
1
	ParaB::execute_with(|| {
196
1
		let account_id = parachain::AccountId::from(PARAALICE);
197
1
		assert_eq!(Assets::balance(relay_asset_id, &account_id), 100);
198
1
	});
199
1
}
200

            
201
#[test]
202
1
fn receive_relay_asset_with_trader() {
203
1
	MockNet::reset();
204
1

            
205
1
	// Use helper for high units_per_second registration
206
1
	let relay_asset_id = register_relay_asset_with_units_per_second(2_500_000_000_000);
207
1

            
208
1
	let dest: Location = Junction::AccountKey20 {
209
1
		network: None,
210
1
		key: PARAALICE,
211
1
	}
212
1
	.into();
213
1
	// We are sending 100 tokens from relay.
214
1
	// Amount spent in fees is Units per second * weight / 1_000_000_000_000 (weight per second)
215
1
	// weight is 4 since we are executing 4 instructions with a unitweightcost of 1.
216
1
	// Units per second should be 2_500_000_000_000_000
217
1
	// Therefore with no refund, we should receive 10 tokens less
218
1
	// Native trader fails for this, and we use the asset trader
219
1
	Relay::execute_with(|| {
220
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
221
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
222
1
			Box::new(Parachain(1).into()),
223
1
			Box::new(VersionedLocation::from(dest).clone()),
224
1
			Box::new(([], 100).into()),
225
1
			0,
226
1
			WeightLimit::Unlimited
227
1
		));
228
1
	});
229
1

            
230
1
	// Use assertion
231
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 90);
232
1
	ParaA::execute_with(|| {
233
1
		assert_eq!(Assets::balance(relay_asset_id, &Treasury::account_id()), 10);
234
1
	});
235
1
}
236

            
237
#[test]
238
1
fn error_when_not_paying_enough() {
239
1
	MockNet::reset();
240
1

            
241
1
	let relay_asset_id = register_relay_asset_with_units_per_second(2500000000000);
242
1

            
243
1
	let dest: Location = Junction::AccountKey20 {
244
1
		network: None,
245
1
		key: PARAALICE,
246
1
	}
247
1
	.into();
248
1

            
249
1
	// Initial state verification - should be zero
250
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 0);
251
1

            
252
1
	// We are sending 5 tokens from relay - not enough to pay fees
253
1
	// If we set the dest weight to be 1e7, we know the buy_execution will spend 1e7*1e6/1e12 = 10
254
1
	// Therefore with no refund, we should receive 10 tokens less
255
1
	Relay::execute_with(|| {
256
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
257
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
258
1
			Box::new(Parachain(1).into()),
259
1
			Box::new(VersionedLocation::from(dest).clone()),
260
1
			Box::new(([], 5).into()),
261
1
			0,
262
1
			WeightLimit::Unlimited
263
1
		));
264
1
	});
265
1

            
266
1
	// Use helper for assertion - amount not received as it is not paying enough
267
1
	assert_asset_balance(&PARAALICE, relay_asset_id, 0);
268
1
}
269

            
270
#[test]
271
1
fn receive_asset_with_no_sufficients_not_possible_if_non_existent_account() {
272
1
	MockNet::reset();
273
1

            
274
1
	let fresh_account = [2u8; 20];
275
1
	let relay_asset_id = register_relay_asset_non_sufficient();
276
1

            
277
1
	// Actually send relay asset to parachain - should fail for non-existent account
278
1
	let dest: Location = AccountKey20 {
279
1
		network: None,
280
1
		key: fresh_account,
281
1
	}
282
1
	.into();
283
1
	Relay::execute_with(|| {
284
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
285
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
286
1
			Box::new(Parachain(1).into()),
287
1
			Box::new(VersionedLocation::from(dest.clone()).clone().into()),
288
1
			Box::new(([], 123).into()),
289
1
			0,
290
1
			WeightLimit::Unlimited
291
1
		));
292
1
	});
293
1

            
294
1
	// parachain should not have received assets (non-sufficient asset to non-existent account)
295
1
	assert_asset_balance(&fresh_account, relay_asset_id, 0);
296
1

            
297
1
	// Fund fresh account with native tokens
298
1
	ParaA::execute_with(|| {
299
1
		let account_id = parachain::AccountId::from(fresh_account);
300
1
		let _ = parachain::Balances::force_set_balance(
301
1
			parachain::RuntimeOrigin::root(),
302
1
			account_id.into(),
303
1
			100,
304
1
		);
305
1
	});
306
1

            
307
1
	// Re-send tokens
308
1
	Relay::execute_with(|| {
309
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
310
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
311
1
			Box::new(Parachain(1).into()),
312
1
			Box::new(VersionedLocation::from(dest).clone()),
313
1
			Box::new(([], 123).into()),
314
1
			0,
315
1
			WeightLimit::Unlimited
316
1
		));
317
1
	});
318
1

            
319
1
	// parachain should have received assets (account now exists)
320
1
	assert_asset_balance(&fresh_account, relay_asset_id, 123);
321
1
}
322

            
323
#[test]
324
1
fn receive_assets_with_sufficients_true_allows_non_funded_account_to_receive_assets() {
325
1
	MockNet::reset();
326
1

            
327
1
	let fresh_account = [2u8; 20];
328
1
	let relay_asset_id = register_relay_asset(); // Uses sufficient=true by default
329
1

            
330
1
	// Actually send relay asset to parachain
331
1
	let dest: Location = AccountKey20 {
332
1
		network: None,
333
1
		key: fresh_account,
334
1
	}
335
1
	.into();
336
1
	Relay::execute_with(|| {
337
1
		assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets(
338
1
			relay_chain::RuntimeOrigin::signed(RELAYALICE),
339
1
			Box::new(Parachain(1).into()),
340
1
			Box::new(VersionedLocation::from(dest.clone()).clone().into()),
341
1
			Box::new(([], 123).into()),
342
1
			0,
343
1
			WeightLimit::Unlimited
344
1
		));
345
1
	});
346
1

            
347
1
	// parachain should have received assets (sufficient asset allows non-funded account)
348
1
	assert_asset_balance(&fresh_account, relay_asset_id, 123);
349
1
}