diff --git a/bridge-forwarder/src/lib.rs b/bridge-forwarder/src/lib.rs index f446b6a..52cda55 100644 --- a/bridge-forwarder/src/lib.rs +++ b/bridge-forwarder/src/lib.rs @@ -43,7 +43,7 @@ pub mod pallet { what: MultiAsset, dest: MultiLocation, ) -> DispatchResult { - let cap_weight: Weight = Weight::from_parts(6_000_000_000u64, 2_000_000u64); + let cap_weight: Weight = Weight::from_all(u64::MAX); T::XCMBridge::transfer(origin, what.clone(), dest, Some(cap_weight))?; let origin_location: MultiLocation = diff --git a/bridge-forwarder/src/xcm_asset_transactor.rs b/bridge-forwarder/src/xcm_asset_transactor.rs index 3ec294e..ab70bcd 100644 --- a/bridge-forwarder/src/xcm_asset_transactor.rs +++ b/bridge-forwarder/src/xcm_asset_transactor.rs @@ -27,9 +27,9 @@ impl< // 2. recipient is on non-substrate chain(evm, cosmos, etc.) // 3. recipient is on the remote parachain fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult { - match (who.parents, who.first_interior()) { + match (who.parents, who.interior) { // 1. recipient is on the local parachain - (0, Some(AccountId32 { .. })) | (0, Some(AccountKey20 { .. })) => { + (0, X1(AccountId32 { .. })) | (0, X1(AccountKey20 { .. })) | (1, X1(Parachain(_))) => { // check if the asset is native, and call the corresponding deposit_asset() if AssetTypeChecker::is_native_asset(what) { CurrencyTransactor::deposit_asset(what, who, context)?; diff --git a/scripts/e2e_setup.sh b/scripts/e2e_setup.sh index 66c007e..5a25eb1 100755 --- a/scripts/e2e_setup.sh +++ b/scripts/e2e_setup.sh @@ -19,7 +19,7 @@ CHAINSPECFILE="chain-spec.json" # Run setup script echo "run scripts to set up pallets..." npm i --prefix $SETUP_SCRIPTS_DIR/scripts/js -node $SETUP_SCRIPTS_DIR/scripts/js/setup.js +node $SETUP_SCRIPTS_DIR/scripts/standalone/setup.js sleep 10 diff --git a/scripts/js/execute_proposal_test.js b/scripts/standalone/execute_proposal_test.js similarity index 100% rename from scripts/js/execute_proposal_test.js rename to scripts/standalone/execute_proposal_test.js diff --git a/scripts/js/package.json b/scripts/standalone/package.json similarity index 100% rename from scripts/js/package.json rename to scripts/standalone/package.json diff --git a/scripts/js/setup.js b/scripts/standalone/setup.js similarity index 100% rename from scripts/js/setup.js rename to scripts/standalone/setup.js diff --git a/scripts/js/util.js b/scripts/standalone/util.js similarity index 100% rename from scripts/js/util.js rename to scripts/standalone/util.js diff --git a/scripts/xcm/e2e_tests.js b/scripts/xcm/e2e_tests.js new file mode 100644 index 0000000..d6973d0 --- /dev/null +++ b/scripts/xcm/e2e_tests.js @@ -0,0 +1,547 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +require('dotenv').config(); + +const {decodeAddress} = require('@polkadot/util-crypto'); +const {utils} = require('ethers'); + +const {ApiPromise, WsProvider, Keyring} = require('@polkadot/api'); +const {cryptoWaitReady} = require('@polkadot/util-crypto'); +const { + assetHubProvider, + bridgeHubProvider, + getNativeMultiAsset, + getUSDCMultiAsset, + getUSDCMultiAssetX2, + getUSDCAssetIdX2, + getTTTMultiAsset, + getAHNMultiAsset, + getAssetDepositDest, + teleportTokenViaXCM, + getAssetHubTeleportDest, + getAssetHubTeleportBeneficiary, + getAssetHubTeleportBeneficiaryToSygma, + getAssetHubTeleportAsset, + getAssetHubTeleportWeightLimit, + deposit, + executeProposal, + subEvents, + queryBalance, + queryAssetBalance, + FeeReserveAccount, + NativeTokenTransferReserveAccount, + OtherTokenTransferReserveAccount, + usdcAssetID, + usdcMinBalance, + usdcName, + usdcSymbol, + usdcDecimal, + ahnAssetID, + tttAssetID, + tttMinBalance, + tttName, + tttSymbol, + tttDecimal, bhnAssetID, +} = require("./util"); + +async function main() { + const assetHubApi = await ApiPromise.create({ + provider: assetHubProvider, + }); + const bridgeHubApi = await ApiPromise.create({ + provider: bridgeHubProvider, + }); + + // prepare keyring + await cryptoWaitReady(); + const keyring = new Keyring({type: 'sr25519'}); + const sudo = keyring.addFromUri('//Alice'); + + // collection of failed testcases + let failedTestcases = []; + + // run testcases + + // bridge hub parachain local test + await testcase1(bridgeHubApi, sudo, failedTestcases); + await testcase2(bridgeHubApi, sudo, failedTestcases); + + // asset hub to bridge hub and then to sygma relayer test + await testcase3(assetHubApi, bridgeHubApi, sudo, failedTestcases); + await testcase4(assetHubApi, bridgeHubApi, sudo, failedTestcases); + await testcase5(assetHubApi, bridgeHubApi, sudo, failedTestcases); + await testcase6(assetHubApi, bridgeHubApi, sudo, failedTestcases); + + // sygma relayer to bridge hub and then to asset hub test + await testcase7(bridgeHubApi, sudo, failedTestcases); + await testcase8(bridgeHubApi, sudo, failedTestcases); + await testcase9(bridgeHubApi, assetHubApi, sudo, failedTestcases); + await testcase10(bridgeHubApi, assetHubApi, sudo, failedTestcases); + + // checking if any testcase failed + for (const item of failedTestcases) { + // console.error('\x1b[31m%s\x1b[0m\n', item); + // return + throw Error(`\x1b[31m${item}\x1b[0m`); + } + console.log('\x1b[32m%s\x1b[0m', "All testcases pass"); +} + +function str2BigInt(a) { + return BigInt(a.replaceAll(',', '')); +} + +// testcase 1: Native token deposit on Bridge hub, dest on relayer +async function testcase1(bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 1 ...'); + + const nativeBalanceBeforeAlice = await queryBalance(bridgeHubApi, sudo.address); + console.log('Alice native asset balance before: ', nativeBalanceBeforeAlice.data.free); + + const nativeBalanceBeforeNativeTokenTransferReserveAccount = await queryBalance(bridgeHubApi, NativeTokenTransferReserveAccount); + console.log('token reserve account native asset balance before: ', nativeBalanceBeforeNativeTokenTransferReserveAccount.data.free); + + const nativeBalanceBeforeFeeAccount = await queryBalance(bridgeHubApi, FeeReserveAccount); + console.log('fee account native asset balance before: ', nativeBalanceBeforeFeeAccount.data.free); + + const depositAmount = 10000000000000; // 10 tokens + await deposit(bridgeHubApi, getNativeMultiAsset(bridgeHubApi, depositAmount), getAssetDepositDest(bridgeHubApi), true, sudo) + + const nativeBalanceAfterAlice = await queryBalance(bridgeHubApi, sudo.address); + console.log('Alice native asset balance after: ', nativeBalanceAfterAlice.data.free); + + const nativeBalanceAfterNativeTokenTransferReserveAccount = await queryBalance(bridgeHubApi, NativeTokenTransferReserveAccount); + console.log('token reserve account native asset balance after: ', nativeBalanceAfterNativeTokenTransferReserveAccount.data.free); + + const nativeBalanceAfterFeeAccount = await queryBalance(bridgeHubApi, FeeReserveAccount); + console.log('fee account native asset balance after: ', nativeBalanceAfterFeeAccount.data.free); + + // Alice balance should be deducted by 10 + tx fee, so the before - after should be greater than 10 tokens + if (str2BigInt(nativeBalanceBeforeAlice.data.free) - str2BigInt(nativeBalanceAfterAlice.data.free) <= BigInt(depositAmount)) { + failedTestcases.push("testcase 1 failed: Alice balance not match") + } + // balance reserve account should add deposit amount - fee which is 9,500,000,000,000 + if (str2BigInt(nativeBalanceAfterNativeTokenTransferReserveAccount.data.free) - str2BigInt(nativeBalanceBeforeNativeTokenTransferReserveAccount.data.free) !== BigInt(9500000000000)) { + failedTestcases.push("testcase 1 failed: NativeTokenTransferReserveAccount balance not match") + } + // fee account should add 500,000,000,000 + if (str2BigInt(nativeBalanceAfterFeeAccount.data.free) - str2BigInt(nativeBalanceBeforeFeeAccount.data.free) !== BigInt(500000000000)) { + failedTestcases.push("testcase 1 failed: FeeAccount balance not match") + } +} + +// testcase 2: Foreign token TTT deposit on Bridge hub, dest on relayer +async function testcase2(bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 2 ...'); + + let tttBalanceBeforeAlice = await queryAssetBalance(bridgeHubApi, tttAssetID, sudo.address); + console.log('Alice TTT asset balance before: ', tttBalanceBeforeAlice.balance); + + const tttBalanceBeforeOtherTokenTransferReserveAccount = await queryAssetBalance(bridgeHubApi, tttAssetID, OtherTokenTransferReserveAccount); + console.log('OtherTokenTransferReserveAccount TTT asset balance before: ', tttBalanceBeforeOtherTokenTransferReserveAccount.balance); + + const tttBalanceBeforeFeeReserveAccount = await queryAssetBalance(bridgeHubApi, tttAssetID, FeeReserveAccount); + console.log('FeeReserveAccountAddress TTT asset balance before: ', tttBalanceBeforeFeeReserveAccount.balance); + + const tttDepositAmount = 10000000000000; // 10 tokens + await deposit(bridgeHubApi, getTTTMultiAsset(bridgeHubApi, tttDepositAmount), getAssetDepositDest(bridgeHubApi), true, sudo) + + let tttBalanceAfterAlice = await queryAssetBalance(bridgeHubApi, tttAssetID, sudo.address); + console.log('Alice TTT asset balance after: ', tttBalanceAfterAlice.balance); + + const tttBalanceAfterOtherTokenTransferReserveAccount = await queryAssetBalance(bridgeHubApi, tttAssetID, OtherTokenTransferReserveAccount); + console.log('OtherTokenTransferReserveAccount TTT asset balance after: ', tttBalanceAfterOtherTokenTransferReserveAccount.balance); + + const tttBalanceAfterFeeReserveAccount = await queryAssetBalance(bridgeHubApi, tttAssetID, FeeReserveAccount); + console.log('FeeReserveAccountAddress TTT asset balance after: ', tttBalanceAfterFeeReserveAccount.balance); + + // Alice should be deducted by 10 TTT tokens + if (str2BigInt(tttBalanceBeforeAlice.balance) - str2BigInt(tttBalanceAfterAlice.balance) !== BigInt(tttDepositAmount)) { + failedTestcases.push("testcase 2 failed: Alice TTT token balance not match") + } + // OtherTokenTransferReserveAccount should add 0 because TTT is a non-reserve token on Bridge hub + if (str2BigInt(tttBalanceBeforeOtherTokenTransferReserveAccount.balance) !== str2BigInt(tttBalanceAfterOtherTokenTransferReserveAccount.balance)) { + failedTestcases.push("testcase 2 failed: OtherTokenTransferReserveAccount TTT token balance not match") + } + // FeeReserveAccount should add fee which is 500000000000 + if (str2BigInt(tttBalanceAfterFeeReserveAccount.balance) - str2BigInt(tttBalanceBeforeFeeReserveAccount.balance) !== BigInt(500000000000)) { + failedTestcases.push("testcase 2 failed: FeeReserveAccount TTT token balance not match") + } +} + +// testcase 3: Foreign token(USDC) deposit on Asset hub, dest on Bridge hub +async function testcase3(assetHubApi, bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 3 ...'); + + const usdcBalanceBeforeAliceAssethub = await queryAssetBalance(assetHubApi, usdcAssetID, sudo.address); + console.log('Alice USDC asset balance on Asset hub before: ', usdcBalanceBeforeAliceAssethub.balance); + + const usdcBalanceBeforeAliceBridgehub = await queryAssetBalance(bridgeHubApi, usdcAssetID, sudo.address); + console.log('Alice USDC asset balance on Bridge hub before: ', usdcBalanceBeforeAliceBridgehub.balance); + + const depositAmount = 10000000000000; // 10 tokens + const beneficiary = "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" // Alice + await teleportTokenViaXCM( + assetHubApi, + { + dest: getAssetHubTeleportDest(assetHubApi), + beneficiary: getAssetHubTeleportBeneficiary(assetHubApi, beneficiary), + assets: getAssetHubTeleportAsset(assetHubApi, getUSDCMultiAssetX2(assetHubApi, depositAmount)), + feeAssetItem: 0, + weightLimit: getAssetHubTeleportWeightLimit(assetHubApi), + fromParachainID: 1000, + toParachainID: 1013 + }, + true, sudo) + + const usdcBalanceAfterAliceAssethub = await queryAssetBalance(assetHubApi, usdcAssetID, sudo.address); + console.log('Alice USDC asset balance on Asset hub after: ', usdcBalanceAfterAliceAssethub.balance); + + const usdcBalanceAfterAliceBridgehub = await queryAssetBalance(bridgeHubApi, usdcAssetID, sudo.address); + console.log('Alice USDC asset balance on Bridge hub after: ', usdcBalanceAfterAliceBridgehub.balance); + + // Alice USDC balance should be deducted by 10, so before - after should be equal to 10 tokens on asset hub + if (str2BigInt(usdcBalanceBeforeAliceAssethub.balance) - str2BigInt(usdcBalanceAfterAliceAssethub.balance) !== BigInt(depositAmount)) { + failedTestcases.push("testcase 3 failed: Alice USDC balance on Asset hub not match") + } + + // Alice USDC balance should be added by 10 - tx fee, so after - before should be less than 10 tokens + if (str2BigInt(usdcBalanceAfterAliceBridgehub.balance) - str2BigInt(usdcBalanceBeforeAliceBridgehub.balance) >= BigInt(depositAmount)) { + failedTestcases.push("testcase 3 failed: Alice USDC balance on Bridge hub not match") + } +} + +// testcase 4: Native token deposit on Asset hub, dest on Bridge hub +async function testcase4(assetHubApi, bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 4 ...'); + + const beneficiaryAddressOnBridgehub = "5GYrSdyt7wydaQiqsnrvq11neaC2eTUBXCnXhSJKpUPT3hXP"; + const beneficiary = "0xc668b505f6a7012a50dca169757c629651bfd6cefbfc24301dea2d2cc0ab2732" // Alice_extension + + const nativeBalanceBeforeAliceAssethub = await queryBalance(assetHubApi, sudo.address); + console.log('Alice native asset balance on Asset hub before: ', nativeBalanceBeforeAliceAssethub.data.free); + + const nativeBalanceBeforeAliceBridgehub = await queryAssetBalance(bridgeHubApi, ahnAssetID, beneficiaryAddressOnBridgehub); + console.log('Alice assethub\'s native asset balance on Bridge hub before: ', nativeBalanceBeforeAliceBridgehub.balance); + + const depositAmount = 2000000000000; // 2 tokens + await teleportTokenViaXCM( + assetHubApi, + { + dest: getAssetHubTeleportDest(assetHubApi), + beneficiary: getAssetHubTeleportBeneficiary(assetHubApi, beneficiary), + assets: getAssetHubTeleportAsset(assetHubApi, getNativeMultiAsset(assetHubApi, depositAmount)), + feeAssetItem: 0, + weightLimit: getAssetHubTeleportWeightLimit(assetHubApi), + fromParachainID: 1000, + toParachainID: 1013 + }, + true, sudo) + + const nativeBalanceAfterAliceAssethub = await queryBalance(assetHubApi, sudo.address); + console.log('Alice native asset balance on Asset hub after: ', nativeBalanceAfterAliceAssethub.data.free); + + const nativeBalanceAfterAliceBridgehub = await queryAssetBalance(bridgeHubApi, ahnAssetID, beneficiaryAddressOnBridgehub); + console.log('Alice assethub\'s native asset balance on Bridge hub after: ', nativeBalanceAfterAliceBridgehub.balance); + + // Alice native token balance should be deducted by 2 and some tx fee, so before - after should be greater than 2 tokens on asset hub + if (str2BigInt(nativeBalanceBeforeAliceAssethub.data.free) - str2BigInt(nativeBalanceAfterAliceAssethub.data.free) <= BigInt(depositAmount)) { + failedTestcases.push("testcase 4 failed: Alice native asset balance on Asset hub not match") + } + + // Alice native asset token balance should be added by 2 - tx fee on bridge hub, so after - before should be less than 2 tokens + if (str2BigInt(nativeBalanceAfterAliceBridgehub.balance) - str2BigInt(nativeBalanceBeforeAliceBridgehub.balance) >= BigInt(depositAmount)) { + failedTestcases.push("testcase 4 failed: Alice native asset token balance on Bridge hub not match") + } +} + +// testcase 5: Foreign token(USDC) deposit on Asset hub, dest on sygma via Bridge hub +async function testcase5(assetHubApi, bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 5 ...'); + + const events = []; + await subEvents(bridgeHubApi, events); + + const usdcBalanceBeforeAliceAssethub = await queryAssetBalance(assetHubApi, usdcAssetID, sudo.address); + console.log('Alice USDC asset balance on Asset hub before: ', usdcBalanceBeforeAliceAssethub.balance); + + const usdcBalanceBeforeOtherTokenTransferReserveAccount = await queryAssetBalance(bridgeHubApi, usdcAssetID, OtherTokenTransferReserveAccount); + console.log('OtherTokenTransferReserveAccount USDC asset balance on Bridge hub before: ', usdcBalanceBeforeOtherTokenTransferReserveAccount.balance); + + const depositAmount = 5000000000000; // 5 tokens + await teleportTokenViaXCM( + assetHubApi, + { + dest: getAssetHubTeleportDest(assetHubApi), + beneficiary: getAssetHubTeleportBeneficiaryToSygma(assetHubApi), // EVM address: 0x1abd6948e422a1b6ced1ba28ba72ca562333df01 + assets: getAssetHubTeleportAsset(assetHubApi, getUSDCMultiAssetX2(assetHubApi, depositAmount)), + feeAssetItem: 0, + weightLimit: getAssetHubTeleportWeightLimit(assetHubApi), + fromParachainID: 1000, + toParachainID: 1013 + }, + true, sudo) + + const usdcBalanceAfterAliceAssethub = await queryAssetBalance(assetHubApi, usdcAssetID, sudo.address); + console.log('Alice USDC asset balance on Asset hub after: ', usdcBalanceAfterAliceAssethub.balance); + + const usdcBalanceAfterOtherTokenTransferReserveAccount = await queryAssetBalance(bridgeHubApi, usdcAssetID, OtherTokenTransferReserveAccount); + console.log('OtherTokenTransferReserveAccount USDC asset balance on Bridge hub after: ', usdcBalanceAfterOtherTokenTransferReserveAccount.balance); + + // Alice USDC balance should be deducted by 5, so before - after should be equal to 10 tokens on asset hub + if (str2BigInt(usdcBalanceBeforeAliceAssethub.balance) - str2BigInt(usdcBalanceAfterAliceAssethub.balance) !== BigInt(depositAmount)) { + failedTestcases.push("testcase 5 failed: Alice USDC balance on Asset hub not match") + } + + // OtherTokenTransferReserveAccount USDC balance should be added by 5 - tx fee, so after - before should be less than 5 tokens + if (str2BigInt(usdcBalanceAfterOtherTokenTransferReserveAccount.balance) - str2BigInt(usdcBalanceBeforeOtherTokenTransferReserveAccount.balance) >= BigInt(depositAmount)) { + failedTestcases.push("testcase 5 failed: OtherTokenTransferReserveAccount USDC balance on Bridge hub not match") + } + + // checking if any sygma events emitted + for (const e of events) { + console.log('sygma pallets event emitted: \x1b[32m%s\x1b[0m\n', e); + } + if (events.length === 0) { + failedTestcases.push("testcase 5 failed: sygma pallets event not emitted"); + } +} + +// testcase 6: Native token deposit on Asset hub, dest on sygma via Bridge hub +async function testcase6(assetHubApi, bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 6 ...'); + + const events = []; + await subEvents(bridgeHubApi, events); + + const nativeBalanceBeforeAliceAssethub = await queryBalance(assetHubApi, sudo.address); + console.log('Alice native asset balance on Asset hub before: ', nativeBalanceBeforeAliceAssethub.data.free); + + const nativeBalanceBeforeOtherTokenTransferReserveAccount = await queryAssetBalance(bridgeHubApi, ahnAssetID, OtherTokenTransferReserveAccount); + console.log('OtherTokenTransferReserveAccount AHN balance on Bridge hub before: ', nativeBalanceBeforeOtherTokenTransferReserveAccount.balance); + + const depositAmount = 2000000000000; // 5 tokens + await teleportTokenViaXCM( + assetHubApi, + { + dest: getAssetHubTeleportDest(assetHubApi), + beneficiary: getAssetHubTeleportBeneficiaryToSygma(assetHubApi), // EVM address: 0x1abd6948e422a1b6ced1ba28ba72ca562333df01 + assets: getAssetHubTeleportAsset(assetHubApi, getNativeMultiAsset(assetHubApi, depositAmount)), + feeAssetItem: 0, + weightLimit: getAssetHubTeleportWeightLimit(assetHubApi), + fromParachainID: 1000, + toParachainID: 1013 + }, + true, sudo) + + const nativeBalanceAfterAliceAssethub = await queryBalance(assetHubApi, sudo.address); + console.log('Alice native asset balance on Asset hub before: ', nativeBalanceAfterAliceAssethub.data.free); + + const nativeBalanceAfterOtherTokenTransferReserveAccount = await queryAssetBalance(bridgeHubApi, ahnAssetID, OtherTokenTransferReserveAccount); + console.log('OtherTokenTransferReserveAccount AHN balance on Bridge hub after: ', nativeBalanceAfterOtherTokenTransferReserveAccount.balance); + + // Alice native token balance should be deducted by 2 and some tx fee, so before - after should be greater than 2 tokens on asset hub + if (str2BigInt(nativeBalanceBeforeAliceAssethub.data.free) - str2BigInt(nativeBalanceAfterAliceAssethub.data.free) <= BigInt(depositAmount)) { + failedTestcases.push("testcase 6 failed: Alice native asset balance on Asset hub not match") + } + + // OtherTokenTransferReserveAccount AHN balance should be added by 2 - tx fee, so after - before should be less than 2 tokens + if (str2BigInt(nativeBalanceAfterOtherTokenTransferReserveAccount.balance) - str2BigInt(nativeBalanceBeforeOtherTokenTransferReserveAccount.balance) >= BigInt(depositAmount)) { + failedTestcases.push("testcase 6 failed: OtherTokenTransferReserveAccount AHN balance on Bridge hub not match") + } + + // checking if any sygma events emitted + for (const e of events) { + console.log('sygma pallets event emitted: \x1b[32m%s\x1b[0m\n', e); + } + if (events.length === 0) { + failedTestcases.push("testcase 6 failed: sygma pallets event not emitted"); + } +} + +// testcase 7: Foreign token(USDC) send from sygma relayer to Bridge hub +async function testcase7(bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 7 ...'); + + const events = []; + await subEvents(bridgeHubApi, events); + + // transfer 0.0001 USDC from sygma relayer to Alice on bridge hub + const proposal_usdc = { + origin_domain_id: 1, + deposit_nonce: 111, + resource_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0], + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 243, 16, 122, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 1, 1, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] + } + // signature is not used in the integration demo, this is just a placeholder + const signature = [180, 250, 104, 54, 47, 69, 174, 209, 145, 226, 25, 32, 184, 96, 142, 125, 103, 53, 60, 180, 107, 207, 80, 188, 9, 138, 218, 97, 50, 132, 193, 10, 6, 15, 186, 139, 6, 21, 63, 39, 157, 144, 81, 12, 81, 165, 215, 213, 200, 105, 198, 105, 115, 193, 42, 183, 145, 118, 52, 47, 45, 198, 165, 5, 28]; + + const usdcBalanceBefore = await queryAssetBalance(bridgeHubApi, usdcAssetID, sudo.address); + console.log('usdc asset balance before: ', usdcBalanceBefore.balance); + + await executeProposal(bridgeHubApi, [proposal_usdc], signature, true, sudo); + + const usdcbalanceAfter = await queryAssetBalance(bridgeHubApi, usdcAssetID, sudo.address); + console.log('usdc asset balance after: ', usdcbalanceAfter.balance); + + // USDC balance of Alice on Bridge hub should not equal + // USDC is a configured as a reserved token + if (str2BigInt(usdcbalanceAfter.balance) !== str2BigInt(usdcBalanceBefore.balance) + BigInt(100000000)) { + failedTestcases.push('testcase 7 failed: USDC balance not match') + } + + // checking if any sygma events emitted + for (const e of events) { + console.log('sygma pallets event emitted: \x1b[32m%s\x1b[0m\n', e); + } + if (events.length === 0) { + failedTestcases.push("testcase 7 failed: sygma pallets event not emitted"); + } +} + +// testcase 8: Native token of bridge hub send from sygma relayer to Bridge hub +async function testcase8(bridgeHubApi, sudo, failedTestcases) { + console.log('testcase 8 ...'); + + const events = []; + await subEvents(bridgeHubApi, events); + + // transfer 0.0001 native from sygma relayer to Alice on bridge hub + const proposal_native = { + origin_domain_id: 1, + deposit_nonce: 222, + resource_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 243, 16, 122, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 1, 1, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] + } + // signature is not used in the integration demo, this is just a placeholder + const signature = [180, 250, 104, 54, 47, 69, 174, 209, 145, 226, 25, 32, 184, 96, 142, 125, 103, 53, 60, 180, 107, 207, 80, 188, 9, 138, 218, 97, 50, 132, 193, 10, 6, 15, 186, 139, 6, 21, 63, 39, 157, 144, 81, 12, 81, 165, 215, 213, 200, 105, 198, 105, 115, 193, 42, 183, 145, 118, 52, 47, 45, 198, 165, 5, 28]; + + const nativeBalanceBefore = await queryBalance(bridgeHubApi, sudo.address); + console.log('native asset balance before: ', nativeBalanceBefore.data.free); + + await executeProposal(bridgeHubApi, [proposal_native], signature, true, sudo); + + const nativeBalanceAfter = await queryBalance(bridgeHubApi, sudo.address); + console.log('native asset balance after: ', nativeBalanceAfter.data.free); + + const before_num = BigInt(nativeBalanceBefore.data.free.replaceAll(',', '')); + const after_num = BigInt(nativeBalanceAfter.data.free.replaceAll(',', '')); + + if (after_num <= before_num) { + failedTestcases.push('testcase 8 failed: Native asset not match') + } + + // checking if any sygma events emitted + for (const e of events) { + console.log('sygma pallets event emitted: \x1b[32m%s\x1b[0m\n', e); + } + if (events.length === 0) { + failedTestcases.push("testcase 8 failed: sygma pallets event not emitted"); + } +} + +// testcase 9: Foreign token(USDC) send from sygma relayer to Bridge hub then to Asset hub via XCM +async function testcase9(bridgeHubApi, assetHubApi, sudo, failedTestcases) { + console.log('testcase 9 ...'); + + const events = []; + await subEvents(bridgeHubApi, events); + + // transfer 1 USDC token from sygma relayer to Alice on Asset hub via Bridge hub + // data: [0, 32] => amount, [33, 64] => recipient length, [64 - end] => recipient address(Alice) + const proposal_usdc = { + origin_domain_id: 1, + deposit_nonce: 333, + resource_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0], + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, 179, 167, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 1, 2, 0, 161, 15, 1, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125], + } + // signature is not used in the integration demo, this is just a placeholder + const signature = [180, 250, 104, 54, 47, 69, 174, 209, 145, 226, 25, 32, 184, 96, 142, 125, 103, 53, 60, 180, 107, 207, 80, 188, 9, 138, 218, 97, 50, 132, 193, 10, 6, 15, 186, 139, 6, 21, 63, 39, 157, 144, 81, 12, 81, 165, 215, 213, 200, 105, 198, 105, 115, 193, 42, 183, 145, 118, 52, 47, 45, 198, 165, 5, 28]; + + const usdcBalanceBeforeBridgehub = await queryAssetBalance(bridgeHubApi, usdcAssetID, OtherTokenTransferReserveAccount); + console.log('usdc asset balance before on Bridge hub: ', usdcBalanceBeforeBridgehub.balance); + + const usdcBalanceBeforeAssethub = await queryAssetBalance(assetHubApi, usdcAssetID, sudo.address); + console.log('usdc asset balance before on Asset hub: ', usdcBalanceBeforeAssethub.balance); + + await executeProposal(bridgeHubApi, [proposal_usdc], signature, true, sudo); + + const usdcBalanceAfterBridgehub = await queryAssetBalance(bridgeHubApi, usdcAssetID, OtherTokenTransferReserveAccount); + console.log('usdc asset balance after on Bridge hub: ', usdcBalanceAfterBridgehub.balance); + + const usdcBalanceAfterAssethub = await queryAssetBalance(assetHubApi, usdcAssetID, sudo.address); + console.log('usdc asset balance after on Asset hub: ', usdcBalanceAfterAssethub.balance); + + // OtherTokenTransferReserveAccount as the USDC token reserved account on Bridge hub, should be deducted by 1 USDC token + if (str2BigInt(usdcBalanceBeforeBridgehub.balance) - str2BigInt(usdcBalanceAfterBridgehub.balance) !== BigInt(1000000000000)) { + failedTestcases.push('testcase 9 failed: USDC asset not match in OtherTokenTransferReserveAccount') + } + + // the recipient on Asset hub(Alice) should receive less than 1 USDC token bcs a small port of fee is charged + if (str2BigInt(usdcBalanceAfterAssethub.balance) - str2BigInt(usdcBalanceBeforeAssethub.balance) >= BigInt(1000000000000)) { + failedTestcases.push('testcase 9 failed: USDC asset not match in recipient account on Asset hub') + } + + // checking if any sygma events emitted + for (const e of events) { + console.log('sygma pallets event emitted: \x1b[32m%s\x1b[0m\n', e); + } + if (events.length === 0) { + failedTestcases.push("testcase 9 failed: sygma pallets event not emitted"); + } +} + +// testcase 10: Native token of bridge hub send from sygma relayer to Bridge hub then to Asset hub via XCM +async function testcase10(bridgeHubApi, assetHubApi, sudo, failedTestcases) { + console.log('testcase 10 ...'); + + const events = []; + await subEvents(bridgeHubApi, events); + + // transfer 1 native from sygma relayer to Alice on Asset hub via Bridge hub + // data: [0, 32] => amount, [33, 64] => recipient length, [64 - end] => recipient address(Alice) + const proposal_native = { + origin_domain_id: 1, + deposit_nonce: 444, + resource_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, 179, 167, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 1, 2, 0, 161, 15, 1, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125], + } + // signature is not used in the integration demo, this is just a placeholder + const signature = [180, 250, 104, 54, 47, 69, 174, 209, 145, 226, 25, 32, 184, 96, 142, 125, 103, 53, 60, 180, 107, 207, 80, 188, 9, 138, 218, 97, 50, 132, 193, 10, 6, 15, 186, 139, 6, 21, 63, 39, 157, 144, 81, 12, 81, 165, 215, 213, 200, 105, 198, 105, 115, 193, 42, 183, 145, 118, 52, 47, 45, 198, 165, 5, 28]; + + const nativeBalanceBeforeBridgehub = await queryBalance(bridgeHubApi, NativeTokenTransferReserveAccount); + console.log('native asset balance before on Bridge hub: ', nativeBalanceBeforeBridgehub.data.free); + + const nativeBalanceBeforeAssethub = await queryAssetBalance(assetHubApi, bhnAssetID, sudo.address); + console.log('native asset balance before on Asset hub: ', nativeBalanceBeforeAssethub.balance); + + await executeProposal(bridgeHubApi, [proposal_native], signature, true, sudo); + + const nativeBalanceAfterBridgehub = await queryBalance(bridgeHubApi, NativeTokenTransferReserveAccount); + console.log('native asset balance after on Bridge hub: ', nativeBalanceAfterBridgehub.data.free); + + const nativeBalanceAfterAssethub = await queryAssetBalance(assetHubApi, bhnAssetID, sudo.address); + console.log('native asset balance after on Asset hub: ', nativeBalanceAfterAssethub.balance); + + // NativeTokenTransferReserveAccount as the native token reserved account on Bridge hub, should be deducted by 1 token + if (str2BigInt(nativeBalanceBeforeBridgehub.data.free) - str2BigInt(nativeBalanceAfterBridgehub.data.free) !== BigInt(1000000000000)) { + failedTestcases.push('testcase 10 failed: native asset not match in NativeTokenTransferReserveAccount on Bridge hub') + } + + // the recipient on Asset hub(Alice) should receive less than 1 BHN token bcs a small port of fee is charged + if (str2BigInt(nativeBalanceAfterAssethub.balance) - str2BigInt(nativeBalanceBeforeAssethub.balance) >= BigInt(1000000000000)) { + failedTestcases.push('testcase 10 failed: native asset not match in recipient account on Asset hub') + } + + // checking if any sygma events emitted + for (const e of events) { + console.log('sygma pallets event emitted: \x1b[32m%s\x1b[0m\n', e); + } + if (events.length === 0) { + failedTestcases.push("testcase 10 failed: sygma pallets event not emitted"); + } +} + +main().catch(console.error).finally(() => process.exit()); + diff --git a/scripts/xcm/package.json b/scripts/xcm/package.json new file mode 100644 index 0000000..75e3464 --- /dev/null +++ b/scripts/xcm/package.json @@ -0,0 +1,17 @@ +{ + "name": "sygma-substrate-pallet-xcm-scripts", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "@polkadot/api": "^10.11.2", + "@polkadot/api-augment": "^10.11.2", + "@polkadot/keyring": "^12.6.2", + "@polkadot/rpc-provider": "^10.11.2", + "@polkadot/types": "^10.11.2", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", + "bn.js": "^5.2.1", + "dotenv": "^16.0.3", + "ethers": "5.4.5" + } +} diff --git a/scripts/xcm/setup.js b/scripts/xcm/setup.js new file mode 100644 index 0000000..58a6ac7 --- /dev/null +++ b/scripts/xcm/setup.js @@ -0,0 +1,274 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +require('dotenv').config(); + +const {ApiPromise, WsProvider, Keyring} = require('@polkadot/api'); +const {cryptoWaitReady} = require('@polkadot/util-crypto'); +const { + relayChainProvider, + assetHubProvider, + bridgeHubProvider, + transferBalance, + setFeeHandler, + setMpcAddress, + registerDomain, + setFee, + setFeeRate, + getNativeAssetId, + createAsset, + setAssetMetadata, + mintAsset, + getUSDCAssetId, + getAHNMultiAsset, + getAHNAssetId, + getTTTMultiAsset, + getTTTAssetId, + queryBridgePauseStatus, + FeeReserveAccount, + NativeTokenTransferReserveAccount, + OtherTokenTransferReserveAccount, + hrmpChannelRequest, + getHRMPChannelMessage, + getHRMPChannelDest, + delay, + usdcAssetID, + usdcMinBalance, + usdcName, + usdcSymbol, + usdcDecimal, + ahnAssetID, + ahnMinBalance, + ahnName, + ahnSymbol, + ahnDecimal, + tttAssetID, + tttMinBalance, + tttName, + tttSymbol, + tttDecimal, + bhnAssetID, + bhnMinBalance, + bhnName, + bhnSymbol, + bhnDecimal, +} = require("./util"); + +const BN = require('bn.js'); +const bn1e12 = new BN(10).pow(new BN(12)); +const bn1e18 = new BN(10).pow(new BN(18)); +const bn1e20 = new BN(10).pow(new BN(20)); + +const feeHandlerType = { + BasicFeeHandler: "BasicFeeHandler", + PercentageFeeHandler: "PercentageFeeHandler", + DynamicFeeHandler: "DynamicFeeHandler" +} + +const supportedDestDomains = [ + { + domainID: 1, + chainID: 1 + } +] + +async function main() { + // relay chain + const relayChainApi = await ApiPromise.create({ + provider: relayChainProvider, + }); + // asset hub parachain + const assetHubApi = await ApiPromise.create({ + provider: assetHubProvider, + }); + // bridge hub parachain + const bridgeHubApi = await ApiPromise.create({ + provider: bridgeHubProvider, + }); + + // prepare keyring + await cryptoWaitReady(); + const keyring = new Keyring({type: 'sr25519'}); + const sudo = keyring.addFromUri('//Alice'); + + console.log('======= Relaychain setup begin ======='); + + // relaychain setup + // sovereignaccount of parachain 1000 on relaychain: + const sovereignAccount1000 = "5Ec4AhPZk8STuex8Wsi9TwDtJQxKqzPJRCH7348Xtcs9vZLJ"; + // sovereignaccount of parachain 1013 on relaychain: + const sovereignAccount1013 = "5Ec4AhPcMD9pfD1dC3vbyKXoZdZjigWthS9nEwGqaSJksLJv"; + // transfer native asset to parachain sovereignaccount on relay chain + // this is for creating the HRMP channels bcs sovereignaccount will be the sender of the OpenHRMPChannel and AcceptHRMPChannel call on the relaychain + await transferBalance(relayChainApi, sovereignAccount1000, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 native asset + await transferBalance(relayChainApi, sovereignAccount1013, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 native asset + + console.log('======= Relaychain setup is done ======='); + + // checking if parachains start to producing blocks + console.log("wait for the parachain stars producing blocks...") + let currentBlockNumber = 0; + while (currentBlockNumber < 1) { + const signedBlock = await assetHubApi.rpc.chain.getBlock(); + const blockNumber = signedBlock.block.header.number.toHuman(); + await delay(1000); + currentBlockNumber = blockNumber + } + + console.log('======= Parchain setup begin ======='); + // USDC token admin + const usdcAdmin = sudo.address; + // AHN token admin + const ahnAdmin = sudo.address; + // BHN token admin + const bhnAdmin = sudo.address; + // TTT token admin + const tttAdmin = sudo.address; + + const extensionAliceAccount = "5GYrSdyt7wydaQiqsnrvq11neaC2eTUBXCnXhSJKpUPT3hXP" + + // create USDC test asset (foreign asset) on asset hub + await createAsset(assetHubApi, usdcAssetID, usdcAdmin, usdcMinBalance, true, sudo); + await setAssetMetadata(assetHubApi, usdcAssetID, usdcName, usdcSymbol, usdcDecimal, true, sudo); + await mintAsset(assetHubApi, usdcAssetID, usdcAdmin, bn1e12.mul(new BN(100)), true, sudo); // mint 100 USDC to Alice + + // create USDC test asset (foreign asset) on bridge hub + await createAsset(bridgeHubApi, usdcAssetID, usdcAdmin, usdcMinBalance, true, sudo); + await setAssetMetadata(bridgeHubApi, usdcAssetID, usdcName, usdcSymbol, usdcDecimal, true, sudo); + await mintAsset(bridgeHubApi, usdcAssetID, usdcAdmin, bn1e12.mul(new BN(100)), true, sudo); // mint 100 USDC to Alice + + // create Asset Hub Native(AHN) test asset (foreign asset) on bridge hub + // this is for mapping the Asset Hub Native asset on Bridge hub + await createAsset(bridgeHubApi, ahnAssetID, ahnAdmin, ahnMinBalance, true, sudo); + await setAssetMetadata(bridgeHubApi, ahnAssetID, ahnName, ahnSymbol, ahnDecimal, true, sudo); + await mintAsset(bridgeHubApi, ahnAssetID, ahnAdmin, bn1e12.mul(new BN(100)), true, sudo); // mint 100 AHN to Alice + + // create Bridge Hub Native(BHN) test asset (foreign asset) on Asset hub + // this is for mapping the Bridge Hub Native asset on Asset hub + await createAsset(assetHubApi, bhnAssetID, bhnAdmin, bhnMinBalance, true, sudo); + await setAssetMetadata(assetHubApi, bhnAssetID, bhnName, bhnSymbol, bhnDecimal, true, sudo); + await mintAsset(assetHubApi, bhnAssetID, bhnAdmin, bn1e12.mul(new BN(100)), true, sudo); // mint 100 BHN to Alice + + // create TTT test asset (foreign asset) on bridge hub + // this is for mapping the local foreign token on Bridge hub + await createAsset(bridgeHubApi, tttAssetID, tttAdmin, tttMinBalance, true, sudo); + await setAssetMetadata(bridgeHubApi, tttAssetID, tttName, tttSymbol, tttDecimal, true, sudo); + await mintAsset(bridgeHubApi, tttAssetID, tttAdmin, bn1e12.mul(new BN(100)), true, sudo); // mint 100 TTT to Alice + + // make sure access segregator is set up for Alice before setting up all sygma pallet! + // sygma config + const basicFeeAmount = bn1e12.mul(new BN(1)); // 1 * 10 ** 12 + const percentageFeeRate = 500; // 5% + const feeRateLowerBound = 0; + const feeRateUpperBound = bn1e12.mul(new BN(1000)); // 1000 * 10 ** 12 + const mpcAddr = process.env.MPCADDR || "0x1c5541A79AcC662ab2D2647F3B141a3B7Cdb2Ae4"; + + // register sygma on bridge hub parachain + // register dest domains + for (const domain of supportedDestDomains) { + await registerDomain(bridgeHubApi, domain.domainID, domain.chainID, true, sudo); + } + // set fee rate for native asset for domains + for (const domain of supportedDestDomains) { + await setFeeHandler(bridgeHubApi, domain.domainID, getNativeAssetId(bridgeHubApi), feeHandlerType.PercentageFeeHandler, true, sudo) + await setFeeRate(bridgeHubApi, domain.domainID, getNativeAssetId(bridgeHubApi), percentageFeeRate, feeRateLowerBound, feeRateUpperBound, true, sudo); + } + // set fee for tokens with domains on bridge hub + for (const domain of supportedDestDomains) { + // set fee for TTT token for local token tests on bridge hub + await setFeeHandler(bridgeHubApi, domain.domainID, getTTTAssetId(bridgeHubApi), feeHandlerType.PercentageFeeHandler, true, sudo) + await setFeeRate(bridgeHubApi, domain.domainID, getTTTAssetId(bridgeHubApi), percentageFeeRate, feeRateLowerBound, feeRateUpperBound,true, sudo); + + // USDC as a foreign token is used for xcm related testcases + await setFeeHandler(bridgeHubApi, domain.domainID, getUSDCAssetId(bridgeHubApi), feeHandlerType.BasicFeeHandler, true, sudo) + await setFee(bridgeHubApi, domain.domainID, getUSDCAssetId(bridgeHubApi), basicFeeAmount,true, sudo); + + // AHN as a Native token of asset hub is used for xcm related testcases + await setFeeHandler(bridgeHubApi, domain.domainID, getAHNAssetId(bridgeHubApi), feeHandlerType.BasicFeeHandler, true, sudo) + await setFee(bridgeHubApi, domain.domainID, getAHNAssetId(bridgeHubApi), basicFeeAmount,true, sudo); + } + + // transfer some native asset to FeeReserveAccount and TransferReserveAccount as Existential Deposit(aka ED) on bridge hub + await transferBalance(bridgeHubApi, FeeReserveAccount, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 native asset + await transferBalance(bridgeHubApi, NativeTokenTransferReserveAccount, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 native asset reserved account + await transferBalance(bridgeHubApi, OtherTokenTransferReserveAccount, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 other asset reserved account + + // mint 1 TTT to reserve and fee account so that in the testcase they will not have null as balance + await mintAsset(bridgeHubApi, tttAssetID, OtherTokenTransferReserveAccount, bn1e12.mul(new BN(1)), true, sudo); // mint 1 TTT to OtherTokenTransferReserveAccount + await mintAsset(bridgeHubApi, tttAssetID, FeeReserveAccount, bn1e12.mul(new BN(1)), true, sudo); // mint 1 TTT to FeeReserveAccount + + // mint 100 USDC to reserve account so that the testcase 7 will have some init funds + await mintAsset(bridgeHubApi, usdcAssetID, OtherTokenTransferReserveAccount, bn1e12.mul(new BN(100)), true, sudo); // mint 100 USDC to OtherTokenTransferReserveAccount + + // mint 10 USDC to the sibling sovereignaccount of 1013 on asset hub + // USDC is reserved on asset hub, so in testcase 9, the reserved token from siblingSovereignAccount1013 will be transferred to the recipient on Asset hub + const siblingSovereignAccount1013= "5Eg2fntRRwLinojmk3sh5xscp7F3S6Zzm5oDVtoLTALKiypR"; + await transferBalance(assetHubApi, siblingSovereignAccount1013, bn1e12.mul(new BN(10)), true, sudo); // make sure the sibling sovereignaccount of 1013 on asset hub exists + await mintAsset(assetHubApi, usdcAssetID, siblingSovereignAccount1013, bn1e12.mul(new BN(10)), true, sudo); + + // set up MPC address(will also unpause all registered domains) on bridge hub + if (mpcAddr) { + console.log(`set up mpc address: ${mpcAddr}`); + await setMpcAddress(bridgeHubApi, mpcAddr, true, sudo); + // bridge should be unpaused by the end of the setup + for (const domain of supportedDestDomains) { + if (!await queryBridgePauseStatus(bridgeHubApi, domain.domainID)) console.log(`DestDomainID: ${domain.domainID} is readyโœ…`); + } + } + + // transfer native asset to the sovereignaccount of the other + await transferBalance(assetHubApi, sovereignAccount1013, bn1e12.mul(new BN(1)), true, sudo); // set balance to 1 native asset + await transferBalance(bridgeHubApi, sovereignAccount1000, bn1e12.mul(new BN(1)), true, sudo); // set balance to 1 native asset + + // transfer native asset to extension alice account on bridge hub + // this is for teleport native asset of Asset hub(AHN) -> Bridge hub testcase + await transferBalance(bridgeHubApi, extensionAliceAccount, bn1e12.mul(new BN(1)), true, sudo); // set balance to 1 native asset + + // mint 10 AHN to extensionAliceAccount, used in testcase 4 + await mintAsset(bridgeHubApi, ahnAssetID, extensionAliceAccount, bn1e12.mul(new BN(10)), true, sudo); + // mint 10 AHN to OtherTokenTransferReserveAccount, used in testcase 6 + await mintAsset(bridgeHubApi, ahnAssetID, OtherTokenTransferReserveAccount, bn1e12.mul(new BN(10)), true, sudo); // mint 10 AHN to OtherTokenTransferReserveAccount + + + // transfer native asset to FungiblesTransactor CheckingAccount on both parachains + // this is part of the parachain launching setup, ideally should be done by parachain team after launching, but in our testing env we are using brand-new chain, so we need to set this up. + const CheckingAccount = "5EYCAe5ijiYgWYWi1fs8Xz1td1djEtJVVnNfzvDRP4VtLL7Y"; + await transferBalance(assetHubApi, CheckingAccount, bn1e12.mul(new BN(1)), true, sudo); // set balance to 1 native asset + await transferBalance(bridgeHubApi, CheckingAccount, bn1e12.mul(new BN(1)), true, sudo); // set balance to 1 native asset + + // some other addresses need to exist as well, they are tmp accounts in pallets + const sygmaXCMTransactorAccount = "5ExVnaLuWGe8WqCpaY4jg65kMz5hefx5A2covME3RhE4Y1m1"; + const sygmaXCMTransactorAccount2 = "5D6gSNWpCcRowidpVC2k3FzmrfJjHX1Wu2NuBgsi717qtL5Y"; // when transfer BHN from sygma relayer to asset hub via bridge hub, this account received the BHN from NativeReservedAcoount on bridge hub + await transferBalance(bridgeHubApi, sygmaXCMTransactorAccount, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 native asset + await transferBalance(bridgeHubApi, sygmaXCMTransactorAccount2, bn1e12.mul(new BN(10)), true, sudo); // set balance to 10 native asset + + console.log('======= Parachain setup is done ======='); + + console.log('======= HRMP channel setup begin ======='); + // setup HRMP channel between two parachains + // init HRMP channel open request from 1000 to 1013 + const openHRMPChannelRequestEncodedData1000To1013 = "0x3c00f50300000800000000001000"; + await hrmpChannelRequest(assetHubApi, getHRMPChannelDest(assetHubApi), getHRMPChannelMessage(assetHubApi, openHRMPChannelRequestEncodedData1000To1013, 1000), 1000, 1013, true, sudo); + console.log("wait processing on the relay chain...") + await delay(10000); + // accept HRMP channel open request on 1013 + const acceptHRMPChannelRequestEncodedData1000To1013 = "0x3c01e8030000"; + await hrmpChannelRequest(bridgeHubApi, getHRMPChannelDest(bridgeHubApi), getHRMPChannelMessage(bridgeHubApi, acceptHRMPChannelRequestEncodedData1000To1013, 1013), 1000, 1013, true, sudo); + + await delay(5000); + + // init HRMP channel open request from 1013 to 1000 + const openHRMPChannelRequestEncodedData1013To1000 = "0x3c00e80300000800000000001000"; + await hrmpChannelRequest(bridgeHubApi, getHRMPChannelDest(bridgeHubApi), getHRMPChannelMessage(bridgeHubApi, openHRMPChannelRequestEncodedData1013To1000, 1013), 1013, 1000, true, sudo); + console.log("wait processing on the relay chain...") + await delay(10000); + // accept HRMP channel open request on 1000 + const acceptHRMPChannelRequestEncodedData1013To1000 = "0x3c01f5030000"; + await hrmpChannelRequest(assetHubApi, getHRMPChannelDest(assetHubApi), getHRMPChannelMessage(assetHubApi, acceptHRMPChannelRequestEncodedData1013To1000, 1000), 1013, 1000, true, sudo); + + console.log('======= HRMP Channel setup is done! ======='); + + console.log('๐Ÿš€ setup is done! ๐Ÿš€'); +} + +main().catch(console.error).finally(() => process.exit()); diff --git a/scripts/xcm/util.js b/scripts/xcm/util.js new file mode 100644 index 0000000..0af059c --- /dev/null +++ b/scripts/xcm/util.js @@ -0,0 +1,911 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +const {WsProvider} = require("@polkadot/api"); +require('dotenv').config(); + +// those account are configured in the substrate-node runtime, and are only applicable for sygma pallet standalone node, +// other parachain might have different runtime config so those account address need to be adjusted accordingly +const FeeReserveAccount = "5ELLU7ibt5ZrNEYRwohtaRBDBa3TzcWwwPELBPSWWd2mbgv3"; +const NativeTokenTransferReserveAccount = "5EYCAe5jLbHcAAMKvLFSXgCTbPrLgBJusvPwfKcaKzuf5X5e"; +const OtherTokenTransferReserveAccount = "5EYCAe5jLbHcAAMKvLFiGhk3htXY8jQncbLTDGJQnpnPMAVp"; + +// UsdcAssetId: AssetId defined in runtime.rs +// USDC: Foreign token created on both Asset hub and Bridge hub +const usdcAssetID = 2000; +const usdcMinBalance = 100; +const usdcName = "USDC test asset"; +const usdcSymbol = "USDC"; +const usdcDecimal = 12; + +// AHN: Asset hub native token on Bridge hub +const + ahnAssetID = 2001; +const ahnMinBalance = 100; +const ahnName = "Asset Hub Native"; +const ahnSymbol = "AHN"; +const ahnDecimal = 12; + +// TTT: Foreign token created on Bridge hub +const tttAssetID = 2002; +const tttMinBalance = 100; +const tttName = "Test Token Tub"; +const tttSymbol = "TTT"; +const tttDecimal = 12; + +// BHN: Bridge hub native token on Asset hub +const bhnAssetID = 2003; +const bhnMinBalance = 100; +const bhnName = "Bridge Hub Native"; +const bhnSymbol = "BHN"; +const bhnDecimal = 12; + +// relay chain +const relayChainProvider = new WsProvider(process.env.RELAYCHAINENDPOINT || 'ws://127.0.0.1:9942'); +// asset hub parachain +const assetHubProvider = new WsProvider(process.env.ASSETHUBENDPOINT || 'ws://127.0.0.1:9910'); +// bridge hub parachain +const bridgeHubProvider = new WsProvider(process.env.BRIDGEHUBENDPOINT || 'ws://127.0.0.1:8943'); + +async function transferBalance(api, who, value, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to transfer balance of ${who} to ${value}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.balances.transferKeepAlive(who, value) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function setFeeHandler(api, domainID, asset, feeHandlerType, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to set fee handler on domainID ${domainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaFeeHandlerRouter.setFeeHandler(domainID, asset, feeHandlerType) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function setFee(api, domainID, asset, amount, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to set basic fee on domainID ${domainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaBasicFeeHandler.setFee(domainID, asset, amount) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function setFeeRate(api, domainID, asset, feeRate, feeRateLowerBound, feeRateUpperBound, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to set percentage fee rate on domainID ${domainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaPercentageFeeHandler.setFeeRate(domainID, asset, feeRate, feeRateLowerBound, feeRateUpperBound) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function setMpcAddress(api, mpcAddr, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to set MPC address. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaBridge.setMpcAddress(mpcAddr) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function createAsset(api, id, admin, minBalance, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to create asset: (nonce: ${nonce}) ---` + ); + + const unsub = await api.tx.assets.create(id, admin, minBalance) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function setAssetMetadata(api, id, name, symbol, decimals, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to register asset metadata: (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.assets.setMetadata(id, name, symbol, decimals) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function mintAsset(api, id, recipient, amount, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to mint asset to ${recipient}: (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.assets.mint(id, recipient, amount) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function registerDomain(api, domainID, chainID, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to register domainID ${domainID} with chainID ${chainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaBridge.registerDomain(domainID, chainID) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +function getNativeAssetId(api) { + return api.createType('StagingXcmV3MultiassetAssetId', { + Concrete: api.createType('StagingXcmV3MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV3Junctions', 'Here') + }) + }) +} + +function getNativeMultiAsset(api, amount) { + return api.createType('StagingXcmV3MultiAsset', { + id: getNativeAssetId(api), + fun: api.createType('StagingXcmV3MultiassetFungibility', { + Fungible: api.createType('Compact', amount) + }) + }) +} +function getAHNAssetId(api) { + return api.createType('StagingXcmV3MultiassetAssetId', { + Concrete: api.createType('StagingXcmV3MultiLocation', { + parents: 1, + interior: api.createType('StagingXcmV3Junctions', { + X1: + api.createType('StagingXcmV3Junction', { + Parachain: api.createType('Compact', 1000) + }) + }) + }) + }) +} + +function getAHNMultiAsset(api, amount) { + return api.createType('StagingXcmV3MultiAsset', { + id: getAHNAssetId(api), + fun: api.createType('StagingXcmV3MultiassetFungibility', { + Fungible: api.createType('Compact', amount) + }) + }) +} + +// return USDC assetID with parachain(full X3) +function getUSDCAssetId(api) { + return api.createType('StagingXcmV3MultiassetAssetId', { + Concrete: api.createType('StagingXcmV3MultiLocation', { + parents: 1, + interior: api.createType('StagingXcmV3Junctions', { + X3: [ + api.createType('StagingXcmV3Junction', { + Parachain: api.createType('Compact', 1000) + }), + api.createType('StagingXcmV3Junction', { + PalletInstance: 50 + }), + api.createType('StagingXcmV3Junction', { + GeneralIndex: 2000 + }), + ] + }) + }) + }) +} + +// return USDC assetID but without parachain(only X2) +function getUSDCAssetIdX2(api) { + return api.createType('StagingXcmV3MultiassetAssetId', { + Concrete: api.createType('StagingXcmV3MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV3Junctions', { + X2: [ + api.createType('StagingXcmV3Junction', { + PalletInstance: 50 + }), + api.createType('StagingXcmV3Junction', { + GeneralIndex: 2000 + }), + ] + }) + }) + }) +} + +function getUSDCMultiAsset(api, amount) { + return api.createType('StagingXcmV3MultiAsset', { + id: getUSDCAssetId(api), + fun: api.createType('StagingXcmV3MultiassetFungibility', { + Fungible: api.createType('Compact', amount) + }) + }) +} + +function getUSDCMultiAssetX2(api, amount) { + return api.createType('StagingXcmV3MultiAsset', { + id: getUSDCAssetIdX2(api), + fun: api.createType('StagingXcmV3MultiassetFungibility', { + Fungible: api.createType('Compact', amount) + }) + }) +} + + +// TTT is used for foriegn token on Bridge hub test +function getTTTMultiAsset(api, amount) { + return api.createType('StagingXcmV3MultiAsset', { + id: getTTTAssetId(api), + fun: api.createType('StagingXcmV3MultiassetFungibility', { + Fungible: api.createType('Compact', amount) + }) + }) +} + +function getTTTAssetId(api) { + return api.createType('StagingXcmV3MultiassetAssetId', { + Concrete: api.createType('StagingXcmV3MultiLocation', { + parents: 1, + interior: api.createType('StagingXcmV3Junctions', { + X3: [ + api.createType('StagingXcmV3Junction', { + Parachain: api.createType('Compact', 1013) + }), + api.createType('StagingXcmV3Junction', { + // 0x7379676d61 is general key of sygma + GeneralKey: { + length: 5, + data: '0x7379676d61000000000000000000000000000000000000000000000000000000' + } + }), + api.createType('StagingXcmV3Junction', { + // 0x545454 is general key of TTT + GeneralKey: { + length: 3, + data: '0x5454540000000000000000000000000000000000000000000000000000000000' + } + }), + ] + }) + }) + }) +} + +function getAssetDepositDest(api) { + return api.createType('StagingXcmV3MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV3Junctions', { + X4: [ + api.createType('StagingXcmV3Junction', { + GeneralKey: { + length: 5, + data: '0x7379676d61000000000000000000000000000000000000000000000000000000' + } + }), + api.createType('StagingXcmV3Junction', { + GeneralKey: { + length: 12, + data: '0x7379676d612d6272696467650000000000000000000000000000000000000000' + } + }), + api.createType('StagingXcmV3Junction', { + GeneralIndex: '1' + }), + api.createType('StagingXcmV3Junction', { + GeneralKey: { + length: 20, + data: '0x1abd6948e422a1b6ced1ba28ba72ca562333df01000000000000000000000000' + } + }), + ] + }) + }) +} + +// The dest of teleport tokens from Asset hub +function getAssetHubTeleportDest(api) { + return api.createType('StagingXcmVersionedMultiLocation', { + V3: api.createType('StagingXcmV3MultiLocation', { + parents: 1, + interior: api.createType('StagingXcmV3Junctions', { + X1: api.createType('StagingXcmV3Junction', { + Parachain: api.createType('Compact', 1013) + }), + }) + }) + }) +} + +// The Beneficiary of teleport tokens from Asset hub to Bridge hub +function getAssetHubTeleportBeneficiary(api, beneficiary) { + return api.createType('StagingXcmVersionedMultiLocation', { + V3: api.createType('StagingXcmV3MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV3Junctions', { + X1: api.createType('StagingXcmV3Junction', { + AccountId32: { + network: api.createType('Option', 'rococo'), + id: beneficiary, + } + }), + }) + }) + }) +} + +// The Beneficiary of teleport tokens from Asset hub to Sygma via Bridge hub +function getAssetHubTeleportBeneficiaryToSygma(api) { + return api.createType('StagingXcmVersionedMultiLocation', { + V3: api.createType('StagingXcmV3MultiLocation', getAssetDepositDest(api)) + }) +} + +// The asset of teleport tokens from Asset hub to Bridge hub +function getAssetHubTeleportAsset(api, asset) { + return api.createType('StagingXcmVersionedMultiAssets', { + V3: api.createType('StagingXcmV3MultiassetMultiAssets', [ + asset + ]) + }) +} + +// The weight limit of teleport tokens from Asset hub +function getAssetHubTeleportWeightLimit(api) { + return api.createType('StagingXcmV3WeightLimit', "Unlimited") +} + +async function executeProposal(api, proposalList, signature, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to execute dummy proposal for testing : (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaBridge.executeProposal(proposalList, signature) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function deposit(api, asset, dest, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to deposit. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sygmaBridge.deposit(asset, dest) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +function getHRMPChannelDest(api) { + return api.createType('StagingXcmVersionedMultiLocation', { + V2: api.createType('StagingXcmV2MultiLocation', { + parents: 1, + interior: api.createType('StagingXcmV3Junctions', 'Here') + }) + }) +} + +function getHRMPChannelMessage(api, encodedData, fromParaID) { + return api.createType('StagingXcmVersionedXcm', { + V2: api.createType('StagingXcmV2Xcm', [ + api.createType('StagingXcmV2Instruction', { + WithdrawAsset: [ + api.createType('StagingXcmV2MultiAsset', { + id: api.createType('StagingXcmV2MultiassetAssetId', { + Concrete: api.createType('StagingXcmV2MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV2MultilocationJunctions', 'Here') + }), + }), + fun: api.createType('StagingXcmV2MultiassetFungibility', { + Fungible: api.createType('Compact', 1000000000000) + }) + }) + ] + }), + api.createType('StagingXcmV2Instruction', { + BuyExecution: { + fees: api.createType('StagingXcmV2MultiAsset', { + id: api.createType('StagingXcmV2MultiassetAssetId', { + Concrete: api.createType('StagingXcmV2MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV2MultilocationJunctions', 'Here') + }), + }), + fun: api.createType('StagingXcmV2MultiassetFungibility', { + Fungible: api.createType('Compact', 1000000000000) + }) + }), + weightLimit: api.createType('StagingXcmV2WeightLimit', "Unlimited") + }, + }), + api.createType('StagingXcmV2Instruction', { + Transact: { + originType: api.createType('StagingXcmV2OriginKind', "Native"), + requireWeightAtMost: api.createType('Compact', 4000000000), + call: api.createType('StagingXcmDoubleEncoded', { + encoded: api.createType('Bytes', encodedData), + }), + }, + }), + api.createType('StagingXcmV2Instruction', { + RefundSurplus: {}, + }), + api.createType('StagingXcmV2Instruction', { + DepositAsset: { + assets: api.createType('StagingXcmV2MultiassetMultiAssetFilter', { + Wild: api.createType('StagingXcmV2MultiassetWildMultiAsset', "All") + }), + maxAssets: api.createType('Compact', 1), + beneficiary: api.createType('StagingXcmV2MultiLocation', { + parents: 0, + interior: api.createType('StagingXcmV2MultilocationJunctions', { + X1: api.createType('StagingXcmV2Junction', { + Parachain: api.createType('Compact', fromParaID) + }) + }) + }) + } + }) + ]) + }) +} + +async function hrmpChannelRequest(api, dest, message, fromParachainID, toParachainID, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting HRMP channel open request from ${fromParachainID} to ${toParachainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sudo + .sudo(api.tx.polkadotXcm.send(dest, message)) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +async function teleportTokenViaXCM(api, {dest, beneficiary, assets, feeAssetItem, weightLimit, fromParachainID, toParachainID}, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting Teleport Token call from ${fromParachainID} to ${toParachainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.polkadotXcm.limitedTeleportAssets(dest, beneficiary, assets, feeAssetItem, weightLimit) + .signAndSend(sudo, {nonce: nonce, era: 0}, ({ events = [], status, isError }) => { + console.log(`Current status is ${status}`); + if (status.isInBlock) { + console.log( + `Transaction included at blockHash ${status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + + console.log('Events:'); + events.forEach(({ event: { data, method, section }, phase }) => { + console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString()); + }); + } else if (status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${status.asFinalized}` + ); + unsub(); + resolve(); + } else if (isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + +// Subscribe to system events via storage +async function subEvents (api, eventsList) { + api.query.system.events((events) => { + console.log(`\nReceived ${events.length} events:`); + + // Loop through the Vec + events.forEach((record) => { + // Extract the phase, event and the event types + const { event, phase } = record; + const types = event.typeDef; + + // Show what we are busy with + // console.log(`\t${event.section}:${event.method}:: (phase=${phase.toString()})`); + // console.log(`\t\t${event.meta}`); + console.log(`${event.section}:${event.method}`); + if (event.section.startsWith("sygmaBridge") || event.section.startsWith("sygmaBridgeForwarder")) { + eventsList.push(`${event.section}:${event.method}`); + } + + // Loop through each of the parameters, displaying the type and data + // event.data.forEach((data, index) => { + // console.log(`\t\t\t${types[index].type}: ${data.toString()}`); + // }); + }); + }); +} + +async function queryBridgePauseStatus(api, domainID) { + let result = await api.query.sygmaBridge.isPaused(domainID); + return result.toJSON() +} + +async function queryAssetBalance(api, assetID, account) { + let result = await api.query.assets.account(assetID, account); + return result.toHuman() +} + +async function queryBalance(api, account) { + let result = await api.query.system.account(account); + return result.toHuman() +} + +async function queryMPCAddress(api) { + let result = await api.query.sygmaBridge.mpcAddr(); + return result.toJSON() +} + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +module.exports = { + relayChainProvider, + assetHubProvider, + bridgeHubProvider, + getNativeAssetId, + getNativeMultiAsset, + getUSDCAssetId, + getUSDCMultiAssetX2, + getUSDCMultiAsset, + getUSDCAssetIdX2, + getAHNAssetId, + getAHNMultiAsset, + getTTTMultiAsset, + getTTTAssetId, + getAssetDepositDest, + deposit, + registerDomain, + setMpcAddress, + setFee, + setFeeRate, + setFeeHandler, + executeProposal, + transferBalance, + createAsset, + setAssetMetadata, + mintAsset, + subEvents, + queryBridgePauseStatus, + queryAssetBalance, + queryBalance, + queryMPCAddress, + hrmpChannelRequest, + getHRMPChannelMessage, + getHRMPChannelDest, + teleportTokenViaXCM, + getAssetHubTeleportDest, + getAssetHubTeleportBeneficiary, + getAssetHubTeleportBeneficiaryToSygma, + getAssetHubTeleportAsset, + getAssetHubTeleportWeightLimit, + delay, + FeeReserveAccount, + NativeTokenTransferReserveAccount, + OtherTokenTransferReserveAccount, + usdcAssetID, + usdcMinBalance, + usdcName, + usdcSymbol, + usdcDecimal, + ahnAssetID, + ahnMinBalance, + ahnName, + ahnSymbol, + ahnDecimal, + tttAssetID, + tttMinBalance, + tttName, + tttSymbol, + tttDecimal, + bhnAssetID, + bhnMinBalance, + bhnName, + bhnSymbol, + bhnDecimal, +} diff --git a/substrate-node/parachain/runtime/src/lib.rs b/substrate-node/parachain/runtime/src/lib.rs index 655b911..e4a7e59 100644 --- a/substrate-node/parachain/runtime/src/lib.rs +++ b/substrate-node/parachain/runtime/src/lib.rs @@ -883,17 +883,18 @@ impl ExtractDestinationData for DestinationDataParser { match (dest.parents, &dest.interior) { ( 0, - Junctions::X2( + Junctions::X3( + GeneralKey { length: path_len, data: sygma_path }, + GeneralIndex(dest_domain_id), GeneralKey { length: recipient_len, data: recipient }, - GeneralKey { length: _domain_len, data: dest_domain_id }, ), ) => { - let d = u8::default(); - let domain_id = dest_domain_id.as_slice().first().unwrap_or(&d); - if *domain_id == d { - return None; + if sygma_path[..*path_len as usize] == [0x73, 0x79, 0x67, 0x6d, 0x61] { + return TryInto::::try_into(*dest_domain_id).ok().map(|domain_id| { + (recipient[..*recipient_len as usize].to_vec(), domain_id) + }); } - Some((recipient[..*recipient_len as usize].to_vec(), *domain_id)) + None }, _ => None, } diff --git a/substrate-node/parachain/runtime/src/xcm_config.rs b/substrate-node/parachain/runtime/src/xcm_config.rs index 4037bc3..38b1b77 100644 --- a/substrate-node/parachain/runtime/src/xcm_config.rs +++ b/substrate-node/parachain/runtime/src/xcm_config.rs @@ -17,6 +17,7 @@ use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; +use sp_runtime::traits::CheckedConversion; use sp_std::vec; use sp_std::{marker::PhantomData, vec::Vec}; use sygma_traits::AssetTypeIdentifier; @@ -25,12 +26,12 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, WithUniqueTopic, + FixedWeightBounds, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WithComputedOrigin, WithUniqueTopic, }; -use xcm_executor::XcmExecutor; +use xcm_executor::{traits::MatchesFungible, XcmExecutor}; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); @@ -59,7 +60,7 @@ pub type LocalAssetTransactor = CurrencyAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + NativeAssetMatcher>, // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): @@ -221,6 +222,18 @@ impl sygma_bridge_forwarder::Config for Runtime { type XCMBridge = BridgeImpl; } +pub struct NativeAssetMatcher(PhantomData); +impl> MatchesFungible for NativeAssetMatcher { + fn matches_fungible(a: &MultiAsset) -> Option { + match (&a.id, &a.fun) { + (Concrete(_), Fungible(ref amount)) if C::is_native_asset(a) => { + CheckedConversion::checked_from(*amount) + }, + _ => None, + } + } +} + /// NativeAssetTypeIdentifier impl AssetTypeIdentifier for XCMAssetTransactor /// This impl is only for local mock purpose, the integrated parachain might have their own version pub struct NativeAssetTypeIdentifier(PhantomData); diff --git a/xcm-bridge/src/lib.rs b/xcm-bridge/src/lib.rs index 1b9e52b..d168607 100644 --- a/xcm-bridge/src/lib.rs +++ b/xcm-bridge/src/lib.rs @@ -155,7 +155,7 @@ pub mod pallet { self.origin, xcm_instructions.clone(), hash, - message_weight, + self.weight, message_weight, ) .ensure_complete() diff --git a/zombienet/bridge_hub_rococo_local_network.toml b/zombienet/bridge_hub_rococo_local_network.toml index ccf13c8..bcb66de 100644 --- a/zombienet/bridge_hub_rococo_local_network.toml +++ b/zombienet/bridge_hub_rococo_local_network.toml @@ -33,11 +33,10 @@ cumulus_based = true rpc_port = 8933 ws_port = 8943 args = [ - "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", + "-lparachain=warn,runtime::bridge-hub=debug,runtime::bridge=trace,runtime::bridge-dispatch=debug,bridge=trace,runtime::bridge-messages=debug,xcm=trace", ] extra_args = [ - "--force-authoring", "--no-mdns", "--bootnodes {{'bridge-hub-bob-collator'|zombie('multiAddress')}}", - "-- --port 41333 --rpc-port 48933 --ws-port 48943 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + "--force-authoring", "--no-mdns", "--bootnodes {{'bridge-hub-bob-collator'|zombie('multiAddress')}}", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" ] # run bob as parachain collator @@ -48,11 +47,10 @@ cumulus_based = true rpc_port = 8934 ws_port = 8944 args = [ - "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", + "-lparachain=warn,runtime::bridge-hub=debug,runtime::bridge=trace,runtime::bridge-dispatch=debug,bridge=trace,runtime::bridge-messages=debug,xcm=trace", ] extra_args = [ - "--force-authoring", "--no-mdns", "--bootnodes {{'bridge-hub-alice-collator'|zombie('multiAddress')}}", - "-- --port 41334 --rpc-port 48934 --ws-port 48944 --no-mdns", "--bootnodes {{'bob-validator'|zombie('multiAddress')}}" + "--force-authoring", "--no-mdns", "--bootnodes {{'bridge-hub-alice-collator'|zombie('multiAddress')}}", "--bootnodes {{'bob-validator'|zombie('multiAddress')}}" ] [[parachains]] @@ -66,32 +64,34 @@ cumulus_based = true ws_port = 9910 command = "./polkadot-parachain" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", + "-lparachain=warn,runtime::bridge-hub=debug,runtime::bridge=trace,runtime::bridge-dispatch=debug,bridge=trace,runtime::bridge-messages=debug,xcm=trace", ] extra_args = [ - "--no-mdns", "--bootnodes {{'asset-hub-bob-collator'|zombie('multiAddress')}}", - "-- --port 51333 --rpc-port 58933 --ws-port 58943 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + "--no-mdns", "--bootnodes {{'asset-hub-bob-collator'|zombie('multiAddress')}}", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" ] [[parachains.collators]] name = "asset-hub-bob-collator" + rpc_port = 9811 + ws_port = 9810 command = "./polkadot-parachain" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", + "-lparachain=warn,runtime::bridge-hub=debug,runtime::bridge=trace,runtime::bridge-dispatch=debug,bridge=trace,runtime::bridge-messages=debug,xcm=trace", ] extra_args = [ - "--no-mdns", "--bootnodes {{'asset-hub-alice-collator'|zombie('multiAddress')}}", - "-- --port 51433 --rpc-port 58833 --ws-port 58843 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + "--no-mdns", "--bootnodes {{'asset-hub-alice-collator'|zombie('multiAddress')}}", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" ] -[[hrmp_channels]] -sender = 1000 -recipient = 1013 -max_capacity = 4 -max_message_size = 524288 - -[[hrmp_channels]] -sender = 1013 -recipient = 1000 -max_capacity = 4 -max_message_size = 524288 +# TODO: open an issue on zombienet repo +# for now, using setup scripts to build HRMP channels +#[[hrmp_channels]] +#sender = 1000 +#recipient = 1013 +#max_capacity = 8 +#max_message_size = 1048576 +# +#[[hrmp_channels]] +#sender = 1013 +#recipient = 1000 +#max_capacity = 8 +#max_message_size = 1048576