git.delta.rocks / unique-network / refs/commits / bce2cc1e9ab8

difftreelog

Merge pull request #1003 from UniqueNetwork/feature/llxcm-qtz

Yaroslav Bolyukin2023-10-02parents: #22b45b5 #3107202.patch.diff
in: master
added `XcmTestHelper` + allow XCM Transact for Gov/Identity/System calls

14 files changed

modified.baedeker/.gitignorediffbeforeafterboth
1/.bdk-env1/.bdk-env
2/rewrites.jsonnet2/rewrites*.jsonnet
3/vendor3/vendor
4/baedeker-library4/baedeker-library
55!/rewrites.example.jsonnet
modified.github/workflows/xcm.ymldiffbeforeafterboth
36 uses: CertainLach/create-matrix-action@v436 uses: CertainLach/create-matrix-action@v4
37 id: create_matrix37 id: create_matrix
38 with:38 with:
39 matrix: |39 matrix: |
40 network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}40 network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
41 network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runtime_features {quartz-runtime}41 network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
42 network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}42 network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
4343
44 xcm:44 xcm:
45 needs: prepare-execution-marix45 needs: prepare-execution-marix
modifiedruntime/common/config/xcm/mod.rsdiffbeforeafterboth
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17use frame_support::{17use frame_support::{
18 traits::{Everything, Nothing, Get, ConstU32, ProcessMessageError},18 traits::{Everything, Nothing, Get, ConstU32, ProcessMessageError, Contains},
19 parameter_types,19 parameter_types,
20};20};
21use frame_system::EnsureRoot;21use frame_system::EnsureRoot;
162162
163pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;163pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
164
165pub struct XcmCallFilter;
166impl XcmCallFilter {
167 fn allow_gov_and_sys_call(call: &RuntimeCall) -> bool {
168 match call {
169 RuntimeCall::System(..) => true,
170
171 #[cfg(feature = "governance")]
172 RuntimeCall::Identity(..)
173 | RuntimeCall::Preimage(..)
174 | RuntimeCall::Democracy(..)
175 | RuntimeCall::Council(..)
176 | RuntimeCall::TechnicalCommittee(..)
177 | RuntimeCall::CouncilMembership(..)
178 | RuntimeCall::TechnicalCommitteeMembership(..)
179 | RuntimeCall::FellowshipCollective(..)
180 | RuntimeCall::FellowshipReferenda(..) => true,
181 _ => false,
182 }
183 }
184
185 fn allow_utility_call(call: &RuntimeCall) -> bool {
186 match call {
187 RuntimeCall::Utility(pallet_utility::Call::batch { calls, .. }) => {
188 calls.iter().all(Self::allow_gov_and_sys_call)
189 }
190 RuntimeCall::Utility(pallet_utility::Call::batch_all { calls, .. }) => {
191 calls.iter().all(Self::allow_gov_and_sys_call)
192 }
193 RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) => {
194 Self::allow_gov_and_sys_call(call)
195 }
196 RuntimeCall::Utility(pallet_utility::Call::dispatch_as { call, .. }) => {
197 Self::allow_gov_and_sys_call(call)
198 }
199 RuntimeCall::Utility(pallet_utility::Call::force_batch { calls, .. }) => {
200 calls.iter().all(Self::allow_gov_and_sys_call)
201 }
202 _ => false,
203 }
204 }
205}
206
207impl Contains<RuntimeCall> for XcmCallFilter {
208 fn contains(call: &RuntimeCall) -> bool {
209 Self::allow_gov_and_sys_call(call) || Self::allow_utility_call(call)
210 }
211}
164212
165pub struct XcmExecutorConfig<T>(PhantomData<T>);213pub struct XcmExecutorConfig<T>(PhantomData<T>);
166impl<T> xcm_executor::Config for XcmExecutorConfig<T>214impl<T> xcm_executor::Config for XcmExecutorConfig<T>
192 type UniversalAliases = Nothing;240 type UniversalAliases = Nothing;
193 type CallDispatcher = RuntimeCall;241 type CallDispatcher = RuntimeCall;
194
195 // Deny all XCM Transacts.
196 type SafeCallFilter = Nothing;242 type SafeCallFilter = XcmCallFilter;
197}243}
198244
199#[cfg(feature = "runtime-benchmarks")]245#[cfg(feature = "runtime-benchmarks")]
modifiedruntime/opal/src/xcm_barrier.rsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17use frame_support::traits::Everything;17use frame_support::{match_types, traits::Everything};
18use xcm::latest::{Junctions::*, MultiLocation};
18use xcm_builder::{AllowTopLevelPaidExecutionFrom, TakeWeightCredit};19use xcm_builder::{AllowTopLevelPaidExecutionFrom, TakeWeightCredit, AllowExplicitUnpaidExecutionFrom};
20
21match_types! {
22 pub type ParentOnly: impl Contains<MultiLocation> = {
23 MultiLocation { parents: 1, interior: Here }
24 };
25}
1926
20pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom<Everything>);27pub type Barrier = (
28 TakeWeightCredit,
29 AllowExplicitUnpaidExecutionFrom<ParentOnly>,
30 AllowTopLevelPaidExecutionFrom<Everything>,
31);
2132
modifiedruntime/quartz/src/xcm_barrier.rsdiffbeforeafterboth
18use xcm::latest::{Junctions::*, MultiLocation};18use xcm::latest::{Junctions::*, MultiLocation};
19use xcm_builder::{19use xcm_builder::{
20 AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit,20 AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit,
21 AllowTopLevelPaidExecutionFrom,21 AllowTopLevelPaidExecutionFrom, AllowExplicitUnpaidExecutionFrom,
22};22};
2323
24use crate::PolkadotXcm;24use crate::PolkadotXcm;
2525
26match_types! {26match_types! {
27 pub type ParentOnly: impl Contains<MultiLocation> = {
28 MultiLocation { parents: 1, interior: Here }
29 };
30
27 pub type ParentOrSiblings: impl Contains<MultiLocation> = {31 pub type ParentOrSiblings: impl Contains<MultiLocation> = {
28 MultiLocation { parents: 1, interior: Here } |32 MultiLocation { parents: 1, interior: Here } |
3236
33pub type Barrier = (37pub type Barrier = (
34 TakeWeightCredit,38 TakeWeightCredit,
39 AllowExplicitUnpaidExecutionFrom<ParentOnly>,
35 AllowTopLevelPaidExecutionFrom<Everything>,40 AllowTopLevelPaidExecutionFrom<Everything>,
36 // Expected responses are OK.41 // Expected responses are OK.
37 AllowKnownQueryResponses<PolkadotXcm>,42 AllowKnownQueryResponses<PolkadotXcm>,
modifiedruntime/unique/src/xcm_barrier.rsdiffbeforeafterboth
18use xcm::latest::{Junctions::*, MultiLocation};18use xcm::latest::{Junctions::*, MultiLocation};
19use xcm_builder::{19use xcm_builder::{
20 AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit,20 AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit,
21 AllowTopLevelPaidExecutionFrom,21 AllowTopLevelPaidExecutionFrom, AllowExplicitUnpaidExecutionFrom,
22};22};
2323
24use crate::PolkadotXcm;24use crate::PolkadotXcm;
2525
26match_types! {26match_types! {
27 pub type ParentOnly: impl Contains<MultiLocation> = {
28 MultiLocation { parents: 1, interior: Here }
29 };
30
27 pub type ParentOrSiblings: impl Contains<MultiLocation> = {31 pub type ParentOrSiblings: impl Contains<MultiLocation> = {
28 MultiLocation { parents: 1, interior: Here } |32 MultiLocation { parents: 1, interior: Here } |
3236
33pub type Barrier = (37pub type Barrier = (
34 TakeWeightCredit,38 TakeWeightCredit,
39 AllowExplicitUnpaidExecutionFrom<ParentOnly>,
35 AllowTopLevelPaidExecutionFrom<Everything>,40 AllowTopLevelPaidExecutionFrom<Everything>,
36 // Expected responses are OK.41 // Expected responses are OK.
37 AllowKnownQueryResponses<PolkadotXcm>,42 AllowKnownQueryResponses<PolkadotXcm>,
modifiedtests/package.jsondiffbeforeafterboth
117 "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",117 "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",
118 "testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts",118 "testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts",
119 "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",119 "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",
120 "testLowLevelXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/lowLevelXcmQuartz.test.ts",
121 "testFullXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Quartz.test.ts",
120 "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",122 "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",
121 "testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",123 "testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",
122 "testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",124 "testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
9import {IKeyringPair} from '@polkadot/types/types';9import {IKeyringPair} from '@polkadot/types/types';
10import {EventRecord} from '@polkadot/types/interfaces';10import {EventRecord} from '@polkadot/types/interfaces';
11import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';11import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';
12import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';12import {FrameSystemEventRecord, XcmV2TraitsError, XcmV3TraitsOutcome} from '@polkadot/types/lookup';
13import {SignerOptions, VoidFn} from '@polkadot/api/types';13import {SignerOptions, VoidFn} from '@polkadot/api/types';
14import {Pallets} from '..';14import {Pallets} from '..';
15import {spawnSync} from 'child_process';15import {spawnSync} from 'child_process';
261 }));261 }));
262 };262 };
263
264 static DmpQueue = class extends EventSection('dmpQueue') {
265 static ExecutedDownward = this.Method('ExecutedDownward', data => ({
266 outcome: eventData<XcmV3TraitsOutcome>(data, 1),
267 }));
268 };
263}269}
264270
265// eslint-disable-next-line @typescript-eslint/naming-convention271// eslint-disable-next-line @typescript-eslint/naming-convention
560 this.wait = new WaitGroup(this);566 this.wait = new WaitGroup(this);
561 }567 }
568
569 getSudo() {
570 // eslint-disable-next-line @typescript-eslint/naming-convention
571 const SudoHelperType = SudoHelper(this.helperBase);
572 return this.clone(SudoHelperType) as DevRelayHelper;
573 }
562}574}
563575
564export class DevWestmintHelper extends WestmintHelper {576export class DevWestmintHelper extends WestmintHelper {
969 };981 };
970 }982 }
983
984 makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {
985 return {
986 V3: [
987 {
988 UnpaidExecution: {
989 weightLimit: 'Unlimited',
990 checkOrigin: null,
991 },
992 },
993 {
994 Transact: {
995 originKind: 'Superuser',
996 requireWeightAtMost: {
997 refTime: info.weightMultiplier * 200000000,
998 proofSize: info.weightMultiplier * 3000,
999 },
1000 call: {
1001 encoded: info.call,
1002 },
1003 },
1004 },
1005 ],
1006 };
1007 }
971}1008}
9721009
973class MoonbeamAccountGroup {1010class MoonbeamAccountGroup {
modifiedtests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth
240}240}
241241
242export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {242export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
243 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {243 async create(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint) {
244 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);244 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
245 }245 }
246246
247 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {247 async setMetadata(signer: TSigner, assetId: number | bigint, name: string, symbol: string, decimals: number) {
248 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);248 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
249 }249 }
250250
251 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {251 async mint(signer: TSigner, assetId: number | bigint, beneficiary: string, amount: bigint) {
252 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);252 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
253 }253 }
254254
255 async account(assetId: string | number, address: string) {255 async account(assetId: string | number | bigint, address: string) {
256 const accountAsset = (256 const accountAsset = (
257 await this.helper.callRpc('api.query.assets.account', [assetId, address])257 await this.helper.callRpc('api.query.assets.account', [assetId, address])
258 ).toJSON()! as any;258 ).toJSON()! as any;
addedtests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth

no changes

modifiedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
1616
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import config from '../config';18import config from '../config';
19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';19import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';
20import {Event} from '../util/playgrounds/unique.dev';
21import {nToBigInt} from '@polkadot/util';20import {nToBigInt} from '@polkadot/util';
22import {hexToString} from '@polkadot/util';21import {hexToString} from '@polkadot/util';
23import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';22import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
2423
24const testHelper = new XcmTestHelper('unique');
2525
26const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
27const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
28const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
29const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
30const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
3126
32let balanceUniqueTokenInit: bigint;
33let balanceUniqueTokenMiddle: bigint;
34let balanceUniqueTokenFinal: bigint;
35let unqFees: bigint;
3627
3728
38async function genericSendUnqTo(
39 networkName: keyof typeof NETWORKS,
40 randomAccount: IKeyringPair,
41 randomAccountOnTargetChain = randomAccount,
42) {
43 const networkUrl = mapToChainUrl(networkName);
44 const targetPlayground = getDevPlayground(networkName);
45 await usingPlaygrounds(async (helper) => {
46 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
47 const destination = {
48 V2: {
49 parents: 1,
50 interior: {
51 X1: {
52 Parachain: mapToChainId(networkName),
53 },
54 },
55 },
56 };
57
58 const beneficiary = {
59 V2: {
60 parents: 0,
61 interior: {
62 X1: (
63 networkName == 'moonbeam' ?
64 {
65 AccountKey20: {
66 network: 'Any',
67 key: randomAccountOnTargetChain.address,
68 },
69 }
70 :
71 {
72 AccountId32: {
73 network: 'Any',
74 id: randomAccountOnTargetChain.addressRaw,
75 },
76 }
77 ),
78 },
79 },
80 };
81
82 const assets = {
83 V2: [
84 {
85 id: {
86 Concrete: {
87 parents: 0,
88 interior: 'Here',
89 },
90 },
91 fun: {
92 Fungible: TRANSFER_AMOUNT,
93 },
94 },
95 ],
96 };
97 const feeAssetItem = 0;
98
99 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
100 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
101 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
102
103 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
104 console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
105 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
106
107 await targetPlayground(networkUrl, async (helper) => {
108 /*
109 Since only the parachain part of the Polkadex
110 infrastructure is launched (without their
111 solochain validators), processing incoming
112 assets will lead to an error.
113 This error indicates that the Polkadex chain
114 received a message from the Unique network,
115 since the hash is being checked to ensure
116 it matches what was sent.
117 */
118 if(networkName == 'polkadex') {
119 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
120 } else {
121 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
122 }
123 });
124
125 });
126}
127
128async function genericSendUnqBack(
129 networkName: keyof typeof NETWORKS,
130 sudoer: IKeyringPair,
131 randomAccountOnUnq: IKeyringPair,
132) {
133 const networkUrl = mapToChainUrl(networkName);
134
135 const targetPlayground = getDevPlayground(networkName);
136 await usingPlaygrounds(async (helper) => {
137
138 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
139 randomAccountOnUnq.addressRaw,
140 uniqueAssetId,
141 SENDBACK_AMOUNT,
142 );
143
144 let xcmProgramSent: any;
145
146
147 await targetPlayground(networkUrl, async (helper) => {
148 if('getSudo' in helper) {
149 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
150 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
151 } else if('fastDemocracy' in helper) {
152 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
153 // Needed to bypass the call filter.
154 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
155 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
156 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
157 }
158 });
159
160 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
161
162 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
163
164 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
165
166 });
167}
168
169async function genericSendOnlyOwnedBalance(
170 networkName: keyof typeof NETWORKS,
171 sudoer: IKeyringPair,
172) {
173 const networkUrl = mapToChainUrl(networkName);
174 const targetPlayground = getDevPlayground(networkName);
175
176 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
177
178 await usingPlaygrounds(async (helper) => {
179 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
180 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
181 const moreThanTargetChainHas = 2n * targetChainBalance;
182
183 const targetAccount = helper.arrange.createEmptyAccount();
184
185 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
186 targetAccount.addressRaw,
187 {
188 Concrete: {
189 parents: 0,
190 interior: 'Here',
191 },
192 },
193 moreThanTargetChainHas,
194 );
195
196 let maliciousXcmProgramSent: any;
197
198
199 await targetPlayground(networkUrl, async (helper) => {
200 if('getSudo' in helper) {
201 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
202 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
203 } else if('fastDemocracy' in helper) {
204 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
205 // Needed to bypass the call filter.
206 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
207 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
208 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
209 }
210 });
211
212 await expectFailedToTransact(helper, maliciousXcmProgramSent);
213
214 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
215 expect(targetAccountBalance).to.be.equal(0n);
216 });
217}
218
219async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
220 const networkUrl = mapToChainUrl(netwokrName);
221 const targetPlayground = getDevPlayground(netwokrName);
222
223 await usingPlaygrounds(async (helper) => {
224 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
225 const targetAccount = helper.arrange.createEmptyAccount();
226
227 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
228 targetAccount.addressRaw,
229 uniqueAssetId,
230 testAmount,
231 );
232
233 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
234 targetAccount.addressRaw,
235 {
236 Concrete: {
237 parents: 0,
238 interior: 'Here',
239 },
240 },
241 testAmount,
242 );
243
244 let maliciousXcmProgramFullIdSent: any;
245 let maliciousXcmProgramHereIdSent: any;
246 const maxWaitBlocks = 3;
247
248 // Try to trick Unique using full UNQ identification
249 await targetPlayground(networkUrl, async (helper) => {
250 if('getSudo' in helper) {
251 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
252 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
253 }
254 // Moonbeam case
255 else if('fastDemocracy' in helper) {
256 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
257 // Needed to bypass the call filter.
258 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
259 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
260
261 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
262 }
263 });
264
265
266 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
267
268 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
269 expect(accountBalance).to.be.equal(0n);
270
271 // Try to trick Unique using shortened UNQ identification
272 await targetPlayground(networkUrl, async (helper) => {
273 if('getSudo' in helper) {
274 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
275 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
276 }
277 else if('fastDemocracy' in helper) {
278 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
279 // Needed to bypass the call filter.
280 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
281 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
282
283 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
284 }
285 });
286
287 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
288
289 accountBalance = await helper.balance.getSubstrate(targetAccount.address);
290 expect(accountBalance).to.be.equal(0n);
291 });
292}
293
294async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
295 const networkUrl = mapToChainUrl(networkName);
296 const targetPlayground = getDevPlayground(networkName);
297 let messageSent: any;
298
299 await usingPlaygrounds(async (helper) => {
300 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
301 helper.arrange.createEmptyAccount().addressRaw,
302 {
303 Concrete: {
304 parents: 1,
305 interior: {
306 X1: {
307 Parachain: mapToChainId(networkName),
308 },
309 },
310 },
311 },
312 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
313 );
314 await targetPlayground(networkUrl, async (helper) => {
315 if('getSudo' in helper) {
316 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
317 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
318 } else if('fastDemocracy' in helper) {
319 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
320 // Needed to bypass the call filter.
321 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
322 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
323
324 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
325 }
326 });
327 await expectFailedToTransact(helper, messageSent);
328 });
329}
330
331
332describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {29describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
37471
375 await usingPlaygrounds(async (helper) => {72 await usingPlaygrounds(async (helper) => {
376 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);73 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
377 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
378 });74 });
379 });75 });
38076
381 itSub('Should connect and send UNQ to Acala', async () => {77 itSub('Should connect and send UNQ to Acala', async () => {
382 await genericSendUnqTo('acala', randomAccount);78 await testHelper.sendUnqTo('acala', randomAccount);
383 });79 });
38480
385 itSub('Should connect to Acala and send UNQ back', async () => {81 itSub('Should connect to Acala and send UNQ back', async () => {
386 await genericSendUnqBack('acala', alice, randomAccount);82 await testHelper.sendUnqBack('acala', alice, randomAccount);
387 });83 });
38884
389 itSub('Acala can send only up to its balance', async () => {85 itSub('Acala can send only up to its balance', async () => {
390 await genericSendOnlyOwnedBalance('acala', alice);86 await testHelper.sendOnlyOwnedBalance('acala', alice);
391 });87 });
39288
393 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {89 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
394 await genericReserveTransferUNQfrom('acala', alice);90 await testHelper.rejectReserveTransferUNQfrom('acala', alice);
395 });91 });
396});92});
39793
429125
430 await usingPlaygrounds(async (helper) => {126 await usingPlaygrounds(async (helper) => {
431 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);127 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
432 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
433 });128 });
434 });129 });
435130
436 itSub('Should connect and send UNQ to Polkadex', async () => {131 itSub('Should connect and send UNQ to Polkadex', async () => {
437 await genericSendUnqTo('polkadex', randomAccount);132 await testHelper.sendUnqTo('polkadex', randomAccount);
438 });133 });
439134
440135
441 itSub('Should connect to Polkadex and send UNQ back', async () => {136 itSub('Should connect to Polkadex and send UNQ back', async () => {
442 await genericSendUnqBack('polkadex', alice, randomAccount);137 await testHelper.sendUnqBack('polkadex', alice, randomAccount);
443 });138 });
444139
445 itSub('Polkadex can send only up to its balance', async () => {140 itSub('Polkadex can send only up to its balance', async () => {
446 await genericSendOnlyOwnedBalance('polkadex', alice);141 await testHelper.sendOnlyOwnedBalance('polkadex', alice);
447 });142 });
448143
449 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {144 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {
450 await genericReserveTransferUNQfrom('polkadex', alice);145 await testHelper.rejectReserveTransferUNQfrom('polkadex', alice);
451 });146 });
452});147});
453148
466 });161 });
467162
468 itSub('Unique rejects ACA tokens from Acala', async () => {163 itSub('Unique rejects ACA tokens from Acala', async () => {
469 await genericRejectNativeTokensFrom('acala', alice);164 await testHelper.rejectNativeTokensFrom('acala', alice);
470 });165 });
471166
472 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {167 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
473 await genericRejectNativeTokensFrom('moonbeam', alice);168 await testHelper.rejectNativeTokensFrom('moonbeam', alice);
474 });169 });
475170
476 itSub('Unique rejects ASTR tokens from Astar', async () => {171 itSub('Unique rejects ASTR tokens from Astar', async () => {
477 await genericRejectNativeTokensFrom('astar', alice);172 await testHelper.rejectNativeTokensFrom('astar', alice);
478 });173 });
479174
480 itSub('Unique rejects PDX tokens from Polkadex', async () => {175 itSub('Unique rejects PDX tokens from Polkadex', async () => {
481 await genericRejectNativeTokensFrom('polkadex', alice);176 await testHelper.rejectNativeTokensFrom('polkadex', alice);
482 });177 });
483});178});
484179
569264
570 await usingPlaygrounds(async (helper) => {265 await usingPlaygrounds(async (helper) => {
571 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);266 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
572 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
573 });267 });
574 });268 });
575269
576 itSub('Should connect and send UNQ to Moonbeam', async () => {270 itSub('Should connect and send UNQ to Moonbeam', async () => {
577 await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);271 await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
578 });272 });
579273
580 itSub('Should connect to Moonbeam and send UNQ back', async () => {274 itSub('Should connect to Moonbeam and send UNQ back', async () => {
581 await genericSendUnqBack('moonbeam', alice, randomAccountUnique);275 await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);
582 });276 });
583277
584 itSub('Moonbeam can send only up to its balance', async () => {278 itSub('Moonbeam can send only up to its balance', async () => {
585 await genericSendOnlyOwnedBalance('moonbeam', alice);279 await testHelper.sendOnlyOwnedBalance('moonbeam', alice);
586 });280 });
587281
588 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {282 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {
589 await genericReserveTransferUNQfrom('moonbeam', alice);283 await testHelper.rejectReserveTransferUNQfrom('moonbeam', alice);
590 });284 });
591});285});
592286
593describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {287describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {
594 let alice: IKeyringPair;288 let alice: IKeyringPair;
595 let randomAccount: IKeyringPair;289 let randomAccount: IKeyringPair;
596290
597 const UNQ_ASSET_ID_ON_ASTAR = 1;291 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar
598 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;292 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar
599293
600 // Unique -> Astar294 // Unique -> Astar
601 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.295 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
602 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?296 const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar
603297
604 before(async () => {298 before(async () => {
605 await usingPlaygrounds(async (helper, privateKey) => {299 await usingPlaygrounds(async (helper, privateKey) => {
615 await usingAstarPlaygrounds(astarUrl, async (helper) => {309 await usingAstarPlaygrounds(astarUrl, async (helper) => {
616 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {310 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {
617 console.log('1. Create foreign asset and metadata');311 console.log('1. Create foreign asset and metadata');
618 // TODO update metadata with values from production
619 await helper.assets.create(312 await helper.assets.create(
620 alice,313 alice,
621 UNQ_ASSET_ID_ON_ASTAR,314 UNQ_ASSET_ID_ON_ASTAR,
626 await helper.assets.setMetadata(319 await helper.assets.setMetadata(
627 alice,320 alice,
628 UNQ_ASSET_ID_ON_ASTAR,321 UNQ_ASSET_ID_ON_ASTAR,
629 'Cross chain UNQ',322 'Unique Network',
630 'xcUNQ',323 'UNQ',
631 Number(UNQ_DECIMALS),324 Number(UNQ_DECIMALS),
632 );325 );
633326
656 });349 });
657350
658 itSub('Should connect and send UNQ to Astar', async () => {351 itSub('Should connect and send UNQ to Astar', async () => {
659 await genericSendUnqTo('astar', randomAccount);352 await testHelper.sendUnqTo('astar', randomAccount);
660 });353 });
661354
662 itSub('Should connect to Astar and send UNQ back', async () => {355 itSub('Should connect to Astar and send UNQ back', async () => {
663 await genericSendUnqBack('astar', alice, randomAccount);356 await testHelper.sendUnqBack('astar', alice, randomAccount);
664 });357 });
665358
666 itSub('Astar can send only up to its balance', async () => {359 itSub('Astar can send only up to its balance', async () => {
667 await genericSendOnlyOwnedBalance('astar', alice);360 await testHelper.sendOnlyOwnedBalance('astar', alice);
668 });361 });
669362
670 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {363 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
671 await genericReserveTransferUNQfrom('astar', alice);364 await testHelper.rejectReserveTransferUNQfrom('astar', alice);
365 });
366});
367
368describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {
369 let sudoer: IKeyringPair;
370
371 before(async function () {
372 await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {
373 sudoer = await privateKey('//Alice');
374 });
375 });
376
377 // At the moment there is no reliable way
378 // to establish the correspondence between the `ExecutedDownward` event
379 // and the relay's sent message due to `SetTopic` instruction
380 // containing an unpredictable topic silently added by the relay's messages on the router level.
381 // This changes the message hash on arrival to our chain.
382 //
383 // See:
384 // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83
385 // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36
386 //
387 // Because of this, we insert time gaps between tests so
388 // different `ExecutedDownward` events won't interfere with each other.
389 afterEach(async () => {
390 await usingPlaygrounds(async (helper) => {
391 await helper.wait.newBlocks(3);
392 });
393 });
394
395 itSub('The relay can set storage', async () => {
396 await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');
397 });
398
399 itSub('The relay can batch set storage', async () => {
400 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');
401 });
402
403 itSub('The relay can batchAll set storage', async () => {
404 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');
405 });
406
407 itSub('The relay can forceBatch set storage', async () => {
408 await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');
409 });
410
411 itSub('[negative] The relay cannot set balance', async () => {
412 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');
413 });
414
415 itSub('[negative] The relay cannot set balance via batch', async () => {
416 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');
417 });
418
419 itSub('[negative] The relay cannot set balance via batchAll', async () => {
420 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');
421 });
422
423 itSub('[negative] The relay cannot set balance via forceBatch', async () => {
424 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');
425 });
426
427 itSub('[negative] The relay cannot set balance via dispatchAs', async () => {
428 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');
672 });429 });
673});430});
674431
modifiedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
1import {IKeyringPair} from '@polkadot/types/types';
2import {hexToString} from '@polkadot/util';
1import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';3import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';
2import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';4import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
3import config from '../config';5import config from '../config';
46
9export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);11export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
10export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);12export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
13
14export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);
15export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);
16export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);
17export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);
18export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
19
20export const relayUrl = config.relayUrl;
21export const statemintUrl = config.statemintUrl;
22export const statemineUrl = config.statemineUrl;
1123
12export const acalaUrl = config.acalaUrl;24export const acalaUrl = config.acalaUrl;
13export const moonbeamUrl = config.moonbeamUrl;25export const moonbeamUrl = config.moonbeamUrl;
14export const astarUrl = config.astarUrl;26export const astarUrl = config.astarUrl;
15export const polkadexUrl = config.polkadexUrl;27export const polkadexUrl = config.polkadexUrl;
28
29export const karuraUrl = config.karuraUrl;
30export const moonriverUrl = config.moonriverUrl;
31export const shidenUrl = config.shidenUrl;
1632
17export const SAFE_XCM_VERSION = 3;33export const SAFE_XCM_VERSION = 3;
34
1835
19export const maxWaitBlocks = 6;36export const RELAY_DECIMALS = 12;
2037export const STATEMINE_DECIMALS = 12;
38export const KARURA_DECIMALS = 12;
39export const SHIDEN_DECIMALS = 18n;
40export const QTZ_DECIMALS = 18n;
2141
22export const ASTAR_DECIMALS = 18n;42export const ASTAR_DECIMALS = 18n;
23export const UNQ_DECIMALS = 18n;43export const UNQ_DECIMALS = 18n;
44
45export const maxWaitBlocks = 6;
2446
25export const uniqueMultilocation = {47export const uniqueMultilocation = {
26 parents: 1,48 parents: 1,
47 && event.outcome.isUntrustedReserveLocation);69 && event.outcome.isUntrustedReserveLocation);
48};70};
71
72export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {
73 // The correct messageHash for downward messages can't be reliably obtained
74 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);
75};
76
77export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {
78 // The correct messageHash for downward messages can't be reliably obtained
79 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);
80};
4981
50export const NETWORKS = {82export const NETWORKS = {
51 acala: usingAcalaPlaygrounds,83 acala: usingAcalaPlaygrounds,
52 astar: usingAstarPlaygrounds,84 astar: usingAstarPlaygrounds,
53 polkadex: usingPolkadexPlaygrounds,85 polkadex: usingPolkadexPlaygrounds,
54 moonbeam: usingMoonbeamPlaygrounds,86 moonbeam: usingMoonbeamPlaygrounds,
87 moonriver: usingMoonriverPlaygrounds,
88 karura: usingKaruraPlaygrounds,
89 shiden: usingShidenPlaygrounds,
55} as const;90} as const;
91type NetworkNames = keyof typeof NETWORKS;
92
93type NativeRuntime = 'opal' | 'quartz' | 'unique';
5694
57export function mapToChainId(networkName: keyof typeof NETWORKS) {95export function mapToChainId(networkName: keyof typeof NETWORKS): number {
58 switch (networkName) {96 switch (networkName) {
59 case 'acala':97 case 'acala':
60 return ACALA_CHAIN;98 return ACALA_CHAIN;
64 return MOONBEAM_CHAIN;102 return MOONBEAM_CHAIN;
65 case 'polkadex':103 case 'polkadex':
66 return POLKADEX_CHAIN;104 return POLKADEX_CHAIN;
105 case 'moonriver':
106 return MOONRIVER_CHAIN;
107 case 'karura':
108 return KARURA_CHAIN;
109 case 'shiden':
110 return SHIDEN_CHAIN;
67 }111 }
68}112}
69113
70export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {114export function mapToChainUrl(networkName: NetworkNames): string {
71 switch (networkName) {115 switch (networkName) {
72 case 'acala':116 case 'acala':
73 return acalaUrl;117 return acalaUrl;
77 return moonbeamUrl;121 return moonbeamUrl;
78 case 'polkadex':122 case 'polkadex':
79 return polkadexUrl;123 return polkadexUrl;
124 case 'moonriver':
125 return moonriverUrl;
126 case 'karura':
127 return karuraUrl;
128 case 'shiden':
129 return shidenUrl;
80 }130 }
81}131}
132
133export function getDevPlayground(name: NetworkNames) {
134 return NETWORKS[name];
135}
136
137export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
138export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
139export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
140export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
141export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
82142
83export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {143export class XcmTestHelper {
144 private _balanceUniqueTokenInit: bigint = 0n;
145 private _balanceUniqueTokenMiddle: bigint = 0n;
146 private _balanceUniqueTokenFinal: bigint = 0n;
147 private _unqFees: bigint = 0n;
148 private _nativeRuntime: NativeRuntime;
149
150 constructor(runtime: NativeRuntime) {
151 this._nativeRuntime = runtime;
152 }
153
154 private _getNativeId() {
155 switch (this._nativeRuntime) {
156 case 'opal':
157 // To-Do
158 return 1001;
159 case 'quartz':
160 return QUARTZ_CHAIN;
161 case 'unique':
162 return UNIQUE_CHAIN;
163 }
164 }
165
166 private _isAddress20FormatFor(network: NetworkNames) {
167 switch (network) {
168 case 'moonbeam':
169 case 'moonriver':
170 return true;
171 default:
172 return false;
173 }
174 }
175
176 private _runtimeVersionedMultilocation() {
177 return {
178 V3: {
179 parents: 1,
180 interior: {
181 X1: {
182 Parachain: this._getNativeId(),
183 },
184 },
185 },
186 };
187 }
188
189 private _uniqueChainMultilocationForRelay() {
190 return {
191 V3: {
192 parents: 0,
193 interior: {
194 X1: {Parachain: this._getNativeId()},
195 },
196 },
197 };
198 }
199
200 async sendUnqTo(
201 networkName: keyof typeof NETWORKS,
202 randomAccount: IKeyringPair,
203 randomAccountOnTargetChain = randomAccount,
204 ) {
205 const networkUrl = mapToChainUrl(networkName);
206 const targetPlayground = getDevPlayground(networkName);
207 await usingPlaygrounds(async (helper) => {
208 this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
209 const destination = {
210 V2: {
211 parents: 1,
212 interior: {
213 X1: {
214 Parachain: mapToChainId(networkName),
215 },
216 },
217 },
218 };
219
220 const beneficiary = {
221 V2: {
222 parents: 0,
223 interior: {
224 X1: (
225 this._isAddress20FormatFor(networkName) ?
226 {
227 AccountKey20: {
228 network: 'Any',
229 key: randomAccountOnTargetChain.address,
230 },
231 }
232 :
233 {
234 AccountId32: {
235 network: 'Any',
236 id: randomAccountOnTargetChain.addressRaw,
237 },
238 }
239 ),
240 },
241 },
242 };
243
244 const assets = {
245 V2: [
246 {
247 id: {
248 Concrete: {
249 parents: 0,
250 interior: 'Here',
251 },
252 },
253 fun: {
254 Fungible: TRANSFER_AMOUNT,
255 },
256 },
257 ],
258 };
259 const feeAssetItem = 0;
260
261 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
262 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
263 this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
264
265 this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
266 console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));
267 expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;
268
269 await targetPlayground(networkUrl, async (helper) => {
270 /*
271 Since only the parachain part of the Polkadex
272 infrastructure is launched (without their
273 solochain validators), processing incoming
274 assets will lead to an error.
275 This error indicates that the Polkadex chain
276 received a message from the Unique network,
277 since the hash is being checked to ensure
278 it matches what was sent.
279 */
280 if(networkName == 'polkadex') {
281 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
282 } else {
283 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
284 }
285 });
286
287 });
288 }
289
290 async sendUnqBack(
291 networkName: keyof typeof NETWORKS,
292 sudoer: IKeyringPair,
293 randomAccountOnUnq: IKeyringPair,
294 ) {
295 const networkUrl = mapToChainUrl(networkName);
296
297 const targetPlayground = getDevPlayground(networkName);
298 await usingPlaygrounds(async (helper) => {
299
300 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
301 randomAccountOnUnq.addressRaw,
302 {
303 Concrete: {
304 parents: 1,
305 interior: {
306 X1: {Parachain: this._getNativeId()},
307 },
308 },
309 },
310 SENDBACK_AMOUNT,
311 );
312
313 let xcmProgramSent: any;
314
315
316 await targetPlayground(networkUrl, async (helper) => {
317 if('getSudo' in helper) {
318 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);
319 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
320 } else if('fastDemocracy' in helper) {
321 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);
322 // Needed to bypass the call filter.
323 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
324 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
325 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
326 }
327 });
328
329 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
330
331 this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
332
333 expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);
334
335 });
336 }
337
338 async sendOnlyOwnedBalance(
339 networkName: keyof typeof NETWORKS,
340 sudoer: IKeyringPair,
341 ) {
342 const networkUrl = mapToChainUrl(networkName);
343 const targetPlayground = getDevPlayground(networkName);
344
345 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
346
347 await usingPlaygrounds(async (helper) => {
348 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
349 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
350 const moreThanTargetChainHas = 2n * targetChainBalance;
351
352 const targetAccount = helper.arrange.createEmptyAccount();
353
354 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
355 targetAccount.addressRaw,
356 {
357 Concrete: {
358 parents: 0,
359 interior: 'Here',
360 },
361 },
362 moreThanTargetChainHas,
363 );
364
365 let maliciousXcmProgramSent: any;
366
367
368 await targetPlayground(networkUrl, async (helper) => {
369 if('getSudo' in helper) {
370 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);
371 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
372 } else if('fastDemocracy' in helper) {
373 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);
374 // Needed to bypass the call filter.
375 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
376 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
377 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
378 }
379 });
380
381 await expectFailedToTransact(helper, maliciousXcmProgramSent);
382
383 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
384 expect(targetAccountBalance).to.be.equal(0n);
385 });
386 }
387
388 async rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
389 const networkUrl = mapToChainUrl(networkName);
390 const targetPlayground = getDevPlayground(networkName);
391
392 await usingPlaygrounds(async (helper) => {
393 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
394 const targetAccount = helper.arrange.createEmptyAccount();
395
396 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
397 targetAccount.addressRaw,
398 {
399 Concrete: {
400 parents: 1,
401 interior: {
402 X1: {
403 Parachain: this._getNativeId(),
404 },
405 },
406 },
407 },
408 testAmount,
409 );
410
411 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
412 targetAccount.addressRaw,
413 {
414 Concrete: {
415 parents: 0,
416 interior: 'Here',
417 },
418 },
419 testAmount,
420 );
421
422 let maliciousXcmProgramFullIdSent: any;
423 let maliciousXcmProgramHereIdSent: any;
424 const maxWaitBlocks = 3;
425
426 // Try to trick Unique using full UNQ identification
427 await targetPlayground(networkUrl, async (helper) => {
428 if('getSudo' in helper) {
429 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);
430 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
431 }
432 // Moonbeam case
433 else if('fastDemocracy' in helper) {
434 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
435 // Needed to bypass the call filter.
436 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
437 await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
438
439 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
440 }
441 });
442
443
444 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
445
446 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
447 expect(accountBalance).to.be.equal(0n);
448
449 // Try to trick Unique using shortened UNQ identification
450 await targetPlayground(networkUrl, async (helper) => {
451 if('getSudo' in helper) {
452 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);
453 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
454 }
455 else if('fastDemocracy' in helper) {
456 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);
457 // Needed to bypass the call filter.
458 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
459 await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
460
461 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
462 }
463 });
464
465 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
466
467 accountBalance = await helper.balance.getSubstrate(targetAccount.address);
468 expect(accountBalance).to.be.equal(0n);
469 });
470 }
471
472 async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
473 const networkUrl = mapToChainUrl(networkName);
474 const targetPlayground = getDevPlayground(networkName);
475 let messageSent: any;
476
477 await usingPlaygrounds(async (helper) => {
478 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
479 helper.arrange.createEmptyAccount().addressRaw,
480 {
481 Concrete: {
482 parents: 1,
483 interior: {
484 X1: {
485 Parachain: mapToChainId(networkName),
486 },
487 },
488 },
489 },
490 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
491 );
492 await targetPlayground(networkUrl, async (helper) => {
493 if('getSudo' in helper) {
494 await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);
495 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
496 } else if('fastDemocracy' in helper) {
497 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
498 // Needed to bypass the call filter.
499 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
500 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
501
502 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
503 }
504 });
505 await expectFailedToTransact(helper, messageSent);
506 });
507 }
508
509 private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {
510 // eslint-disable-next-line require-await
84 return NETWORKS[name];511 return await usingPlaygrounds(async (helper) => {
512 const relayForceKV = () => {
513 const random = Math.random();
514 const key = `relay-forced-key (instance: ${random})`;
515 const val = `relay-forced-value (instance: ${random})`;
516 const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex();
517
518 return {
519 call,
520 key,
521 val,
522 };
523 };
524
525 if(variant == 'plain') {
526 const kv = relayForceKV();
527 return {
528 program: helper.arrange.makeUnpaidSudoTransactProgram({
529 weightMultiplier: 1,
530 call: kv.call,
531 }),
532 kvs: [kv],
533 };
534 } else {
535 const kv0 = relayForceKV();
536 const kv1 = relayForceKV();
537
538 const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex();
539 return {
540 program: helper.arrange.makeUnpaidSudoTransactProgram({
541 weightMultiplier: 2,
542 call: batchCall,
543 }),
544 kvs: [kv0, kv1],
545 };
546 }
547 });
85}548 }
549
550 async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {
551 const {program, kvs} = await this._relayXcmTransactSetStorage(variant);
552
553 await usingRelayPlaygrounds(relayUrl, async (helper) => {
554 await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [
555 this._uniqueChainMultilocationForRelay(),
556 program,
557 ]);
558 });
559
560 await usingPlaygrounds(async (helper) => {
561 await expectDownwardXcmComplete(helper);
562
563 for(const kv of kvs) {
564 const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]);
565 expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val);
566 }
567 });
568 }
569
570 private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') {
571 // eslint-disable-next-line require-await
572 return await usingPlaygrounds(async (helper) => {
573 const emptyAccount = helper.arrange.createEmptyAccount().address;
574
575 const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex();
576
577 let call;
578
579 if(variant == 'plain') {
580 call = forceSetBalanceCall;
581
582 } else if(variant == 'dispatchAs') {
583 call = helper.constructApiCall('api.tx.utility.dispatchAs', [
584 {
585 system: 'Root',
586 },
587 forceSetBalanceCall,
588 ]).method.toHex();
589 } else {
590 call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex();
591 }
592
593 return {
594 program: helper.arrange.makeUnpaidSudoTransactProgram({
595 weightMultiplier: 1,
596 call,
597 }),
598 emptyAccount,
599 };
600 });
601 }
602
603 async relayIsNotPermittedToSetBalance(
604 relaySudoer: IKeyringPair,
605 variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs',
606 ) {
607 const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant);
608
609 await usingRelayPlaygrounds(relayUrl, async (helper) => {
610 await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [
611 this._uniqueChainMultilocationForRelay(),
612 program,
613 ]);
614 });
615
616 await usingPlaygrounds(async (helper) => {
617 await expectDownwardXcmNoPermission(helper);
618 expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n);
619 });
620 }
621}
622
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import config from '../config';
19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';18import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';
20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';19import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
21
22const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);20import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types';
23const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);21import {hexToString} from '@polkadot/util';
24const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);22
25const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);23
26const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
2724
28const STATEMINE_PALLET_INSTANCE = 50;25const STATEMINE_PALLET_INSTANCE = 50;
29
30const relayUrl = config.relayUrl;
31const statemineUrl = config.statemineUrl;
32const karuraUrl = config.karuraUrl;
33const moonriverUrl = config.moonriverUrl;
34const shidenUrl = config.shidenUrl;
35
36const RELAY_DECIMALS = 12;
37const STATEMINE_DECIMALS = 12;
38const KARURA_DECIMALS = 12;
39const SHIDEN_DECIMALS = 18n;
40const QTZ_DECIMALS = 18n;
4126
42const TRANSFER_AMOUNT = 2000000000000000000000000n;27const TRANSFER_AMOUNT = 2000000000000000000000000n;
4328
499 minimalBalance: 1000000000000000000n,484 minimalBalance: 1000000000000000000n,
500 };485 };
501486
487 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
488 hexToString(v.toJSON()['symbol'])) as string[];
489
490 if(!assets.includes('QTZ')) {
502 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);491 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
492 } else {
493 console.log('QTZ token already registered on Karura assetRegistry pallet');
494 }
503 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);495 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
504 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);496 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
505 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});497 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
985 const unitsPerSecond = 1n;977 const unitsPerSecond = 1n;
986 const numAssetsWeightHint = 0;978 const numAssetsWeightHint = 0;
987979
980 if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
981 console.log('Quartz asset already registered on Moonriver');
982 } else {
988 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({983 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
989 location: quartzAssetLocation,984 location: quartzAssetLocation,
990 metadata: quartzAssetMetadata,985 metadata: quartzAssetMetadata,
997 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);992 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
998993
999 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);994 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
1000995 }
1001 // >>> Acquire Quartz AssetId Info on Moonriver >>>996 // >>> Acquire Quartz AssetId Info on Moonriver >>>
1002 console.log('Acquire Quartz AssetId Info on Moonriver.......');997 console.log('Acquire Quartz AssetId Info on Moonriver.......');
1003998
1288 let alice: IKeyringPair;1283 let alice: IKeyringPair;
1289 let sender: IKeyringPair;1284 let sender: IKeyringPair;
12901285
1291 const QTZ_ASSET_ID_ON_SHIDEN = 1;1286 const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden
1292 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;1287 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden
12931288
1294 // Quartz -> Shiden1289 // Quartz -> Shiden
1295 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1290 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden
1296 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1291 const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden
1297 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1292 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ
1298 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens1293 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens
12991294
1314 });1309 });
13151310
1316 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1311 await usingShidenPlaygrounds(shidenUrl, async (helper) => {
1312 if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
1317 console.log('1. Create foreign asset and metadata');1313 console.log('1. Create foreign asset and metadata');
1318 // TODO update metadata with values from production
1319 await helper.assets.create(1314 await helper.assets.create(
1320 alice,1315 alice,
1321 QTZ_ASSET_ID_ON_SHIDEN,1316 QTZ_ASSET_ID_ON_SHIDEN,
1326 await helper.assets.setMetadata(1321 await helper.assets.setMetadata(
1327 alice,1322 alice,
1328 QTZ_ASSET_ID_ON_SHIDEN,1323 QTZ_ASSET_ID_ON_SHIDEN,
1329 'Cross chain QTZ',1324 'Quartz',
1330 'xcQTZ',1325 'QTZ',
1331 Number(QTZ_DECIMALS),1326 Number(QTZ_DECIMALS),
1332 );1327 );
13331328
13471342
1348 console.log('3. Set QTZ payment for XCM execution on Shiden');1343 console.log('3. Set QTZ payment for XCM execution on Shiden');
1349 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1344 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
13501345 } else {
1346 console.log('QTZ is already registered on Shiden');
1347 }
1351 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1348 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');
1352 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1349 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);
1353 });1350 });
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
1511 let alice: IKeyringPair;1511 let alice: IKeyringPair;
1512 let randomAccount: IKeyringPair;1512 let randomAccount: IKeyringPair;
15131513
1514 const UNQ_ASSET_ID_ON_ASTAR = 1;1514 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar
1515 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;1515 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar
15161516
1517 // Unique -> Astar1517 // Unique -> Astar
1518 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1518 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
1519 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1519 const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar
1520 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1520 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ
1521 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens1521 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens
15221522
1539 await usingAstarPlaygrounds(astarUrl, async (helper) => {1539 await usingAstarPlaygrounds(astarUrl, async (helper) => {
1540 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1540 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {
1541 console.log('1. Create foreign asset and metadata');1541 console.log('1. Create foreign asset and metadata');
1542 // TODO update metadata with values from production
1543 await helper.assets.create(1542 await helper.assets.create(
1544 alice,1543 alice,
1545 UNQ_ASSET_ID_ON_ASTAR,1544 UNQ_ASSET_ID_ON_ASTAR,
1550 await helper.assets.setMetadata(1549 await helper.assets.setMetadata(
1551 alice,1550 alice,
1552 UNQ_ASSET_ID_ON_ASTAR,1551 UNQ_ASSET_ID_ON_ASTAR,
1553 'Cross chain UNQ',1552 'Unique Network',
1554 'xcUNQ',1553 'UNQ',
1555 Number(UNQ_DECIMALS),1554 Number(UNQ_DECIMALS),
1556 );1555 );
15571556