difftreelog
Merge pull request #1024 from UniqueNetwork/refactor/foreign-assets-as-thin-proxy
in: master
Refactor/foreign assets as thin proxy
50 files changed
.envdiffbeforeafterboth--- a/.env
+++ b/.env
@@ -16,7 +16,7 @@
KUSAMA_MAINNET_BRANCH=release-v1.0.0
STATEMINE_BUILD_BRANCH=release-parachains-v9430
-KARURA_BUILD_BRANCH=release-karura-2.21.0
+KARURA_BUILD_BRANCH=xnft-poc
MOONRIVER_BUILD_BRANCH=runtime-2500
SHIDEN_BUILD_BRANCH=v5.18.0
QUARTZ_MAINNET_BRANCH=release-v10010063
.github/workflows/ci-develop.ymldiffbeforeafterboth--- a/.github/workflows/ci-develop.yml
+++ b/.github/workflows/ci-develop.yml
@@ -35,6 +35,11 @@
if: github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'CI-xcm')
uses: ./.github/workflows/xcm.yml
secrets: inherit
+
+ xnft:
+ if: github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'CI-xnft')
+ uses: ./.github/workflows/xnft.yml
+ secrets: inherit
collator-selection:
if: github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'CI-collator-selection')
.github/workflows/ci-master.ymldiffbeforeafterboth--- a/.github/workflows/ci-master.yml
+++ b/.github/workflows/ci-master.yml
@@ -31,6 +31,10 @@
xcm:
uses: ./.github/workflows/xcm.yml
secrets: inherit # pass all secrets from initial workflow to nested
+
+ xnft:
+ uses: ./.github/workflows/xnft.yml
+ secrets: inherit # pass all secrets from initial workflow to nested
collator-selection:
uses: ./.github/workflows/collator-selection.yml
.github/workflows/xnft.ymldiffbeforeafterboth--- /dev/null
+++ b/.github/workflows/xnft.yml
@@ -0,0 +1,215 @@
+name: xnft-testnet
+
+# Controls when the action will run.
+on:
+ workflow_call:
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+#Define Workflow variables
+env:
+ REPO_URL: ${{ github.server_url }}/${{ github.repository }}
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ prepare-execution-marix:
+ name: Prepare execution matrix
+
+ runs-on: [self-hosted-ci]
+ outputs:
+ matrix: ${{ steps.create_matrix.outputs.matrix }}
+
+ steps:
+ - name: Clean Workspace
+ uses: AutoModality/action-clean@v1.1.0
+
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v3.1.0
+ with:
+ ref: ${{ github.head_ref }} #Checking out head commit
+
+ - name: Read .env file
+ uses: xom9ikk/dotenv@v2
+
+ - name: Create Execution matrix
+ uses: CertainLach/create-matrix-action@v4
+ id: create_matrix
+ with:
+ matrix: |
+ network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, runtest {all-quartz}, runtime_features {quartz-runtime}
+
+ xnft:
+ needs: prepare-execution-marix
+ # The type of runner that the job will run on
+ runs-on: [XL]
+
+ timeout-minutes: 600
+
+ name: ${{ matrix.network }}
+
+ continue-on-error: true #Do not stop testing of matrix runs failed. As it decided during PR review - it required 50/50& Let's check it with false.
+
+ strategy:
+ matrix:
+ include: ${{fromJson(needs.prepare-execution-marix.outputs.matrix)}}
+
+ steps:
+ - name: Skip if pull request is in Draft
+ if: github.event.pull_request.draft == true
+ run: exit 1
+
+ - name: Clean Workspace
+ uses: AutoModality/action-clean@v1.1.0
+
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v3.1.0
+ with:
+ ref: ${{ github.head_ref }} #Checking out head commit
+
+ # Prepare SHA
+ - name: Prepare SHA
+ uses: ./.github/actions/prepare
+
+ - name: Read .env file
+ uses: xom9ikk/dotenv@v2
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2.1.0
+ with:
+ username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
+
+ # Check POLKADOT version and build it if it doesn't exist in repository
+ - name: Generate ENV related extend Dockerfile file for POLKADOT
+ uses: cuchi/jinja2-action@v1.2.0
+ with:
+ template: .docker/Dockerfile-polkadot.j2
+ output_file: .docker/Dockerfile-polkadot.${{ matrix.relay_branch }}.yml
+ variables: |
+ RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+ POLKADOT_BUILD_BRANCH=${{ matrix.relay_branch }}
+
+ - name: Check if the dockerhub repository contains the needed version POLKADOT
+ run: |
+ # aquire token
+ TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ secrets.CORE_DOCKERHUB_USERNAME }}'", "password": "'${{ secrets.CORE_DOCKERHUB_TOKEN }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+ export TOKEN=$TOKEN
+
+ # Get TAGS from DOCKERHUB POLKADOT repository
+ POLKADOT_TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/uniquenetwork/builder-polkadot/tags/?page_size=100 | jq -r '."results"[]["name"]')
+ # Show TAGS
+ echo "POLKADOT TAGS:"
+ echo $POLKADOT_TAGS
+ # Check correct version POLKADOT and build it if it doesn't exist in POLKADOT TAGS
+ if [[ ${POLKADOT_TAGS[*]} =~ (^|[[:space:]])"${{ matrix.relay_branch }}"($|[[:space:]]) ]]; then
+ echo "Repository has needed POLKADOT version";
+ docker pull uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}
+ else
+ echo "Repository has not needed POLKADOT version, so build it";
+ cd .docker/ && docker build --file ./Dockerfile-polkadot.${{ matrix.relay_branch }}.yml --tag uniquenetwork/builder-polkadot:${{ matrix.relay_branch }} .
+ echo "Push needed POLKADOT version to the repository";
+ docker push uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}
+ fi
+ shell: bash
+
+ # Check ACALA version and build it if it doesn't exist in repository
+ - name: Generate ENV related extend Dockerfile file for ACALA
+ uses: cuchi/jinja2-action@v1.2.0
+ with:
+ template: .docker/Dockerfile-acala.j2
+ output_file: .docker/Dockerfile-acala.${{ matrix.acala_version }}.yml
+ variables: |
+ RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+ ACALA_BUILD_BRANCH=${{ matrix.acala_version }}
+
+ - name: Check if the dockerhub repository contains the needed ACALA version
+ run: |
+ # aquire token
+ TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ secrets.CORE_DOCKERHUB_USERNAME }}'", "password": "'${{ secrets.CORE_DOCKERHUB_TOKEN }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+ export TOKEN=$TOKEN
+
+ # Get TAGS from DOCKERHUB repository
+ ACALA_TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/uniquenetwork/builder-acala/tags/?page_size=100 | jq -r '."results"[]["name"]')
+ # Show TAGS
+ echo "ACALA TAGS:"
+ echo $ACALA_TAGS
+ # Check correct version ACALA and build it if it doesn't exist in ACALA TAGS
+ if [[ ${ACALA_TAGS[*]} =~ (^|[[:space:]])"${{ matrix.acala_version }}"($|[[:space:]]) ]]; then
+ echo "Repository has needed ACALA version";
+ docker pull uniquenetwork/builder-acala:${{ matrix.acala_version }}
+ else
+ echo "Repository has not needed ACALA version, so build it";
+ cd .docker/ && docker build --file ./Dockerfile-acala.${{ matrix.acala_version }}.yml --tag uniquenetwork/builder-acala:${{ matrix.acala_version }} .
+ echo "Push needed ACALA version to the repository";
+ docker push uniquenetwork/builder-acala:${{ matrix.acala_version }}
+ fi
+ shell: bash
+
+ - name: Build unique-chain
+ run: |
+ docker build --file .docker/Dockerfile-unique \
+ --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }} \
+ --build-arg RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} \
+ --tag uniquenetwork/ci-xnft-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }} \
+ .
+
+ - name: Push docker image version
+ run: docker push uniquenetwork/ci-xnft-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}
+
+ - uses: actions/setup-node@v3.5.1
+ with:
+ node-version: 18
+
+ - name: Clone xnft-tests
+ run: git clone https://github.com/UniqueNetwork/xnft-tests.git
+
+ - name: Install baedeker
+ uses: UniqueNetwork/baedeker-action/setup@built
+
+ - name: Setup library
+ run: mkdir -p .baedeker/vendor/ && git clone https://github.com/UniqueNetwork/baedeker-library .baedeker/vendor/baedeker-library
+
+ - name: Start network
+ uses: UniqueNetwork/baedeker-action@built
+ id: bdk
+ with:
+ jpath: |
+ .baedeker/vendor
+ tla-str: |
+ relay_spec=rococo-local
+ inputs: |
+ xnft-tests/.baedeker/testnets.jsonnet
+ snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadot':{dockerImage:'uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}'}})
+ snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/quartz':{dockerImage:'uniquenetwork/ci-xnft-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}'}})
+ snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/karura':{dockerImage:'uniquenetwork/builder-acala:${{ matrix.acala_version }}'}})
+
+ - name: Yarn install
+ working-directory: xnft-tests
+ run: |
+ yarn install
+ yarn add mochawesome
+
+ - name: Run XNFT Tests
+ working-directory: xnft-tests
+ run: |
+ NOW=$(date +%s) && yarn ${{ matrix.runtest }} --reporter mochawesome --reporter-options reportFilename=test-${NOW}
+
+ - name: XNFT Tests Report
+ uses: phoenix-actions/test-reporting@v10
+ id: test-report
+ if: success() || failure()
+ with:
+ name: XNFT Tests ${{ matrix.network }}
+ path: xnft-tests/mochawesome-report/test-*.json
+ reporter: mochawesome-json
+ fail-on-error: 'false'
+
+ - name: Clean Workspace
+ if: always()
+ uses: AutoModality/action-clean@v1.1.0
+
+ - name: Remove builder cache
+ if: always()
+ run: |
+ docker system prune -a -f
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2567,15 +2567,15 @@
[[package]]
name = "data-encoding"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "data-encoding-macro"
-version = "0.1.13"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99"
+checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e"
dependencies = [
"data-encoding",
"data-encoding-macro-internal",
@@ -2583,9 +2583,9 @@
[[package]]
name = "data-encoding-macro-internal"
-version = "0.1.11"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772"
+checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3"
dependencies = [
"data-encoding",
"syn 1.0.109",
@@ -4678,21 +4678,21 @@
[[package]]
name = "if-addrs"
-version = "0.7.0"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9"
+checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a"
dependencies = [
"libc",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
name = "if-watch"
-version = "3.1.0"
+version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbb892e5777fe09e16f3d44de7802f4daa7267ecbe8c466f19d94e25bb0c303e"
+checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e"
dependencies = [
- "async-io 1.13.0",
+ "async-io 2.2.0",
"core-foundation",
"fnv",
"futures",
@@ -6557,7 +6557,6 @@
"impl-trait-for-tuples",
"log",
"num_enum",
- "orml-tokens",
"orml-traits",
"orml-vesting",
"orml-xcm-support",
@@ -6706,26 +6705,9 @@
]
[[package]]
-name = "orml-tokens"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
-dependencies = [
- "frame-support",
- "frame-system",
- "log",
- "orml-traits",
- "parity-scale-codec",
- "scale-info",
- "serde",
- "sp-arithmetic",
- "sp-runtime",
- "sp-std",
-]
-
-[[package]]
name = "orml-traits"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
dependencies = [
"frame-support",
"impl-trait-for-tuples",
@@ -6744,8 +6726,8 @@
[[package]]
name = "orml-utilities"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
dependencies = [
"frame-support",
"parity-scale-codec",
@@ -6759,8 +6741,8 @@
[[package]]
name = "orml-vesting"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
dependencies = [
"frame-support",
"frame-system",
@@ -6774,8 +6756,8 @@
[[package]]
name = "orml-xcm-support"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
dependencies = [
"frame-support",
"orml-traits",
@@ -6788,8 +6770,8 @@
[[package]]
name = "orml-xtokens"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
dependencies = [
"cumulus-primitives-core",
"frame-support",
@@ -7446,7 +7428,6 @@
"frame-support",
"frame-system",
"log",
- "orml-tokens",
"pallet-balances",
"pallet-common",
"pallet-fungible",
@@ -10150,7 +10131,6 @@
"impl-trait-for-tuples",
"log",
"num_enum",
- "orml-tokens",
"orml-traits",
"orml-vesting",
"orml-xcm-support",
@@ -13695,9 +13675,9 @@
[[package]]
name = "spinners"
-version = "4.1.0"
+version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab"
+checksum = "a0ef947f358b9c238923f764c72a4a9d42f2d637c46e059dbd319d6e7cfb4f82"
dependencies = [
"lazy_static",
"maplit",
@@ -15028,7 +15008,6 @@
"impl-trait-for-tuples",
"log",
"num_enum",
- "orml-tokens",
"orml-traits",
"orml-vesting",
"orml-xcm-support",
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -199,7 +199,6 @@
try-runtime-cli = "0.35.0"
# ORML
-orml-tokens = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
orml-traits = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
orml-vesting = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
orml-xcm-support = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
js-packages/playgrounds/types.tsdiffbeforeafterboth--- a/js-packages/playgrounds/types.ts
+++ b/js-packages/playgrounds/types.ts
@@ -160,7 +160,7 @@
External = 1,
/// Supports ERC721Metadata
Erc721metadata = 64,
- /// Tokens in foreign collections can be transferred, but not burnt
+ /// A collection of foreign assets
Foreign = 128,
}
js-packages/playgrounds/types.xcm.tsdiffbeforeafterboth--- a/js-packages/playgrounds/types.xcm.ts
+++ b/js-packages/playgrounds/types.xcm.ts
@@ -27,10 +27,3 @@
conviction: number,
},
}
-
-export interface IForeignAssetMetadata {
- name?: number | Uint8Array,
- symbol?: string,
- decimals?: number,
- minimalBalance?: bigint,
-}
\ No newline at end of file
js-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -2,7 +2,7 @@
import type {IKeyringPair} from '@polkadot/types/types';
import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique.js';
import type {ILogger, TSigner, TSubstrateAccount} from './types.js';
-import type {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm.js';
+import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm.js';
export class XcmChainHelper extends ChainHelperBase {
@@ -104,22 +104,17 @@
}
export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
- async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+ async register(signer: TSigner, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
await this.helper.executeExtrinsic(
signer,
- 'api.tx.foreignAssets.registerForeignAsset',
- [ownerAddress, location, metadata],
+ 'api.tx.foreignAssets.forceRegisterForeignAsset',
+ [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode],
true,
);
}
- async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
- await this.helper.executeExtrinsic(
- signer,
- 'api.tx.foreignAssets.updateForeignAsset',
- [foreignAssetId, location, metadata],
- true,
- );
+ async foreignCollectionId(assetId: any) {
+ return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON();
}
}
@@ -256,6 +251,10 @@
await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
}
+ async assetInfo(assetId: number | bigint) {
+ return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON();
+ }
+
async account(assetId: string | number | bigint, address: string) {
const accountAsset = (
await this.helper.callRpc('api.query.assets.account', [assetId, address])
js-packages/tests/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/js-packages/tests/eth/api/CollectionHelpers.sol
+++ b/js-packages/tests/eth/api/CollectionHelpers.sol
@@ -132,7 +132,7 @@
type CollectionFlags is uint8;
library CollectionFlagsLib {
- /// Tokens in foreign collections can be transferred, but not burnt
+ /// A collection of foreign assets
CollectionFlags constant foreignField = CollectionFlags.wrap(128);
/// Supports ERC721Metadata
CollectionFlags constant erc721metadataField = CollectionFlags.wrap(64);
js-packages/tests/pallet-presence.test.tsdiffbeforeafterboth--- a/js-packages/tests/pallet-presence.test.ts
+++ b/js-packages/tests/pallet-presence.test.ts
@@ -47,7 +47,6 @@
'nonfungible',
'charging',
'configuration',
- 'tokens',
'xtokens',
'maintenance',
];
js-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -505,4 +505,27 @@
await expectFailedToTransact(helper, messageSent);
});
}
+
+ async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {
+ return await usingPlaygrounds(async (helper) => {
+ const relayLocation = {
+ parents: 1,
+ interior: 'Here',
+ };
+ const relayAssetId = {Concrete: relayLocation};
+
+ const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);
+ if(relayCollectionId == null) {
+ const name = 'Relay Tokens';
+ const tokenPrefix = 'xDOT';
+ const decimals = 10;
+ await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});
+
+ return await helper.foreignAssets.foreignCollectionId(relayAssetId);
+ } else {
+ console.log('Relay foreign collection is already registered');
+ return relayCollectionId;
+ }
+ });
+ }
}
js-packages/tests/xcm/xcmOpal.test.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcmOpal.test.ts
+++ b/js-packages/tests/xcm/xcmOpal.test.ts
@@ -17,6 +17,7 @@
import type {IKeyringPair} from '@polkadot/types/types';
import config from '../config.js';
import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
+import {XcmTestHelper} from './xcm.types.js';
const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095);
@@ -25,11 +26,11 @@
const westmintUrl = config.westmintUrl;
const STATEMINE_PALLET_INSTANCE = 50;
-const ASSET_ID = 100;
-const ASSET_METADATA_DECIMALS = 18;
-const ASSET_METADATA_NAME = 'USDT';
-const ASSET_METADATA_DESCRIPTION = 'USDT';
-const ASSET_METADATA_MINIMAL_BALANCE = 1n;
+const USDT_ASSET_ID = 100;
+const USDT_ASSET_METADATA_DECIMALS = 18;
+const USDT_ASSET_METADATA_NAME = 'USDT';
+const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
+const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
const RELAY_DECIMALS = 12;
const WESTMINT_DECIMALS = 12;
@@ -37,7 +38,9 @@
const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n;
// 10,000.00 (ten thousands) USDT
-const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+const USDT_ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+
+const testHelper = new XcmTestHelper('opal');
describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => {
let alice: IKeyringPair;
@@ -57,57 +60,85 @@
let balanceBobRelayTokenBefore: bigint;
let balanceBobRelayTokenAfter: bigint;
+ let usdtCollectionId: number;
+ let relayCollectionId: number;
before(async () => {
await usingPlaygrounds(async (_helper, privateKey) => {
alice = await privateKey('//Alice');
bob = await privateKey('//Bob'); // funds donor
+
+ relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
});
await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
- // 350.00 (three hundred fifty) DOT
- const fundingAmount = 3_500_000_000_000n;
+ const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+ if(assetInfo == null) {
+ await helper.assets.create(
+ alice,
+ USDT_ASSET_ID,
+ alice.address,
+ USDT_ASSET_METADATA_MINIMAL_BALANCE,
+ );
+ await helper.assets.setMetadata(
+ alice,
+ USDT_ASSET_ID,
+ USDT_ASSET_METADATA_NAME,
+ USDT_ASSET_METADATA_DESCRIPTION,
+ USDT_ASSET_METADATA_DECIMALS,
+ );
+ } else {
+ console.log('The USDT asset is already registered on AssetHub');
+ }
+
+ await helper.assets.mint(
+ alice,
+ USDT_ASSET_ID,
+ alice.address,
+ USDT_ASSET_AMOUNT,
+ );
- await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE);
- await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
- await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT);
+ const sovereignFundingAmount = 3_500_000_000n;
// funding parachain sovereing account (Parachain: 2095)
const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
- await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount);
+ await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);
});
-
await usingPlaygrounds(async (helper) => {
const location = {
- V2: {
- parents: 1,
- interior: {X3: [
- {
- Parachain: STATEMINE_CHAIN,
- },
- {
- PalletInstance: STATEMINE_PALLET_INSTANCE,
- },
- {
- GeneralIndex: ASSET_ID,
- },
- ]},
- },
+ parents: 1,
+ interior: {X3: [
+ {
+ Parachain: STATEMINE_CHAIN,
+ },
+ {
+ PalletInstance: STATEMINE_PALLET_INSTANCE,
+ },
+ {
+ GeneralIndex: USDT_ASSET_ID,
+ },
+ ]},
};
+ const assetId = {Concrete: location};
- const metadata =
- {
- name: ASSET_ID,
- symbol: ASSET_METADATA_NAME,
- decimals: ASSET_METADATA_DECIMALS,
- minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,
- };
- await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+ if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
+ const tokenPrefix = USDT_ASSET_METADATA_NAME;
+ await helper.getSudo().foreignAssets.register(
+ alice,
+ assetId,
+ USDT_ASSET_METADATA_NAME,
+ tokenPrefix,
+ {Fungible: USDT_ASSET_METADATA_DECIMALS},
+ );
+ } else {
+ console.log('Foreign collection is already registered on Opal');
+ }
+
balanceOpalBefore = await helper.balance.getSubstrate(alice.address);
+ usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
});
-
// Providing the relay currency to the unique sender account
await usingRelayPlaygrounds(relayUrl, async (helper) => {
const destination = {
@@ -189,7 +220,7 @@
PalletInstance: STATEMINE_PALLET_INSTANCE,
},
{
- GeneralIndex: ASSET_ID,
+ GeneralIndex: USDT_ASSET_ID,
},
]},
},
@@ -216,19 +247,17 @@
expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
});
-
// ensure that asset has been delivered
await helper.wait.newBlocks(3);
- // expext collection id will be with id 1
- const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+ const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
console.log(
'[Westmint -> Opal] transaction fees on Opal: %s USDT',
- helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, ASSET_METADATA_DECIMALS),
+ helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),
);
console.log(
'[Westmint -> Opal] transaction fees on Opal: %s OPL',
@@ -261,16 +290,11 @@
const currencies: [any, bigint][] = [
[
- {
- ForeignAssetId: 0,
- },
- //10_000_000_000_000_000n,
+ usdtCollectionId,
TRANSFER_AMOUNT,
],
[
- {
- NativeAssetId: 'Parent',
- },
+ relayCollectionId,
400_000_000_000_000n,
],
];
@@ -288,7 +312,7 @@
// The USDT token never paid fees. Its amount not changed from begin value.
// Also check that xcm transfer has been succeeded
- expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true;
+ expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;
});
});
@@ -296,7 +320,7 @@
const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
balanceBobBefore = await helper.balance.getSubstrate(bob.address);
- balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+ balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
// Providing the relay currency to the unique sender account
await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -345,7 +369,7 @@
await helper.wait.newBlocks(3);
balanceBobAfter = await helper.balance.getSubstrate(bob.address);
- balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+ balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
console.log(
@@ -383,9 +407,7 @@
const currencies: any = [
[
- {
- NativeAssetId: 'Parent',
- },
+ relayCollectionId,
50_000_000_000_000_000n,
],
];
js-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/js-packages/tests/xcm/xcmQuartz.test.ts
+++ b/js-packages/tests/xcm/xcmQuartz.test.ts
@@ -19,9 +19,8 @@
import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
import {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.js';
import {hexToString} from '@polkadot/util';
-
+import {XcmTestHelper} from './xcm.types.js';
-
const STATEMINE_PALLET_INSTANCE = 50;
const TRANSFER_AMOUNT = 2000000000000000000000000n;
@@ -39,6 +38,8 @@
const SAFE_XCM_VERSION = 2;
+const testHelper = new XcmTestHelper('quartz');
+
describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {
let alice: IKeyringPair;
let bob: IKeyringPair;
@@ -57,6 +58,8 @@
let balanceBobRelayTokenBefore: bigint;
let balanceBobRelayTokenAfter: bigint;
+ let usdtCollectionId: number;
+ let relayCollectionId: number;
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
@@ -65,6 +68,8 @@
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+
+ relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
});
await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -74,21 +79,25 @@
});
await usingStateminePlaygrounds(statemineUrl, async (helper) => {
- const sovereignFundingAmount = 3_500_000_000n;
+ const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+ if(assetInfo == null) {
+ await helper.assets.create(
+ alice,
+ USDT_ASSET_ID,
+ alice.address,
+ USDT_ASSET_METADATA_MINIMAL_BALANCE,
+ );
+ await helper.assets.setMetadata(
+ alice,
+ USDT_ASSET_ID,
+ USDT_ASSET_METADATA_NAME,
+ USDT_ASSET_METADATA_DESCRIPTION,
+ USDT_ASSET_METADATA_DECIMALS,
+ );
+ } else {
+ console.log('The USDT asset is already registered on AssetHub');
+ }
- await helper.assets.create(
- alice,
- USDT_ASSET_ID,
- alice.address,
- USDT_ASSET_METADATA_MINIMAL_BALANCE,
- );
- await helper.assets.setMetadata(
- alice,
- USDT_ASSET_ID,
- USDT_ASSET_METADATA_NAME,
- USDT_ASSET_METADATA_DESCRIPTION,
- USDT_ASSET_METADATA_DECIMALS,
- );
await helper.assets.mint(
alice,
USDT_ASSET_ID,
@@ -96,6 +105,8 @@
USDT_ASSET_AMOUNT,
);
+ const sovereignFundingAmount = 3_500_000_000n;
+
// funding parachain sovereing account on Statemine(t).
// The sovereign account should be created before any action
// (the assets pallet on Statemine(t) check if the sovereign account exists)
@@ -106,31 +117,30 @@
await usingPlaygrounds(async (helper) => {
const location = {
- V2: {
- parents: 1,
- interior: {X3: [
- {
- Parachain: STATEMINE_CHAIN,
- },
- {
- PalletInstance: STATEMINE_PALLET_INSTANCE,
- },
- {
- GeneralIndex: USDT_ASSET_ID,
- },
- ]},
- },
+ parents: 1,
+ interior: {X3: [
+ {
+ Parachain: STATEMINE_CHAIN,
+ },
+ {
+ PalletInstance: STATEMINE_PALLET_INSTANCE,
+ },
+ {
+ GeneralIndex: USDT_ASSET_ID,
+ },
+ ]},
};
+ const assetId = {Concrete: location};
- const metadata =
- {
- name: USDT_ASSET_ID,
- symbol: USDT_ASSET_METADATA_NAME,
- decimals: USDT_ASSET_METADATA_DECIMALS,
- minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,
- };
- await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+ if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
+ const tokenPrefix = USDT_ASSET_METADATA_NAME;
+ await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+ } else {
+ console.log('Foreign collection is already registered on Quartz');
+ }
+
balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);
+ usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
});
@@ -248,8 +258,7 @@
// ensure that asset has been delivered
await helper.wait.newBlocks(3);
- // expext collection id will be with id 1
- const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+ const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);
@@ -288,15 +297,11 @@
const relayFee = 400_000_000_000_000n;
const currencies: [any, bigint][] = [
[
- {
- ForeignAssetId: 0,
- },
+ usdtCollectionId,
TRANSFER_AMOUNT,
],
[
- {
- NativeAssetId: 'Parent',
- },
+ relayCollectionId,
relayFee,
],
];
@@ -321,7 +326,7 @@
itSub('Should connect and send Relay token to Quartz', async ({helper}) => {
balanceBobBefore = await helper.balance.getSubstrate(bob.address);
- balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+ balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
await usingRelayPlaygrounds(relayUrl, async (helper) => {
const destination = {
@@ -369,7 +374,7 @@
await helper.wait.newBlocks(3);
balanceBobAfter = await helper.balance.getSubstrate(bob.address);
- balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+ balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
@@ -409,9 +414,7 @@
const currencies: any = [
[
- {
- NativeAssetId: 'Parent',
- },
+ relayCollectionId,
TRANSFER_AMOUNT_RELAY,
],
];
@@ -1018,9 +1021,7 @@
});
itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {
- const currencyId = {
- NativeAssetId: 'Here',
- };
+ const currencyId = 0;
const dest = {
V2: {
parents: 1,
js-packages/tests/xcm/xcmUnique.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import type {IKeyringPair} from '@polkadot/types/types';18import config from '../config.js';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js';20import {Event} from '@unique/playgrounds/unique.dev.js';21import {hexToString, nToBigInt} from '@polkadot/util';22import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';23import {XcmTestHelper} from './xcm.types.js';2425const STATEMINT_PALLET_INSTANCE = 50;2627const relayUrl = config.relayUrl;28const statemintUrl = config.statemintUrl;29const acalaUrl = config.acalaUrl;30const moonbeamUrl = config.moonbeamUrl;31const astarUrl = config.astarUrl;32const polkadexUrl = config.polkadexUrl;3334const RELAY_DECIMALS = 12;35const STATEMINT_DECIMALS = 12;36const ACALA_DECIMALS = 12;37const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000000000000000000000n;4142const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4344const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4546const USDT_ASSET_ID = 100;47const USDT_ASSET_METADATA_DECIMALS = 18;48const USDT_ASSET_METADATA_NAME = 'USDT';49const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';50const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;51const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5253const testHelper = new XcmTestHelper('unique');5455describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {56 let alice: IKeyringPair;57 let bob: IKeyringPair;5859 let balanceStmnBefore: bigint;60 let balanceStmnAfter: bigint;6162 let balanceUniqueBefore: bigint;63 let balanceUniqueAfter: bigint;64 let balanceUniqueFinal: bigint;6566 let balanceBobBefore: bigint;67 let balanceBobAfter: bigint;68 let balanceBobFinal: bigint;6970 let balanceBobRelayTokenBefore: bigint;71 let balanceBobRelayTokenAfter: bigint;7273 let usdtCollectionId: number;74 let relayCollectionId: number;7576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor8081 // Set the default version to wrap the first message to other chains.82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);8384 relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);85 });8687 await usingRelayPlaygrounds(relayUrl, async (helper) => {88 // Fund accounts on Statemint89 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);90 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);91 });9293 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {94 const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);95 if(assetInfo == null) {96 await helper.assets.create(97 alice,98 USDT_ASSET_ID,99 alice.address,100 USDT_ASSET_METADATA_MINIMAL_BALANCE,101 );102 await helper.assets.setMetadata(103 alice,104 USDT_ASSET_ID,105 USDT_ASSET_METADATA_NAME,106 USDT_ASSET_METADATA_DESCRIPTION,107 USDT_ASSET_METADATA_DECIMALS,108 );109 } else {110 console.log('The USDT asset is already registered on AssetHub');111 }112113 await helper.assets.mint(114 alice,115 USDT_ASSET_ID,116 alice.address,117 USDT_ASSET_AMOUNT,118 );119120 const sovereignFundingAmount = 3_500_000_000n;121122 // funding parachain sovereing account on Statemint.123 // The sovereign account should be created before any action124 // (the assets pallet on Statemint check if the sovereign account exists)125 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);126 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);127 });128129130 await usingPlaygrounds(async (helper) => {131 const location = {132 parents: 1,133 interior: {X3: [134 {135 Parachain: STATEMINT_CHAIN,136 },137 {138 PalletInstance: STATEMINT_PALLET_INSTANCE,139 },140 {141 GeneralIndex: USDT_ASSET_ID,142 },143 ]},144 };145 const assetId = {Concrete: location};146147 if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {148 const tokenPrefix = USDT_ASSET_METADATA_NAME;149 await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});150 } else {151 console.log('Foreign collection is already registered on Unique');152 }153154 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);155 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);156 });157158159 // Providing the relay currency to the unique sender account160 // (fee for USDT XCM are paid in relay tokens)161 await usingRelayPlaygrounds(relayUrl, async (helper) => {162 const destination = {163 V2: {164 parents: 0,165 interior: {X1: {166 Parachain: UNIQUE_CHAIN,167 },168 },169 }};170171 const beneficiary = {172 V2: {173 parents: 0,174 interior: {X1: {175 AccountId32: {176 network: 'Any',177 id: alice.addressRaw,178 },179 }},180 },181 };182183 const assets = {184 V2: [185 {186 id: {187 Concrete: {188 parents: 0,189 interior: 'Here',190 },191 },192 fun: {193 Fungible: TRANSFER_AMOUNT_RELAY,194 },195 },196 ],197 };198199 const feeAssetItem = 0;200201 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');202 });203204 });205206 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {207 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {208 const dest = {209 V2: {210 parents: 1,211 interior: {X1: {212 Parachain: UNIQUE_CHAIN,213 },214 },215 }};216217 const beneficiary = {218 V2: {219 parents: 0,220 interior: {X1: {221 AccountId32: {222 network: 'Any',223 id: alice.addressRaw,224 },225 }},226 },227 };228229 const assets = {230 V2: [231 {232 id: {233 Concrete: {234 parents: 0,235 interior: {236 X2: [237 {238 PalletInstance: STATEMINT_PALLET_INSTANCE,239 },240 {241 GeneralIndex: USDT_ASSET_ID,242 },243 ]},244 },245 },246 fun: {247 Fungible: TRANSFER_AMOUNT,248 },249 },250 ],251 };252253 const feeAssetItem = 0;254255 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);256 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');257258 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);259260 // common good parachain take commission in it native token261 console.log(262 '[Statemint -> Unique] transaction fees on Statemint: %s WND',263 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),264 );265 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;266267 });268269270 // ensure that asset has been delivered271 await helper.wait.newBlocks(3);272273 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});274275 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);276277 console.log(278 '[Statemint -> Unique] transaction fees on Unique: %s USDT',279 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),280 );281 console.log(282 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',283 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),284 );285 // commission has not paid in USDT token286 expect(free).to.be.equal(TRANSFER_AMOUNT);287 // ... and parachain native token288 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;289 });290291 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {292 const destination = {293 V2: {294 parents: 1,295 interior: {X2: [296 {297 Parachain: STATEMINT_CHAIN,298 },299 {300 AccountId32: {301 network: 'Any',302 id: alice.addressRaw,303 },304 },305 ]},306 },307 };308309 const relayFee = 400_000_000_000_000n;310 const currencies: [any, bigint][] = [311 [312 usdtCollectionId,313 TRANSFER_AMOUNT,314 ],315 [316 relayCollectionId,317 relayFee,318 ],319 ];320321 const feeItem = 1;322323 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');324325 // the commission has been paid in parachain native token326 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);327 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));328 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;329330 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {331 await helper.wait.newBlocks(3);332333 // The USDT token never paid fees. Its amount not changed from begin value.334 // Also check that xcm transfer has been succeeded335 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;336 });337 });338339 itSub('Should connect and send Relay token to Unique', async ({helper}) => {340 balanceBobBefore = await helper.balance.getSubstrate(bob.address);341 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});342343 await usingRelayPlaygrounds(relayUrl, async (helper) => {344 const destination = {345 V2: {346 parents: 0,347 interior: {X1: {348 Parachain: UNIQUE_CHAIN,349 },350 },351 }};352353 const beneficiary = {354 V2: {355 parents: 0,356 interior: {X1: {357 AccountId32: {358 network: 'Any',359 id: bob.addressRaw,360 },361 }},362 },363 };364365 const assets = {366 V2: [367 {368 id: {369 Concrete: {370 parents: 0,371 interior: 'Here',372 },373 },374 fun: {375 Fungible: TRANSFER_AMOUNT_RELAY,376 },377 },378 ],379 };380381 const feeAssetItem = 0;382383 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');384 });385386 await helper.wait.newBlocks(3);387388 balanceBobAfter = await helper.balance.getSubstrate(bob.address);389 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});390391 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;392 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;393 console.log(394 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',395 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),396 );397 console.log(398 '[Relay (Westend) -> Unique] transaction fees: %s WND',399 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),400 );401 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);402 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;403 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;404 });405406 itSub('Should connect and send Relay token back', async ({helper}) => {407 let relayTokenBalanceBefore: bigint;408 let relayTokenBalanceAfter: bigint;409 await usingRelayPlaygrounds(relayUrl, async (helper) => {410 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);411 });412413 const destination = {414 V2: {415 parents: 1,416 interior: {417 X1:{418 AccountId32: {419 network: 'Any',420 id: bob.addressRaw,421 },422 },423 },424 },425 };426427 const currencies: any = [428 [429 relayCollectionId,430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceUniqueTokenInit: bigint;457 let balanceUniqueTokenMiddle: bigint;458 let balanceUniqueTokenFinal: bigint;459 let balanceAcalaTokenInit: bigint;460 let balanceAcalaTokenMiddle: bigint;461 let balanceAcalaTokenFinal: bigint;462 let balanceUniqueForeignTokenInit: bigint;463 let balanceUniqueForeignTokenMiddle: bigint;464 let balanceUniqueForeignTokenFinal: bigint;465466 // computed by a test transfer from prod Unique to prod Acala.467 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48468 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)469 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;470 const acalaEps = 8n * 10n ** 16n;471472 let acalaBackwardTransferAmount: bigint;473474 before(async () => {475 await usingPlaygrounds(async (helper, privateKey) => {476 alice = await privateKey('//Alice');477 [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479 // Set the default version to wrap the first message to other chains.480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482483 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {484 const destination = {485 V2: {486 parents: 1,487 interior: {488 X1: {489 Parachain: UNIQUE_CHAIN,490 },491 },492 },493 };494495 const metadata = {496 name: 'Unique Network',497 symbol: 'UNQ',498 decimals: 18,499 minimalBalance: 1250000000000000000n,500 };501 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>502 hexToString(v.toJSON()['symbol'])) as string[];503504 if(!assets.includes('UNQ')) {505 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);506 } else {507 console.log('UNQ token already registered on Acala assetRegistry pallet');508 }509 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);510 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);511 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});512 });513514 await usingPlaygrounds(async (helper) => {515 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);516 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);517 });518 });519520 itSub('Should connect and send UNQ to Acala', async ({helper}) => {521522 const destination = {523 V2: {524 parents: 1,525 interior: {526 X1: {527 Parachain: ACALA_CHAIN,528 },529 },530 },531 };532533 const beneficiary = {534 V2: {535 parents: 0,536 interior: {537 X1: {538 AccountId32: {539 network: 'Any',540 id: randomAccount.addressRaw,541 },542 },543 },544 },545 };546547 const assets = {548 V2: [549 {550 id: {551 Concrete: {552 parents: 0,553 interior: 'Here',554 },555 },556 fun: {557 Fungible: TRANSFER_AMOUNT,558 },559 },560 ],561 };562563 const feeAssetItem = 0;564565 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');566 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);567568 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;569 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));570 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;571572 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {573 await helper.wait.newBlocks(3);574575 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});576 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);577578 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;579 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;580 acalaBackwardTransferAmount = unqIncomeTransfer;581582 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;583584 console.log(585 '[Unique -> Acala] transaction fees on Acala: %s ACA',586 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),587 );588 console.log(589 '[Unique -> Acala] transaction fees on Acala: %s UNQ',590 helper.util.bigIntToDecimals(acaUnqFees),591 );592 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));593 expect(acaFees == 0n).to.be.true;594595 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;596597 expect(598 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,599 'Acala took different income fee, check the Acala foreign asset config',600 ).to.be.true;601 });602 });603604 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {605 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {606 const destination = {607 V2: {608 parents: 1,609 interior: {610 X2: [611 {Parachain: UNIQUE_CHAIN},612 {613 AccountId32: {614 network: 'Any',615 id: randomAccount.addressRaw,616 },617 },618 ],619 },620 },621 };622623 const id = {624 ForeignAsset: 0,625 };626627 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');628 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);629 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);630631 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;632 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;633634 console.log(635 '[Acala -> Unique] transaction fees on Acala: %s ACA',636 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),637 );638 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));639640 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;641 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;642 });643644 await helper.wait.newBlocks(3);645646 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);647 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;648 expect(actuallyDelivered > 0).to.be.true;649650 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));651652 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;653 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));654 expect(unqFees == 0n).to.be.true;655 });656657 itSub('Acala can send only up to its balance', async ({helper}) => {658 // set Acala's sovereign account's balance659 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);660 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);661 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);662663 const moreThanAcalaHas = acalaBalance * 2n;664665 let targetAccountBalance = 0n;666 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);667668 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(669 targetAccount.addressRaw,670 {671 Concrete: {672 parents: 0,673 interior: 'Here',674 },675 },676 moreThanAcalaHas,677 );678679 let maliciousXcmProgramSent: any;680 const maxWaitBlocks = 3;681682 // Try to trick Unique683 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {684 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);685686 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);687 });688689 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash690 && event.outcome.isFailedToTransactAsset);691692 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);693 expect(targetAccountBalance).to.be.equal(0n);694695 // But Acala still can send the correct amount696 const validTransferAmount = acalaBalance / 2n;697 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(698 targetAccount.addressRaw,699 {700 Concrete: {701 parents: 0,702 interior: 'Here',703 },704 },705 validTransferAmount,706 );707708 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);710 });711712 await helper.wait.newBlocks(maxWaitBlocks);713714 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);715 expect(targetAccountBalance).to.be.equal(validTransferAmount);716 });717718 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {719 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);720 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);721722 const uniqueMultilocation = {723 V2: {724 parents: 1,725 interior: {726 X1: {727 Parachain: UNIQUE_CHAIN,728 },729 },730 },731 };732733 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(734 targetAccount.addressRaw,735 uniqueAssetId,736 testAmount,737 );738739 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(740 targetAccount.addressRaw,741 {742 Concrete: {743 parents: 0,744 interior: 'Here',745 },746 },747 testAmount,748 );749750 let maliciousXcmProgramFullIdSent: any;751 let maliciousXcmProgramHereIdSent: any;752 const maxWaitBlocks = 3;753754 // Try to trick Unique using full UNQ identification755 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {756 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);757758 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);759 });760761 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash762 && event.outcome.isUntrustedReserveLocation);763764 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);765 expect(accountBalance).to.be.equal(0n);766767 // Try to trick Unique using shortened UNQ identification768 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {769 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);770771 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);772 });773774 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash775 && event.outcome.isUntrustedReserveLocation);776777 accountBalance = await helper.balance.getSubstrate(targetAccount.address);778 expect(accountBalance).to.be.equal(0n);779 });780});781782describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {783 let alice: IKeyringPair;784 let randomAccount: IKeyringPair;785 let unqFees: bigint;786 let balanceUniqueTokenInit: bigint;787 let balanceUniqueTokenMiddle: bigint;788 let balanceUniqueTokenFinal: bigint;789 const maxWaitBlocks = 6;790791 before(async () => {792 await usingPlaygrounds(async (helper, privateKey) => {793 alice = await privateKey('//Alice');794 [randomAccount] = await helper.arrange.createAccounts([0n], alice);795796 // Set the default version to wrap the first message to other chains.797 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);798 });799800 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {801 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))802 .toJSON() as [])803 .map(nToBigInt).length != 0;804 /*805 Check whether the Unique token has been added806 to the whitelist, since an error will occur807 if it is added again. Needed for debugging808 when this test is run multiple times.809 */810 if(isWhitelisted) {811 console.log('UNQ token is already whitelisted on Polkadex');812 } else {813 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);814 }815816 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);817 });818819 await usingPlaygrounds(async (helper) => {820 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);821 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);822 });823 });824825 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {826827 const destination = {828 V2: {829 parents: 1,830 interior: {831 X1: {832 Parachain: POLKADEX_CHAIN,833 },834 },835 },836 };837838 const beneficiary = {839 V2: {840 parents: 0,841 interior: {842 X1: {843 AccountId32: {844 network: 'Any',845 id: randomAccount.addressRaw,846 },847 },848 },849 },850 };851852 const assets = {853 V2: [854 {855 id: {856 Concrete: {857 parents: 0,858 interior: 'Here',859 },860 },861 fun: {862 Fungible: TRANSFER_AMOUNT,863 },864 },865 ],866 };867868 const feeAssetItem = 0;869870 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');871 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);872 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);873874 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;875 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));876 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;877878 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {879 /*880 Since only the parachain part of the Polkadex881 infrastructure is launched (without their882 solochain validators), processing incoming883 assets will lead to an error.884 This error indicates that the Polkadex chain885 received a message from the Unique network,886 since the hash is being checked to ensure887 it matches what was sent.888 */889 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);890 });891 });892893894 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {895896 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(897 randomAccount.addressRaw,898 uniqueAssetId,899 TRANSFER_AMOUNT,900 );901902 let xcmProgramSent: any;903904905 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {906 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);907908 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);909 });910911 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);912913 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);914915 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);916 });917918 itSub('Polkadex can send only up to its balance', async ({helper}) => {919 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);920 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);921 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);922 const moreThanPolkadexHas = 2n * polkadexBalance;923924 const targetAccount = helper.arrange.createEmptyAccount();925926 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(927 targetAccount.addressRaw,928 {929 Concrete: {930 parents: 0,931 interior: 'Here',932 },933 },934 moreThanPolkadexHas,935 );936937 let maliciousXcmProgramSent: any;938939940 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {941 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);942943 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);944 });945946 await expectFailedToTransact(helper, maliciousXcmProgramSent);947948 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);949 expect(targetAccountBalance).to.be.equal(0n);950 });951952 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {953 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);954 const targetAccount = helper.arrange.createEmptyAccount();955956 const uniqueMultilocation = {957 V2: {958 parents: 1,959 interior: {960 X1: {961 Parachain: UNIQUE_CHAIN,962 },963 },964 },965 };966967 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(968 targetAccount.addressRaw,969 uniqueAssetId,970 testAmount,971 );972973 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(974 targetAccount.addressRaw,975 {976 Concrete: {977 parents: 0,978 interior: 'Here',979 },980 },981 testAmount,982 );983984 let maliciousXcmProgramFullIdSent: any;985 let maliciousXcmProgramHereIdSent: any;986 const maxWaitBlocks = 3;987988 // Try to trick Unique using full UNQ identification989 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {990 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);991992 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);993 });994995 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);996997 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);998 expect(accountBalance).to.be.equal(0n);9991000 // Try to trick Unique using shortened UNQ identification1001 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1002 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10031004 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1005 });10061007 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10081009 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1010 expect(accountBalance).to.be.equal(0n);1011 });1012});10131014// These tests are relevant only when1015// the the corresponding foreign assets are not registered1016describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1017 let alice: IKeyringPair;1018 let alith: IKeyringPair;10191020 const testAmount = 100_000_000_000n;1021 let uniqueParachainJunction;1022 let uniqueAccountJunction;10231024 let uniqueParachainMultilocation: any;1025 let uniqueAccountMultilocation: any;1026 let uniqueCombinedMultilocation: any;1027 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210281029 let messageSent: any;10301031 const maxWaitBlocks = 3;10321033 before(async () => {1034 await usingPlaygrounds(async (helper, privateKey) => {1035 alice = await privateKey('//Alice');10361037 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1038 uniqueAccountJunction = {1039 AccountId32: {1040 network: 'Any',1041 id: alice.addressRaw,1042 },1043 };10441045 uniqueParachainMultilocation = {1046 V2: {1047 parents: 1,1048 interior: {1049 X1: uniqueParachainJunction,1050 },1051 },1052 };10531054 uniqueAccountMultilocation = {1055 V2: {1056 parents: 0,1057 interior: {1058 X1: uniqueAccountJunction,1059 },1060 },1061 };10621063 uniqueCombinedMultilocation = {1064 V2: {1065 parents: 1,1066 interior: {1067 X2: [uniqueParachainJunction, uniqueAccountJunction],1068 },1069 },1070 };10711072 uniqueCombinedMultilocationAcala = {1073 V2: {1074 parents: 1,1075 interior: {1076 X2: [uniqueParachainJunction, uniqueAccountJunction],1077 },1078 },1079 };10801081 // Set the default version to wrap the first message to other chains.1082 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1083 });10841085 // eslint-disable-next-line require-await1086 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1087 alith = helper.account.alithAccount();1088 });1089 });1090109110921093 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1094 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1095 const id = {1096 Token: 'ACA',1097 };1098 const destination = uniqueCombinedMultilocationAcala;1099 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11001101 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1102 });11031104 await expectFailedToTransact(helper, messageSent);1105 });11061107 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1108 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1109 const id = 'SelfReserve';1110 const destination = uniqueCombinedMultilocation;1111 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11121113 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1114 });11151116 await expectFailedToTransact(helper, messageSent);1117 });11181119 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1120 await usingAstarPlaygrounds(astarUrl, async (helper) => {1121 const destinationParachain = uniqueParachainMultilocation;1122 const beneficiary = uniqueAccountMultilocation;1123 const assets = {1124 V2: [{1125 id: {1126 Concrete: {1127 parents: 0,1128 interior: 'Here',1129 },1130 },1131 fun: {1132 Fungible: testAmount,1133 },1134 }],1135 };1136 const feeAssetItem = 0;11371138 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1139 destinationParachain,1140 beneficiary,1141 assets,1142 feeAssetItem,1143 ]);11441145 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1146 });11471148 await expectFailedToTransact(helper, messageSent);1149 });11501151 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11521153 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1154 helper.arrange.createEmptyAccount().addressRaw,1155 {1156 Concrete: {1157 parents: 1,1158 interior: {1159 X1: {1160 Parachain: POLKADEX_CHAIN,1161 },1162 },1163 },1164 },1165 testAmount,1166 );11671168 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1169 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1170 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171 });11721173 await expectFailedToTransact(helper, messageSent);1174 });1175});11761177describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1178 // Unique constants1179 let alice: IKeyringPair;1180 let uniqueAssetLocation;11811182 let randomAccountUnique: IKeyringPair;1183 let randomAccountMoonbeam: IKeyringPair;11841185 // Moonbeam constants1186 let assetId: string;11871188 const uniqueAssetMetadata = {1189 name: 'xcUnique',1190 symbol: 'xcUNQ',1191 decimals: 18,1192 isFrozen: false,1193 minimalBalance: 1n,1194 };11951196 let balanceUniqueTokenInit: bigint;1197 let balanceUniqueTokenMiddle: bigint;1198 let balanceUniqueTokenFinal: bigint;1199 let balanceForeignUnqTokenInit: bigint;1200 let balanceForeignUnqTokenMiddle: bigint;1201 let balanceForeignUnqTokenFinal: bigint;1202 let balanceGlmrTokenInit: bigint;1203 let balanceGlmrTokenMiddle: bigint;1204 let balanceGlmrTokenFinal: bigint;12051206 before(async () => {1207 await usingPlaygrounds(async (helper, privateKey) => {1208 alice = await privateKey('//Alice');1209 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12101211 balanceForeignUnqTokenInit = 0n;12121213 // Set the default version to wrap the first message to other chains.1214 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1215 });12161217 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1218 const alithAccount = helper.account.alithAccount();1219 const baltatharAccount = helper.account.baltatharAccount();1220 const dorothyAccount = helper.account.dorothyAccount();12211222 randomAccountMoonbeam = helper.account.create();12231224 // >>> Sponsoring Dorothy >>>1225 console.log('Sponsoring Dorothy.......');1226 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1227 console.log('Sponsoring Dorothy.......DONE');1228 // <<< Sponsoring Dorothy <<<12291230 uniqueAssetLocation = {1231 XCM: {1232 parents: 1,1233 interior: {X1: {Parachain: UNIQUE_CHAIN}},1234 },1235 };1236 const existentialDeposit = 1n;1237 const isSufficient = true;1238 const unitsPerSecond = 1n;1239 const numAssetsWeightHint = 0;12401241 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1242 console.log('Unique asset is already registered on MoonBeam');1243 } else {1244 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1245 location: uniqueAssetLocation,1246 metadata: uniqueAssetMetadata,1247 existentialDeposit,1248 isSufficient,1249 unitsPerSecond,1250 numAssetsWeightHint,1251 });12521253 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12541255 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1256 }12571258 // >>> Acquire Unique AssetId Info on Moonbeam >>>1259 console.log('Acquire Unique AssetId Info on Moonbeam.......');12601261 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1262 console.log('UNQ asset ID is %s', assetId);1263 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1264 // >>> Acquire Unique AssetId Info on Moonbeam >>>12651266 // >>> Sponsoring random Account >>>1267 console.log('Sponsoring random Account.......');1268 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1269 console.log('Sponsoring random Account.......DONE');1270 // <<< Sponsoring random Account <<<12711272 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1273 });12741275 await usingPlaygrounds(async (helper) => {1276 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1277 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1278 });1279 });12801281 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1282 const currencyId = 0;1283 const dest = {1284 V2: {1285 parents: 1,1286 interior: {1287 X2: [1288 {Parachain: MOONBEAM_CHAIN},1289 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1290 ],1291 },1292 },1293 };1294 const amount = TRANSFER_AMOUNT;12951296 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12971298 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1299 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13001301 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1302 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1303 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13041305 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1306 await helper.wait.newBlocks(3);1307 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13081309 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1310 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1311 expect(glmrFees == 0n).to.be.true;13121313 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13141315 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1316 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1317 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1318 });1319 });13201321 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1322 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1323 const asset = {1324 V2: {1325 id: {1326 Concrete: {1327 parents: 1,1328 interior: {1329 X1: {Parachain: UNIQUE_CHAIN},1330 },1331 },1332 },1333 fun: {1334 Fungible: TRANSFER_AMOUNT,1335 },1336 },1337 };1338 const destination = {1339 V2: {1340 parents: 1,1341 interior: {1342 X2: [1343 {Parachain: UNIQUE_CHAIN},1344 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1345 ],1346 },1347 },1348 };13491350 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13511352 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13531354 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1355 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1356 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13571358 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13591360 expect(unqRandomAccountAsset).to.be.null;13611362 balanceForeignUnqTokenFinal = 0n;13631364 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1365 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1366 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1367 });13681369 await helper.wait.newBlocks(3);13701371 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1372 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1373 expect(actuallyDelivered > 0).to.be.true;13741375 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13761377 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1378 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1379 expect(unqFees == 0n).to.be.true;1380 });13811382 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1383 // set Moonbeam's sovereign account's balance1384 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1385 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1386 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13871388 const moreThanMoonbeamHas = moonbeamBalance * 2n;13891390 let targetAccountBalance = 0n;1391 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13921393 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1394 targetAccount.addressRaw,1395 {1396 Concrete: {1397 parents: 0,1398 interior: 'Here',1399 },1400 },1401 moreThanMoonbeamHas,1402 );14031404 let maliciousXcmProgramSent: any;1405 const maxWaitBlocks = 3;14061407 // Try to trick Unique1408 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1409 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14101411 // Needed to bypass the call filter.1412 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1413 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14141415 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1416 });14171418 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1419 && event.outcome.isFailedToTransactAsset);14201421 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1422 expect(targetAccountBalance).to.be.equal(0n);14231424 // But Moonbeam still can send the correct amount1425 const validTransferAmount = moonbeamBalance / 2n;1426 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1427 targetAccount.addressRaw,1428 {1429 Concrete: {1430 parents: 0,1431 interior: 'Here',1432 },1433 },1434 validTransferAmount,1435 );14361437 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1438 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14391440 // Needed to bypass the call filter.1441 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1442 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1443 });14441445 await helper.wait.newBlocks(maxWaitBlocks);14461447 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1448 expect(targetAccountBalance).to.be.equal(validTransferAmount);1449 });14501451 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1452 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1453 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14541455 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1456 targetAccount.addressRaw,1457 uniqueAssetId,1458 testAmount,1459 );14601461 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1462 targetAccount.addressRaw,1463 {1464 Concrete: {1465 parents: 0,1466 interior: 'Here',1467 },1468 },1469 testAmount,1470 );14711472 let maliciousXcmProgramFullIdSent: any;1473 let maliciousXcmProgramHereIdSent: any;1474 const maxWaitBlocks = 3;14751476 // Try to trick Unique using full UNQ identification1477 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1478 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14791480 // Needed to bypass the call filter.1481 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1482 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14831484 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1485 });14861487 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1488 && event.outcome.isUntrustedReserveLocation);14891490 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1491 expect(accountBalance).to.be.equal(0n);14921493 // Try to trick Unique using shortened UNQ identification1494 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1495 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14961497 // Needed to bypass the call filter.1498 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1499 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15001501 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1502 });15031504 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1505 && event.outcome.isUntrustedReserveLocation);15061507 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1508 expect(accountBalance).to.be.equal(0n);1509 });1510});15111512describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1513 let alice: IKeyringPair;1514 let randomAccount: IKeyringPair;15151516 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar1517 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar15181519 // Unique -> Astar1520 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1521 const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar1522 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1523 const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens15241525 // Astar -> Unique1526 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1527 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ15281529 let balanceAfterUniqueToAstarXCM: bigint;15301531 before(async () => {1532 await usingPlaygrounds(async (helper, privateKey) => {1533 alice = await privateKey('//Alice');1534 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1535 console.log('randomAccount', randomAccount.address);15361537 // Set the default version to wrap the first message to other chains.1538 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1539 });15401541 await usingAstarPlaygrounds(astarUrl, async (helper) => {1542 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1543 console.log('1. Create foreign asset and metadata');1544 await helper.getSudo().assets.forceCreate(1545 alice,1546 UNQ_ASSET_ID_ON_ASTAR,1547 alice.address,1548 UNQ_MINIMAL_BALANCE_ON_ASTAR,1549 );15501551 await helper.assets.setMetadata(1552 alice,1553 UNQ_ASSET_ID_ON_ASTAR,1554 'Unique Network',1555 'UNQ',1556 Number(UNQ_DECIMALS),1557 );15581559 console.log('2. Register asset location on Astar');1560 const assetLocation = {1561 V2: {1562 parents: 1,1563 interior: {1564 X1: {1565 Parachain: UNIQUE_CHAIN,1566 },1567 },1568 },1569 };15701571 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15721573 console.log('3. Set UNQ payment for XCM execution on Astar');1574 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1575 } else {1576 console.log('UNQ is already registered on Astar');1577 }1578 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1579 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1580 });1581 });15821583 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1584 const destination = {1585 V2: {1586 parents: 1,1587 interior: {1588 X1: {1589 Parachain: ASTAR_CHAIN,1590 },1591 },1592 },1593 };15941595 const beneficiary = {1596 V2: {1597 parents: 0,1598 interior: {1599 X1: {1600 AccountId32: {1601 network: 'Any',1602 id: randomAccount.addressRaw,1603 },1604 },1605 },1606 },1607 };16081609 const assets = {1610 V2: [1611 {1612 id: {1613 Concrete: {1614 parents: 0,1615 interior: 'Here',1616 },1617 },1618 fun: {1619 Fungible: unqToAstarTransferred,1620 },1621 },1622 ],1623 };16241625 // Initial balance is 100 UNQ1626 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1627 console.log(`Initial balance is: ${balanceBefore}`);16281629 const feeAssetItem = 0;1630 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16311632 // Balance after reserve transfer is less than 901633 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1634 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1635 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1636 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16371638 await usingAstarPlaygrounds(astarUrl, async (helper) => {1639 await helper.wait.newBlocks(3);1640 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1641 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16421643 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1644 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);16451646 expect(xcUNQbalance).to.eq(unqToAstarArrived);1647 // Astar balance does not changed1648 expect(astarBalance).to.eq(astarInitialBalance);1649 });1650 });16511652 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1653 await usingAstarPlaygrounds(astarUrl, async (helper) => {1654 const destination = {1655 V2: {1656 parents: 1,1657 interior: {1658 X1: {1659 Parachain: UNIQUE_CHAIN,1660 },1661 },1662 },1663 };16641665 const beneficiary = {1666 V2: {1667 parents: 0,1668 interior: {1669 X1: {1670 AccountId32: {1671 network: 'Any',1672 id: randomAccount.addressRaw,1673 },1674 },1675 },1676 },1677 };16781679 const assets = {1680 V2: [1681 {1682 id: {1683 Concrete: {1684 parents: 1,1685 interior: {1686 X1: {1687 Parachain: UNIQUE_CHAIN,1688 },1689 },1690 },1691 },1692 fun: {1693 Fungible: unqFromAstarTransfered,1694 },1695 },1696 ],1697 };16981699 // Initial balance is 1 ASTR1700 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1701 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1702 expect(balanceASTRbefore).to.eq(astarInitialBalance);17031704 const feeAssetItem = 0;1705 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1706 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17071708 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1709 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1710 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17111712 // Assert: xcUNQ balance correctly decreased1713 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1714 // Assert: ASTR balance is 0.996...1715 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1716 });17171718 await helper.wait.newBlocks(3);1719 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1720 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1721 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1722 });17231724 itSub('Astar can send only up to its balance', async ({helper}) => {1725 // set Astar's sovereign account's balance1726 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1727 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1728 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17291730 const moreThanAstarHas = astarBalance * 2n;17311732 let targetAccountBalance = 0n;1733 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17341735 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1736 targetAccount.addressRaw,1737 {1738 Concrete: {1739 parents: 0,1740 interior: 'Here',1741 },1742 },1743 moreThanAstarHas,1744 );17451746 let maliciousXcmProgramSent: any;1747 const maxWaitBlocks = 3;17481749 // Try to trick Unique1750 await usingAstarPlaygrounds(astarUrl, async (helper) => {1751 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17521753 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1754 });17551756 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1757 && event.outcome.isFailedToTransactAsset);17581759 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1760 expect(targetAccountBalance).to.be.equal(0n);17611762 // But Astar still can send the correct amount1763 const validTransferAmount = astarBalance / 2n;1764 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1765 targetAccount.addressRaw,1766 {1767 Concrete: {1768 parents: 0,1769 interior: 'Here',1770 },1771 },1772 validTransferAmount,1773 );17741775 await usingAstarPlaygrounds(astarUrl, async (helper) => {1776 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1777 });17781779 await helper.wait.newBlocks(maxWaitBlocks);17801781 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1782 expect(targetAccountBalance).to.be.equal(validTransferAmount);1783 });17841785 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1786 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1787 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17881789 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1790 targetAccount.addressRaw,1791 uniqueAssetId,1792 testAmount,1793 );17941795 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1796 targetAccount.addressRaw,1797 {1798 Concrete: {1799 parents: 0,1800 interior: 'Here',1801 },1802 },1803 testAmount,1804 );18051806 let maliciousXcmProgramFullIdSent: any;1807 let maliciousXcmProgramHereIdSent: any;1808 const maxWaitBlocks = 3;18091810 // Try to trick Unique using full UNQ identification1811 await usingAstarPlaygrounds(astarUrl, async (helper) => {1812 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18131814 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1815 });18161817 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1818 && event.outcome.isUntrustedReserveLocation);18191820 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1821 expect(accountBalance).to.be.equal(0n);18221823 // Try to trick Unique using shortened UNQ identification1824 await usingAstarPlaygrounds(astarUrl, async (helper) => {1825 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18261827 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1828 });18291830 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1831 && event.outcome.isUntrustedReserveLocation);18321833 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1834 expect(accountBalance).to.be.equal(0n);1835 });1836});node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -167,7 +167,6 @@
.map(|k| (k, 1 << 100))
.collect(),
},
- tokens: TokensConfig { balances: vec![] },
sudo: SudoConfig {
key: Some($root_key),
},
@@ -231,7 +230,6 @@
.map(|k| (k, 1 << 100))
.collect(),
},
- tokens: TokensConfig { balances: vec![] },
sudo: SudoConfig {
key: Some($root_key),
},
pallets/balances-adapter/src/common.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -1,10 +1,16 @@
use alloc::{vec, vec::Vec};
use core::marker::PhantomData;
-use frame_support::{ensure, fail, weights::Weight};
+use frame_support::{
+ ensure, fail,
+ traits::tokens::{fungible::Mutate, Fortitude, Precision},
+ weights::Weight,
+};
use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo, Error as CommonError};
-use up_data_structs::TokenId;
+use pallet_common::{
+ erc::CrossAccountId, CommonCollectionOperations, CommonWeightInfo, Error as CommonError,
+};
+use up_data_structs::{budget::Budget, TokenId};
use crate::{Config, NativeFungibleHandle, Pallet};
@@ -332,8 +338,8 @@
0
}
- fn refungible_extensions(&self) -> Option<&dyn pallet_common::RefungibleExtensions<T>> {
- None
+ fn xcm_extensions(&self) -> Option<&dyn pallet_common::XcmExtensions<T>> {
+ Some(self)
}
fn set_allowance_for_all(
@@ -360,3 +366,72 @@
fail!(<CommonError<T>>::UnsupportedOperation);
}
}
+
+impl<T: Config> pallet_common::XcmExtensions<T> for NativeFungibleHandle<T> {
+ fn create_item_internal(
+ &self,
+ _depositor: &<T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ data: up_data_structs::CreateItemData,
+ _nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, sp_runtime::DispatchError> {
+ match &data {
+ up_data_structs::CreateItemData::Fungible(fungible_data) => {
+ T::Mutate::mint_into(
+ to.as_sub(),
+ fungible_data
+ .value
+ .try_into()
+ .map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+ )?;
+
+ Ok(TokenId::default())
+ }
+ _ => {
+ fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken)
+ }
+ }
+ }
+
+ fn transfer_item_internal(
+ &self,
+ _depositor: &<T>::CrossAccountId,
+ from: &<T>::CrossAccountId,
+ to: &<T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ _nesting_budget: &dyn Budget,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ <Pallet<T>>::transfer(from, to, amount)
+ .map(|_| ())
+ .map_err(|post_info| post_info.error)
+ }
+
+ fn burn_item_internal(
+ &self,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ T::Mutate::burn_from(
+ from.as_sub(),
+ amount
+ .try_into()
+ .map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+ Precision::Exact,
+ Fortitude::Polite,
+ )?;
+
+ Ok(())
+ }
+}
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -33,7 +33,7 @@
MAX_COLLECTION_NAME_LENGTH, MAX_PROPERTIES_PER_ITEM, MAX_TOKEN_PREFIX_LENGTH,
};
-use crate::{BenchmarkPropertyWriter, CollectionHandle, Config, Pallet};
+use crate::{BenchmarkPropertyWriter, CollectionHandle, CollectionIssuer, Config, Pallet};
const SEED: u32 = 1;
@@ -120,7 +120,9 @@
create_collection_raw(
owner,
CollectionMode::NFT,
- |owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+ |owner: T::CrossAccountId, data| {
+ <Pallet<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+ },
|h| h,
)
}
pallets/common/src/dispatch.rsdiffbeforeafterboth--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -11,7 +11,7 @@
use sp_weights::Weight;
use up_data_structs::{CollectionId, CreateCollectionData};
-use crate::{pallet::Config, CommonCollectionOperations};
+use crate::{pallet::Config, CollectionIssuer, CommonCollectionOperations};
// TODO: move to benchmarking
/// Price of [`dispatch_tx`] call with noop `call` argument
@@ -69,13 +69,14 @@
/// Check if the collection is internal.
fn check_is_internal(&self) -> DispatchResult;
- /// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
+ /// Create a regular collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
///
/// * `sender` - The user who will become the owner of the collection.
+ /// * `issuer` - An entity that creates the collection.
/// * `data` - Description of the created collection.
fn create(
sender: T::CrossAccountId,
- payer: T::CrossAccountId,
+ issuer: CollectionIssuer<T::CrossAccountId>,
data: CreateCollectionData<T::CrossAccountId>,
) -> Result<CollectionId, DispatchError>;
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -80,15 +80,16 @@
use sp_std::vec::Vec;
use sp_weights::Weight;
use up_data_structs::{
- budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,
- CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,
- CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,
- PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,
- PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,
- SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,
- TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,
- FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,
- MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ budget::Budget, mapping::TokenAddressMapping, AccessMode, Collection, CollectionId,
+ CollectionLimits, CollectionMode, CollectionPermissions,
+ CollectionProperties as CollectionPropertiesT, CollectionStats, CreateCollectionData,
+ CreateItemData, CreateItemExData, PhantomType, PropertiesError, PropertiesPermissionMap,
+ Property, PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,
+ RpcCollection, RpcCollectionFlags, SponsoringRateLimit, SponsorshipState, TokenChild,
+ TokenData, TokenId, TokenOwnerError, TokenProperties, TrySetProperty, COLLECTION_ADMINS_LIMIT,
+ COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_TOKEN_PREFIX_LENGTH,
+ NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
};
use up_pov_estimate_rpc::PovInfo;
@@ -786,6 +787,9 @@
/// Fungible tokens hold no ID, and the default value of TokenId for a fungible collection is 0.
FungibleItemsHaveNoId,
+
+ /// Not Fungible item data used to mint in Fungible collection.
+ NotFungibleDataUsedToMintFungibleCollectionToken,
}
/// Storage of the count of created collections. Essentially contains the last collection ID.
@@ -940,6 +944,15 @@
}
}
+/// An issuer of a collection.
+pub enum CollectionIssuer<CrossAccountId> {
+ /// A user who creates the collection.
+ User(CrossAccountId),
+
+ /// The internal mechanisms are creating the collection.
+ Internals,
+}
+
fn check_token_permissions<T: Config>(
collection_admin_permitted: bool,
token_owner_permitted: bool,
@@ -1124,33 +1137,36 @@
/// Create new collection.
///
/// * `owner` - The owner of the collection.
- /// * `data` - Description of the created collection.
- /// * `flags` - Extra flags to store.
+ /// * `issuer` - An entity that creates the collection.
+ /// * `is_special_collection` -- Whether this collection is a special one, i.e. can have special flags set.
pub fn init_collection(
owner: T::CrossAccountId,
- payer: T::CrossAccountId,
+ issuer: CollectionIssuer<T::CrossAccountId>,
data: CreateCollectionData<T::CrossAccountId>,
) -> Result<CollectionId, DispatchError> {
- ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);
- Self::init_collection_internal(owner, payer, data)
- }
+ match issuer {
+ CollectionIssuer::User(payer) => {
+ ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);
+
+ // Take a (non-refundable) deposit of collection creation
+ let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
+ imbalance.subsume(<T as Config>::Currency::deposit(
+ &T::TreasuryAccountId::get(),
+ T::CollectionCreationPrice::get(),
+ Precision::Exact,
+ )?);
+ let credit = <T as Config>::Currency::settle(
+ payer.as_sub(),
+ imbalance,
+ Preservation::Preserve,
+ )
+ .map_err(|_| Error::<T>::NotSufficientFounds)?;
- /// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.
- pub fn init_foreign_collection(
- owner: T::CrossAccountId,
- payer: T::CrossAccountId,
- mut data: CreateCollectionData<T::CrossAccountId>,
- ) -> Result<CollectionId, DispatchError> {
- data.flags.foreign = true;
- let id = Self::init_collection_internal(owner, payer, data)?;
- Ok(id)
- }
+ debug_assert!(credit.peek().is_zero());
+ }
+ CollectionIssuer::Internals => {}
+ }
- fn init_collection_internal(
- owner: T::CrossAccountId,
- payer: T::CrossAccountId,
- data: CreateCollectionData<T::CrossAccountId>,
- ) -> Result<CollectionId, DispatchError> {
{
ensure!(
data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,
@@ -1225,21 +1241,6 @@
);
<AdminAmount<T>>::insert(id, admin_amount);
- // Take a (non-refundable) deposit of collection creation
- {
- let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
- imbalance.subsume(<T as Config>::Currency::deposit(
- &T::TreasuryAccountId::get(),
- T::CollectionCreationPrice::get(),
- Precision::Exact,
- )?);
- let credit =
- <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)
- .map_err(|_| Error::<T>::NotSufficientFounds)?;
-
- debug_assert!(credit.peek().is_zero())
- }
-
<CreatedCollectionCount<T>>::put(created_count);
<Pallet<T>>::deposit_event(Event::CollectionCreated(
id,
@@ -2293,7 +2294,14 @@
) -> u128;
/// Get extension for RFT collection.
- fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;
+ fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {
+ None
+ }
+
+ /// Get XCM extensions.
+ fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+ None
+ }
/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.
/// * `owner` - Token owner
@@ -2333,6 +2341,117 @@
) -> DispatchResultWithPostInfo;
}
+/// XCM extensions for fungible and NFT collections
+pub trait XcmExtensions<T>
+where
+ T: Config,
+{
+ /// Does the token have children?
+ fn token_has_children(&self, _token: TokenId) -> bool {
+ false
+ }
+
+ /// Create a collection's item using a transaction.
+ ///
+ /// This function performs additional XCM-related checks before the actual creation.
+ ///
+ /// The `transactional` attribute is needed because the inbound XCM messages
+ /// are processed in a non-transactional context.
+ /// To perform the needed logic, we use the internal pallets' functions
+ /// that are not inherently safe to use outside a transaction.
+ ///
+ /// This requirement is temporary until XCM message processing becomes transactional:
+ /// https://github.com/paritytech/polkadot-sdk/issues/490
+ #[transactional]
+ fn create_item(
+ &self,
+ depositor: &T::CrossAccountId,
+ to: T::CrossAccountId,
+ data: CreateItemData,
+ nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, DispatchError> {
+ if T::CrossTokenAddressMapping::is_token_address(&to) {
+ return unsupported!(T);
+ }
+
+ self.create_item_internal(depositor, to, data, nesting_budget)
+ }
+
+ /// Create a collection's item.
+ fn create_item_internal(
+ &self,
+ depositor: &T::CrossAccountId,
+ to: T::CrossAccountId,
+ data: CreateItemData,
+ nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, DispatchError>;
+
+ /// Transfer an item from the `from` account to the `to` account using a transaction.
+ ///
+ /// This function performs additional XCM-related checks before the actual transfer.
+ ///
+ /// The `transactional` attribute is needed because the inbound XCM messages
+ /// are processed in a non-transactional context.
+ /// To perform the needed logic, we use the internal pallets' functions
+ /// that are not inherently safe to use outside a transaction.
+ ///
+ /// This requirement is temporary until XCM message processing becomes transactional:
+ /// https://github.com/paritytech/polkadot-sdk/issues/490
+ #[transactional]
+ fn transfer_item(
+ &self,
+ depositor: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ to: &T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult {
+ if T::CrossTokenAddressMapping::is_token_address(to) {
+ return unsupported!(T);
+ }
+
+ if self.token_has_children(token) {
+ return unsupported!(T);
+ }
+
+ self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)
+ }
+
+ /// Transfer an item from the `from` account to the `to` account.
+ fn transfer_item_internal(
+ &self,
+ depositor: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ to: &T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult;
+
+ /// Burn a collection's item using a transaction.
+ ///
+ /// The `transactional` attribute is needed because the inbound XCM messages
+ /// are processed in a non-transactional context.
+ /// To perform the needed logic, we use the internal pallets' functions
+ /// that are not inherently safe to use outside a transaction.
+ ///
+ /// This requirement is temporary until XCM message processing becomes transactional:
+ /// https://github.com/paritytech/polkadot-sdk/issues/490
+ #[transactional]
+ fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {
+ self.burn_item_internal(from, token, amount)
+ }
+
+ /// Burn a collection's item.
+ fn burn_item_internal(
+ &self,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> DispatchResult;
+}
+
/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].
///
/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.
pallets/foreign-assets/Cargo.tomldiffbeforeafterboth--- a/pallets/foreign-assets/Cargo.toml
+++ b/pallets/foreign-assets/Cargo.toml
@@ -12,7 +12,6 @@
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
-orml-tokens = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
pallet-common = { workspace = true }
pallet-fungible = { workspace = true }
@@ -30,7 +29,6 @@
"frame-support/std",
"frame-system/std",
"log/std",
- "orml-tokens/std",
"pallet-balances/std",
"pallet-common/std",
"pallet-fungible/std",
pallets/foreign-assets/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/foreign-assets/src/benchmarking.rs
+++ b/pallets/foreign-assets/src/benchmarking.rs
@@ -16,88 +16,36 @@
#![allow(missing_docs)]
-use frame_benchmarking::{account, v2::*};
-use frame_support::traits::Currency;
+use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
-use sp_std::{boxed::Box, vec, vec::Vec};
-use staging_xcm::{opaque::latest::Junction::Parachain, v3::Junctions::X1, VersionedMultiLocation};
+use pallet_common::benchmarking::{create_data, create_u16_data};
+use sp_std::{boxed::Box, vec};
+use staging_xcm::prelude::*;
+use up_data_structs::{MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH};
-use super::{Call, Config, Pallet};
-use crate::AssetMetadata;
+use super::{Call, Config, ForeignCollectionMode, Pallet};
-fn bounded<T: TryFrom<Vec<u8>>>(slice: &[u8]) -> T {
- T::try_from(slice.to_vec())
- .map_err(|_| "slice doesn't fit")
- .unwrap()
-}
-
#[benchmarks]
mod benchmarks {
+
use super::*;
#[benchmark]
- fn register_foreign_asset() -> Result<(), BenchmarkError> {
- let owner: T::AccountId = account("user", 0, 1);
- let location: VersionedMultiLocation = VersionedMultiLocation::from(X1(Parachain(1000)));
- let metadata: AssetMetadata<
- <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
- > = AssetMetadata {
- name: bounded(b"name"),
- symbol: bounded(b"symbol"),
- decimals: 18,
- minimal_balance: 1u32.into(),
- };
- let mut balance: <<T as Config>::Currency as Currency<
- <T as frame_system::Config>::AccountId,
- >>::Balance = 4_000_000_000u32.into();
- balance = balance * balance;
- <T as Config>::Currency::make_free_balance_be(&owner, balance);
+ fn force_register_foreign_asset() -> Result<(), BenchmarkError> {
+ let location =
+ MultiLocation::from(X3(Parachain(1000), PalletInstance(42), GeneralIndex(1)));
+ let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
+ let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
+ let mode = ForeignCollectionMode::NFT;
#[extrinsic_call]
_(
RawOrigin::Root,
- owner,
- Box::new(location),
- Box::new(metadata),
+ Box::new(location.into()),
+ name,
+ token_prefix,
+ mode,
);
-
- Ok(())
- }
-
- #[benchmark]
- fn update_foreign_asset() -> Result<(), BenchmarkError> {
- let owner: T::AccountId = account("user", 0, 1);
- let location: VersionedMultiLocation = VersionedMultiLocation::from(X1(Parachain(2000)));
- let metadata: AssetMetadata<
- <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
- > = AssetMetadata {
- name: bounded(b"name"),
- symbol: bounded(b"symbol"),
- decimals: 18,
- minimal_balance: 1u32.into(),
- };
- let metadata2: AssetMetadata<
- <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
- > = AssetMetadata {
- name: bounded(b"name2"),
- symbol: bounded(b"symbol2"),
- decimals: 18,
- minimal_balance: 1u32.into(),
- };
- let mut balance: <<T as Config>::Currency as Currency<
- <T as frame_system::Config>::AccountId,
- >>::Balance = 4_000_000_000u32.into();
- balance = balance * balance;
- <T as Config>::Currency::make_free_balance_be(&owner, balance);
- Pallet::<T>::register_foreign_asset(
- RawOrigin::Root.into(),
- owner,
- Box::new(location.clone()),
- Box::new(metadata),
- )?;
-
- #[extrinsic_call]
- _(RawOrigin::Root, 0, Box::new(location), Box::new(metadata2));
Ok(())
}
pallets/foreign-assets/src/impl_fungibles.rsdiffbeforeafterboth--- a/pallets/foreign-assets/src/impl_fungibles.rs
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-//! Implementations for fungibles trait.
-
-use frame_support::traits::tokens::{
- DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence,
-};
-use frame_system::Config as SystemConfig;
-use pallet_common::{CollectionHandle, CommonCollectionOperations};
-use pallet_fungible::FungibleHandle;
-use sp_runtime::traits::{CheckedAdd, CheckedSub};
-use up_data_structs::budget;
-
-use super::*;
-
-impl<T: Config> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T>
-where
- T: orml_tokens::Config<CurrencyId = AssetId>,
- BalanceOf<T>: From<<T as pallet_balances::Config>::Balance>,
- BalanceOf<T>: From<<T as orml_tokens::Config>::Balance>,
- <T as pallet_balances::Config>::Balance: From<BalanceOf<T>>,
- <T as orml_tokens::Config>::Balance: From<BalanceOf<T>>,
-{
- type AssetId = AssetId;
- type Balance = BalanceOf<T>;
-
- fn total_issuance(asset: Self::AssetId) -> Self::Balance {
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible total_issuance");
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::total_issuance()
- .into()
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::total_issuance(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- )
- .into()
- }
- AssetId::ForeignAssetId(fid) => {
- let target_collection_id = match <AssetBinding<T>>::get(fid) {
- Some(v) => v,
- None => return Zero::zero(),
- };
- let collection_handle = match <CollectionHandle<T>>::try_get(target_collection_id) {
- Ok(v) => v,
- Err(_) => return Zero::zero(),
- };
- let collection = FungibleHandle::cast(collection_handle);
- Self::Balance::try_from(collection.total_supply()).unwrap_or(Zero::zero())
- }
- }
- }
-
- fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible minimum_balance");
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::minimum_balance()
- .into()
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::minimum_balance(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- )
- .into()
- }
- AssetId::ForeignAssetId(fid) => AssetMetadatas::<T>::get(AssetId::ForeignAssetId(fid))
- .map(|x| x.minimal_balance)
- .unwrap_or_else(Zero::zero),
- }
- }
-
- fn balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible balance");
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::balance(who).into()
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::balance(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- who,
- )
- .into()
- }
- AssetId::ForeignAssetId(fid) => {
- let target_collection_id = match <AssetBinding<T>>::get(fid) {
- Some(v) => v,
- None => return Zero::zero(),
- };
- let collection_handle = match <CollectionHandle<T>>::try_get(target_collection_id) {
- Ok(v) => v,
- Err(_) => return Zero::zero(),
- };
- let collection = FungibleHandle::cast(collection_handle);
- Self::Balance::try_from(
- collection.balance(T::CrossAccountId::from_sub(who.clone()), TokenId(0)),
- )
- .unwrap_or(Zero::zero())
- }
- }
- }
-
- fn total_balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
- Self::balance(asset, who)
- }
-
- fn reducible_balance(
- asset: Self::AssetId,
- who: &<T as SystemConfig>::AccountId,
- preservation: Preservation,
- fortitude: Fortitude,
- ) -> Self::Balance {
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible reducible_balance");
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::reducible_balance(
- who,
- preservation,
- fortitude,
- )
- .into()
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::reducible_balance(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- who,
- preservation,
- fortitude,
- )
- .into()
- }
- _ => Self::balance(asset, who),
- }
- }
-
- fn can_deposit(
- asset: Self::AssetId,
- who: &<T as SystemConfig>::AccountId,
- amount: Self::Balance,
- provenance: Provenance,
- ) -> DepositConsequence {
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible can_deposit");
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::can_deposit(
- who,
- amount.into(),
- provenance,
- )
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::can_deposit(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- who,
- amount.into(),
- provenance,
- )
- }
- _ => {
- if amount.is_zero() {
- return DepositConsequence::Success;
- }
-
- let extential_deposit_value = T::ExistentialDeposit::get();
- let ed_value: u128 = match extential_deposit_value.try_into() {
- Ok(val) => val,
- Err(_) => return DepositConsequence::CannotCreate,
- };
- let extential_deposit: Self::Balance = match ed_value.try_into() {
- Ok(val) => val,
- Err(_) => return DepositConsequence::CannotCreate,
- };
-
- let new_total_balance = match Self::balance(asset, who).checked_add(&amount) {
- Some(x) => x,
- None => return DepositConsequence::Overflow,
- };
-
- if new_total_balance < extential_deposit {
- return DepositConsequence::BelowMinimum;
- }
-
- DepositConsequence::Success
- }
- }
- }
-
- fn can_withdraw(
- asset: Self::AssetId,
- who: &<T as SystemConfig>::AccountId,
- amount: Self::Balance,
- ) -> WithdrawConsequence<Self::Balance> {
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible can_withdraw");
- let value: u128 = match amount.try_into() {
- Ok(val) => val,
- Err(_) => return WithdrawConsequence::UnknownAsset,
- };
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- let this_amount: <T as pallet_balances::Config>::Balance = match value.try_into() {
- Ok(val) => val,
- Err(_) => {
- return WithdrawConsequence::UnknownAsset;
- }
- };
- match <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::can_withdraw(
- who,
- this_amount,
- ) {
- WithdrawConsequence::BalanceLow => WithdrawConsequence::BalanceLow,
- WithdrawConsequence::WouldDie => WithdrawConsequence::WouldDie,
- WithdrawConsequence::UnknownAsset => WithdrawConsequence::UnknownAsset,
- WithdrawConsequence::Underflow => WithdrawConsequence::Underflow,
- WithdrawConsequence::Overflow => WithdrawConsequence::Overflow,
- WithdrawConsequence::Frozen => WithdrawConsequence::Frozen,
- WithdrawConsequence::Success => WithdrawConsequence::Success,
- _ => WithdrawConsequence::BalanceLow,
- }
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- let parent_amount: <T as orml_tokens::Config>::Balance = match value.try_into() {
- Ok(val) => val,
- Err(_) => {
- return WithdrawConsequence::UnknownAsset;
- }
- };
- match <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::can_withdraw(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- who,
- parent_amount,
- ) {
- WithdrawConsequence::BalanceLow => WithdrawConsequence::BalanceLow,
- WithdrawConsequence::WouldDie => WithdrawConsequence::WouldDie,
- WithdrawConsequence::UnknownAsset => WithdrawConsequence::UnknownAsset,
- WithdrawConsequence::Underflow => WithdrawConsequence::Underflow,
- WithdrawConsequence::Overflow => WithdrawConsequence::Overflow,
- WithdrawConsequence::Frozen => WithdrawConsequence::Frozen,
- WithdrawConsequence::Success => WithdrawConsequence::Success,
- _ => WithdrawConsequence::BalanceLow,
- }
- }
- _ => match Self::balance(asset, who).checked_sub(&amount) {
- Some(_) => WithdrawConsequence::Success,
- None => WithdrawConsequence::BalanceLow,
- },
- }
- }
-
- fn asset_exists(asset: AssetId) -> bool {
- match asset {
- AssetId::NativeAssetId(_) => true,
- AssetId::ForeignAssetId(fid) => <AssetBinding<T>>::contains_key(fid),
- }
- }
-}
-
-impl<T: Config> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T>
-where
- T: orml_tokens::Config<CurrencyId = AssetId>,
- BalanceOf<T>: From<<T as pallet_balances::Config>::Balance>,
- BalanceOf<T>: From<<T as orml_tokens::Config>::Balance>,
- <T as pallet_balances::Config>::Balance: From<BalanceOf<T>>,
- <T as orml_tokens::Config>::Balance: From<BalanceOf<T>>,
- u128: From<BalanceOf<T>>,
-{
- fn mint_into(
- asset: Self::AssetId,
- who: &<T as SystemConfig>::AccountId,
- amount: Self::Balance,
- ) -> Result<BalanceOf<T>, DispatchError> {
- //Self::do_mint(asset, who, amount, None)
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible mint_into {:?}", asset);
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::mint_into(
- who,
- amount.into(),
- )
- .map(Into::into)
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Mutate<T::AccountId>>::mint_into(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- who,
- amount.into(),
- )
- .map(Into::into)
- }
- AssetId::ForeignAssetId(fid) => {
- let target_collection_id = match <AssetBinding<T>>::get(fid) {
- Some(v) => v,
- None => {
- return Err(DispatchError::Other(
- "Associated collection not found for asset",
- ))
- }
- };
- let collection =
- FungibleHandle::cast(<CollectionHandle<T>>::try_get(target_collection_id)?);
- let account = T::CrossAccountId::from_sub(who.clone());
-
- let amount_data: pallet_fungible::CreateItemData<T> =
- (account.clone(), amount.into());
-
- pallet_fungible::Pallet::<T>::create_item_foreign(
- &collection,
- &account,
- amount_data,
- &budget::Value::new(0),
- )?;
-
- Ok(amount)
- }
- }
- }
-
- fn burn_from(
- asset: Self::AssetId,
- who: &<T as SystemConfig>::AccountId,
- amount: Self::Balance,
- precision: Precision,
- fortitude: Fortitude,
- ) -> Result<Self::Balance, DispatchError> {
- // let f = DebitFlags { keep_alive: false, best_effort: false };
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible burn_from");
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- <pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::burn_from(
- who,
- amount.into(),
- precision,
- fortitude,
- )
- .map(Into::into)
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- <orml_tokens::Pallet<T> as fungibles::Mutate<T::AccountId>>::burn_from(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- who,
- amount.into(),
- precision,
- fortitude,
- )
- .map(Into::into)
- }
- AssetId::ForeignAssetId(fid) => {
- let target_collection_id = match <AssetBinding<T>>::get(fid) {
- Some(v) => v,
- None => {
- return Err(DispatchError::Other(
- "Associated collection not found for asset",
- ))
- }
- };
- let collection =
- FungibleHandle::cast(<CollectionHandle<T>>::try_get(target_collection_id)?);
- pallet_fungible::Pallet::<T>::burn_foreign(
- &collection,
- &T::CrossAccountId::from_sub(who.clone()),
- amount.into(),
- )?;
-
- Ok(amount)
- }
- }
- }
-
- fn transfer(
- asset: Self::AssetId,
- source: &<T as SystemConfig>::AccountId,
- dest: &<T as SystemConfig>::AccountId,
- amount: Self::Balance,
- preservation: Preservation,
- ) -> Result<Self::Balance, DispatchError> {
- // let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false };
- log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible transfer");
-
- match asset {
- AssetId::NativeAssetId(NativeCurrency::Here) => {
- match <pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::transfer(
- source,
- dest,
- amount.into(),
- preservation,
- ) {
- Ok(_) => Ok(amount),
- Err(_) => Err(DispatchError::Other(
- "Bad amount to relay chain value conversion",
- )),
- }
- }
- AssetId::NativeAssetId(NativeCurrency::Parent) => {
- match <orml_tokens::Pallet<T> as fungibles::Mutate<T::AccountId>>::transfer(
- AssetId::NativeAssetId(NativeCurrency::Parent),
- source,
- dest,
- amount.into(),
- preservation,
- ) {
- Ok(_) => Ok(amount),
- Err(e) => Err(e),
- }
- }
- AssetId::ForeignAssetId(fid) => {
- let target_collection_id = match <AssetBinding<T>>::get(fid) {
- Some(v) => v,
- None => {
- return Err(DispatchError::Other(
- "Associated collection not found for asset",
- ))
- }
- };
- let collection =
- FungibleHandle::cast(<CollectionHandle<T>>::try_get(target_collection_id)?);
-
- pallet_fungible::Pallet::<T>::transfer(
- &collection,
- &T::CrossAccountId::from_sub(source.clone()),
- &T::CrossAccountId::from_sub(dest.clone()),
- amount.into(),
- &budget::Value::new(0),
- )
- .map_err(|e| e.error)?;
-
- Ok(amount)
- }
- }
- }
-}
-
-#[cfg(not(debug_assertions))]
-extern "C" {
- // This function does not exists, thus compilation will fail, if its call is
- // not optimized away, which is only possible if it's not called at all.
- //
- // not(debug_assertions) is used to ensure compiler is dropping unused functions, as
- // this option is enabled in release by defailt
- //
- // FIXME: maybe use build.rs, to ensure it will fail even in release with debug_assertions
- // enabled?
- fn unbalanced_fungible_is_called();
-}
-macro_rules! ensure_balanced {
- () => {{
- #[cfg(debug_assertions)]
- panic!("unbalanced fungible methods should not be used");
- #[cfg(not(debug_assertions))]
- {
- unsafe { unbalanced_fungible_is_called() };
- unreachable!();
- }
- }};
-}
-
-impl<T: Config> fungibles::Unbalanced<<T as SystemConfig>::AccountId> for Pallet<T>
-where
- T: orml_tokens::Config<CurrencyId = AssetId>,
- BalanceOf<T>: From<<T as pallet_balances::Config>::Balance>,
- BalanceOf<T>: From<<T as orml_tokens::Config>::Balance>,
- <T as pallet_balances::Config>::Balance: From<BalanceOf<T>>,
- <T as orml_tokens::Config>::Balance: From<BalanceOf<T>>,
- u128: From<BalanceOf<T>>,
-{
- fn handle_dust(_dust: fungibles::Dust<<T as SystemConfig>::AccountId, Self>) {
- ensure_balanced!();
- }
- fn write_balance(
- _asset: Self::AssetId,
- _who: &<T as SystemConfig>::AccountId,
- _amount: Self::Balance,
- ) -> Result<Option<Self::Balance>, DispatchError> {
- ensure_balanced!();
- }
- fn set_total_issuance(_asset: Self::AssetId, _amount: Self::Balance) {
- ensure_balanced!();
- }
-}
pallets/foreign-assets/src/lib.rsdiffbeforeafterboth--- a/pallets/foreign-assets/src/lib.rs
+++ b/pallets/foreign-assets/src/lib.rs
@@ -14,113 +14,38 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-//! # Foreign assets
-//!
-//! - [`Config`]
-//! - [`Call`]
-//! - [`Pallet`]
+//! # Foreign Assets
//!
//! ## Overview
//!
-//! The foreign assests pallet provides functions for:
-//!
-//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade.
-//! - Bounds between asset and target collection for cross chain transfer and inner transfers.
-//!
-//! ## Overview
-//!
-//! Under construction
+//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unused_unit)]
-use frame_support::{
- dispatch::DispatchResult,
- ensure,
- pallet_prelude::*,
- traits::{fungible, fungibles, Currency, EnsureOrigin},
-};
+use core::ops::Deref;
+
+use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};
use frame_system::pallet_prelude::*;
-use pallet_common::erc::CrossAccountId;
-use pallet_fungible::Pallet as PalletFungible;
-use scale_info::TypeInfo;
-use serde::{Deserialize, Serialize};
-use sp_runtime::{
- traits::{One, Zero},
- ArithmeticError,
+use pallet_common::{
+ dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,
};
-use sp_std::{boxed::Box, vec::Vec};
-use staging_xcm::{latest::MultiLocation, VersionedMultiLocation};
-// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the
-// MultiLocation to the XCM v3.
+use sp_runtime::traits::AccountIdConversion;
+use sp_std::{boxed::Box, vec, vec::Vec};
use staging_xcm::{
opaque::latest::{prelude::XcmError, Weight},
- v3::XcmContext,
+ v3::{prelude::*, MultiAsset, XcmContext},
+ VersionedAssetId,
};
-use staging_xcm_executor::{traits::WeightTrader, Assets};
-use up_data_structs::{CollectionId, CollectionMode, CreateCollectionData, TokenId};
-
-// TODO: Move to primitives
-// Id of native currency.
-// 0 - QTZ\UNQ
-// 1 - KSM\DOT
-#[derive(
- Clone,
- Copy,
- Eq,
- PartialEq,
- PartialOrd,
- Ord,
- MaxEncodedLen,
- RuntimeDebug,
- Encode,
- Decode,
- TypeInfo,
- Serialize,
- Deserialize,
-)]
-pub enum NativeCurrency {
- Here = 0,
- Parent = 1,
-}
-
-#[derive(
- Clone,
- Copy,
- Eq,
- PartialEq,
- PartialOrd,
- Ord,
- MaxEncodedLen,
- RuntimeDebug,
- Encode,
- Decode,
- TypeInfo,
- Serialize,
- Deserialize,
-)]
-pub enum AssetId {
- ForeignAssetId(ForeignAssetId),
- NativeAssetId(NativeCurrency),
-}
-
-pub trait TryAsForeign<T, F> {
- fn try_as_foreign(asset: T) -> Option<F>;
-}
-
-impl TryAsForeign<AssetId, ForeignAssetId> for AssetId {
- fn try_as_foreign(asset: AssetId) -> Option<ForeignAssetId> {
- match asset {
- Self::ForeignAssetId(id) => Some(id),
- _ => None,
- }
- }
-}
-
-pub type ForeignAssetId = u32;
-pub type CurrencyId = AssetId;
+use staging_xcm_executor::{
+ traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},
+ Assets,
+};
+use up_data_structs::{
+ budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,
+ CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,
+};
-mod impl_fungibles;
pub mod weights;
#[cfg(feature = "runtime-benchmarks")]
@@ -128,44 +53,12 @@
pub use module::*;
pub use weights::WeightInfo;
-
-/// Type alias for currency balance.
-pub type BalanceOf<T> =
- <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
-
-/// A mapping between ForeignAssetId and AssetMetadata.
-pub trait AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata> {
- /// Returns the AssetMetadata associated with a given ForeignAssetId.
- fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata>;
- /// Returns the MultiLocation associated with a given ForeignAssetId.
- fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation>;
- /// Returns the CurrencyId associated with a given MultiLocation.
- fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId>;
-}
-
-pub struct XcmForeignAssetIdMapping<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: Config> AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata<BalanceOf<T>>>
- for XcmForeignAssetIdMapping<T>
-{
- fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata<BalanceOf<T>>> {
- log::trace!(target: "fassets::asset_metadatas", "call");
- Pallet::<T>::asset_metadatas(AssetId::ForeignAssetId(foreign_asset_id))
- }
-
- fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation> {
- log::trace!(target: "fassets::get_multi_location", "call");
- Pallet::<T>::foreign_asset_locations(foreign_asset_id)
- }
-
- fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {
- log::trace!(target: "fassets::get_currency_id", "call");
- Pallet::<T>::location_to_currency_ids(multi_location).map(AssetId::ForeignAssetId)
- }
-}
#[frame_support::pallet]
pub mod module {
+ use pallet_common::CollectionIssuer;
+ use up_data_structs::CollectionDescription;
+
use super::*;
#[pallet::config]
@@ -173,44 +66,34 @@
frame_system::Config
+ pallet_common::Config
+ pallet_fungible::Config
- + orml_tokens::Config
+ pallet_balances::Config
{
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
- /// Currency type for withdraw and balance storage.
- type Currency: Currency<Self::AccountId>;
+ /// Origin for force registering of a foreign asset.
+ type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
- /// Required origin for registering asset.
- type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
+ /// The ID of the foreign assets pallet.
+ type PalletId: Get<PalletId>;
+
+ /// Self-location of this parachain.
+ type SelfLocation: Get<MultiLocation>;
+
+ /// The converter from a MultiLocation to a CrossAccountId.
+ type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;
/// Weight information for the extrinsics in this module.
type WeightInfo: WeightInfo;
- }
-
- pub type AssetName = BoundedVec<u8, ConstU32<32>>;
- pub type AssetSymbol = BoundedVec<u8, ConstU32<7>>;
-
- #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
- pub struct AssetMetadata<Balance> {
- pub name: AssetName,
- pub symbol: AssetSymbol,
- pub decimals: u8,
- pub minimal_balance: Balance,
}
#[pallet::error]
pub enum Error<T> {
- /// The given location could not be used (e.g. because it cannot be expressed in the
- /// desired version of XCM).
- BadLocation,
- /// MultiLocation existed
- MultiLocationExisted,
- /// AssetId not exists
- AssetIdNotExists,
- /// AssetId exists
- AssetIdExisted,
+ /// The foreign asset is already registered.
+ ForeignAssetAlreadyRegistered,
+
+ /// The given asset ID could not be converted into the current XCM version.
+ BadForeignAssetId,
}
#[pallet::event]
@@ -218,64 +101,46 @@
pub enum Event<T: Config> {
/// The foreign asset registered.
ForeignAssetRegistered {
- asset_id: ForeignAssetId,
- asset_address: MultiLocation,
- metadata: AssetMetadata<BalanceOf<T>>,
- },
- /// The foreign asset updated.
- ForeignAssetUpdated {
- asset_id: ForeignAssetId,
- asset_address: MultiLocation,
- metadata: AssetMetadata<BalanceOf<T>>,
- },
- /// The asset registered.
- AssetRegistered {
- asset_id: AssetId,
- metadata: AssetMetadata<BalanceOf<T>>,
- },
- /// The asset updated.
- AssetUpdated {
- asset_id: AssetId,
- metadata: AssetMetadata<BalanceOf<T>>,
+ collection_id: CollectionId,
+ asset_id: Box<VersionedAssetId>,
},
}
- /// Next available Foreign AssetId ID.
- ///
- /// NextForeignAssetId: ForeignAssetId
+ /// The corresponding collections of foreign assets.
#[pallet::storage]
- #[pallet::getter(fn next_foreign_asset_id)]
- pub type NextForeignAssetId<T: Config> = StorageValue<_, ForeignAssetId, ValueQuery>;
- /// The storages for MultiLocations.
- ///
- /// ForeignAssetLocations: map ForeignAssetId => Option<MultiLocation>
- #[pallet::storage]
- #[pallet::getter(fn foreign_asset_locations)]
- pub type ForeignAssetLocations<T: Config> =
- StorageMap<_, Twox64Concat, ForeignAssetId, staging_xcm::v3::MultiLocation, OptionQuery>;
+ #[pallet::getter(fn foreign_asset_to_collection)]
+ pub type ForeignAssetToCollection<T: Config> =
+ StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;
- /// The storages for CurrencyIds.
- ///
- /// LocationToCurrencyIds: map MultiLocation => Option<ForeignAssetId>
+ /// The corresponding foreign assets of collections.
#[pallet::storage]
- #[pallet::getter(fn location_to_currency_ids)]
- pub type LocationToCurrencyIds<T: Config> =
- StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, ForeignAssetId, OptionQuery>;
+ #[pallet::getter(fn collection_to_foreign_asset)]
+ pub type CollectionToForeignAsset<T: Config> =
+ StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;
- /// The storages for AssetMetadatas.
- ///
- /// AssetMetadatas: map AssetIds => Option<AssetMetadata>
+ /// The correponding NFT token id of reserve NFTs
#[pallet::storage]
- #[pallet::getter(fn asset_metadatas)]
- pub type AssetMetadatas<T: Config> =
- StorageMap<_, Twox64Concat, AssetId, AssetMetadata<BalanceOf<T>>, OptionQuery>;
+ #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]
+ pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<
+ Hasher1 = Twox64Concat,
+ Key1 = CollectionId,
+ Hasher2 = Blake2_128Concat,
+ Key2 = staging_xcm::v3::AssetInstance,
+ Value = TokenId,
+ QueryKind = OptionQuery,
+ >;
- /// The storages for assets to fungible collection binding
- ///
+ /// The correponding reserve NFT of a token ID
#[pallet::storage]
- #[pallet::getter(fn asset_binding)]
- pub type AssetBinding<T: Config> =
- StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>;
+ #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]
+ pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<
+ Hasher1 = Twox64Concat,
+ Key1 = CollectionId,
+ Hasher2 = Blake2_128Concat,
+ Key2 = TokenId,
+ Value = staging_xcm::v3::AssetInstance,
+ QueryKind = OptionQuery,
+ >;
#[pallet::pallet]
pub struct Pallet<T>(_);
@@ -284,196 +149,432 @@
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]
- pub fn register_foreign_asset(
+ pub fn force_register_foreign_asset(
origin: OriginFor<T>,
- owner: T::AccountId,
- location: Box<VersionedMultiLocation>,
- metadata: Box<AssetMetadata<BalanceOf<T>>>,
+ versioned_asset_id: Box<VersionedAssetId>,
+ name: CollectionName,
+ token_prefix: CollectionTokenPrefix,
+ mode: ForeignCollectionMode,
) -> DispatchResult {
- T::RegisterOrigin::ensure_origin(origin.clone())?;
+ T::ForceRegisterOrigin::ensure_origin(origin.clone())?;
- let location: MultiLocation = (*location)
+ let asset_id: AssetId = versioned_asset_id
+ .as_ref()
+ .clone()
.try_into()
- .map_err(|()| Error::<T>::BadLocation)?;
+ .map_err(|()| Error::<T>::BadForeignAssetId)?;
- let md = metadata.clone();
- let name: Vec<u16> = md.name.into_iter().map(|x| x as u16).collect::<Vec<u16>>();
- let mut description: Vec<u16> = "Foreign assets collection for "
+ ensure!(
+ !<ForeignAssetToCollection<T>>::contains_key(asset_id),
+ <Error<T>>::ForeignAssetAlreadyRegistered,
+ );
+
+ let foreign_collection_owner = Self::pallet_account();
+
+ let description: CollectionDescription = "Foreign Assets Collection"
.encode_utf16()
- .collect::<Vec<u16>>();
- description.append(&mut name.clone());
+ .collect::<Vec<_>>()
+ .try_into()
+ .expect("description length < max description length; qed");
+
+ let collection_id = T::CollectionDispatch::create(
+ foreign_collection_owner,
+ CollectionIssuer::Internals,
+ CreateCollectionData {
+ name,
+ token_prefix,
+ description,
+ mode: mode.into(),
+ ..Default::default()
+ },
+ )?;
- let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {
- name: name.try_into().unwrap(),
- description: description.try_into().unwrap(),
- mode: CollectionMode::Fungible(md.decimals),
- ..Default::default()
- };
- let owner = T::CrossAccountId::from_sub(owner);
- let bounded_collection_id =
- <PalletFungible<T>>::init_foreign_collection(owner.clone(), owner, data)?;
- let foreign_asset_id =
- Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?;
+ <ForeignAssetToCollection<T>>::insert(asset_id, collection_id);
+ <CollectionToForeignAsset<T>>::insert(collection_id, asset_id);
Self::deposit_event(Event::<T>::ForeignAssetRegistered {
- asset_id: foreign_asset_id,
- asset_address: location,
- metadata: *metadata,
+ collection_id,
+ asset_id: versioned_asset_id,
});
- Ok(())
- }
-
- #[pallet::call_index(1)]
- #[pallet::weight(<T as Config>::WeightInfo::update_foreign_asset())]
- pub fn update_foreign_asset(
- origin: OriginFor<T>,
- foreign_asset_id: ForeignAssetId,
- location: Box<VersionedMultiLocation>,
- metadata: Box<AssetMetadata<BalanceOf<T>>>,
- ) -> DispatchResult {
- T::RegisterOrigin::ensure_origin(origin)?;
-
- let location: MultiLocation = (*location)
- .try_into()
- .map_err(|()| Error::<T>::BadLocation)?;
- Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?;
- Self::deposit_event(Event::<T>::ForeignAssetUpdated {
- asset_id: foreign_asset_id,
- asset_address: location,
- metadata: *metadata,
- });
Ok(())
}
}
}
impl<T: Config> Pallet<T> {
- fn get_next_foreign_asset_id() -> Result<ForeignAssetId, DispatchError> {
- NextForeignAssetId::<T>::try_mutate(|current| -> Result<ForeignAssetId, DispatchError> {
- let id = *current;
- *current = current
- .checked_add(One::one())
- .ok_or(ArithmeticError::Overflow)?;
- Ok(id)
- })
+ fn pallet_account() -> T::CrossAccountId {
+ let owner: T::AccountId = T::PalletId::get().into_account_truncating();
+ T::CrossAccountId::from_sub(owner)
}
- fn do_register_foreign_asset(
- location: &MultiLocation,
- metadata: &AssetMetadata<BalanceOf<T>>,
- bounded_collection_id: CollectionId,
- ) -> Result<ForeignAssetId, DispatchError> {
- let foreign_asset_id = Self::get_next_foreign_asset_id()?;
- LocationToCurrencyIds::<T>::try_mutate(location, |maybe_currency_ids| -> DispatchResult {
- ensure!(
- maybe_currency_ids.is_none(),
- Error::<T>::MultiLocationExisted
- );
- *maybe_currency_ids = Some(foreign_asset_id);
- // *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));
+ /// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.
+ ///
+ /// The multilocation corresponds to a local collection if:
+ /// * It is `Here` location that corresponds to the native token of this parachain.
+ /// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.
+ /// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds
+ /// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,
+ /// otherwise `None` is returned.
+ /// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.
+ ///
+ /// If the multilocation doesn't match the patterns listed above,
+ /// or the `<Collection ID>` points to a foreign collection,
+ /// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.
+ fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {
+ let AssetId::Concrete(asset_location) = asset_id else {
+ return None;
+ };
- ForeignAssetLocations::<T>::try_mutate(
- foreign_asset_id,
- |maybe_location| -> DispatchResult {
- ensure!(maybe_location.is_none(), Error::<T>::MultiLocationExisted);
- *maybe_location = Some(*location);
+ let self_location = T::SelfLocation::get();
- AssetMetadatas::<T>::try_mutate(
- AssetId::ForeignAssetId(foreign_asset_id),
- |maybe_asset_metadatas| -> DispatchResult {
- ensure!(maybe_asset_metadatas.is_none(), Error::<T>::AssetIdExisted);
- *maybe_asset_metadatas = Some(metadata.clone());
- Ok(())
- },
- )
- },
- )?;
+ if *asset_location == Here.into() || *asset_location == self_location {
+ return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));
+ }
+
+ let prefix = if asset_location.parents == 0 {
+ &Here
+ } else if asset_location.parents == self_location.parents {
+ &self_location.interior
+ } else {
+ return None;
+ };
+
+ let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {
+ return None;
+ };
- AssetBinding::<T>::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult {
- *collection_id = Some(bounded_collection_id);
- Ok(())
- })
- })?;
+ let collection_id = CollectionId((*collection_id).try_into().ok()?);
- Ok(foreign_asset_id)
+ Self::collection_to_foreign_asset(collection_id)
+ .is_none()
+ .then_some(CollectionLocality::Local(collection_id))
+ }
+
+ /// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).
+ ///
+ /// The function will check if the asset's reserve location has the corresponding
+ /// foreign collection on Unique Network,
+ /// and will return the "foreign" locality containing the collection ID if found.
+ ///
+ /// If no corresponding foreign collection is found, the function will check
+ /// if the asset's reserve location corresponds to a local collection.
+ /// If the local collection is found, the "local" locality with the collection ID is returned.
+ ///
+ /// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.
+ fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {
+ Self::foreign_asset_to_collection(asset_id)
+ .map(CollectionLocality::Foreign)
+ .or_else(|| Self::local_asset_id_to_collection(asset_id))
+ .ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())
}
- fn do_update_foreign_asset(
- foreign_asset_id: ForeignAssetId,
- location: &MultiLocation,
- metadata: &AssetMetadata<BalanceOf<T>>,
+ /// Converts an XCM asset instance of local collection to the Unique Network's token ID.
+ ///
+ /// The asset instance corresponds to the Unique Network's token ID if it is in the following format:
+ /// `AssetInstance::Index(<token ID>)`.
+ ///
+ /// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,
+ /// `None` will be returned.
+ ///
+ /// Note: this function can return `Some` containing the token ID of a non-existing NFT.
+ /// It returns `None` when it failed to convert the `asset_instance` to a local ID.
+ fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {
+ match asset_instance {
+ AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),
+ _ => None,
+ }
+ }
+
+ /// Obtains the token ID of the `asset_instance` in the collection.
+ ///
+ /// Note: this function can return `Some` containing the token ID of a non-existing NFT.
+ /// It returns `None` when it failed to convert the `asset_instance` to a local ID.
+ fn asset_instance_to_token_id(
+ collection_locality: CollectionLocality,
+ asset_instance: &AssetInstance,
+ ) -> Option<TokenId> {
+ match collection_locality {
+ CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),
+ CollectionLocality::Foreign(collection_id) => {
+ Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)
+ }
+ }
+ }
+
+ /// Creates a foreign item in the the collection.
+ fn create_foreign_asset_instance(
+ xcm_ext: &dyn XcmExtensions<T>,
+ collection_id: CollectionId,
+ asset_instance: &AssetInstance,
+ to: T::CrossAccountId,
) -> DispatchResult {
- ForeignAssetLocations::<T>::try_mutate(
- foreign_asset_id,
- |maybe_multi_locations| -> DispatchResult {
- let old_multi_locations = maybe_multi_locations
- .as_mut()
- .ok_or(Error::<T>::AssetIdNotExists)?;
+ let derivative_token_id = xcm_ext.create_item(
+ &Self::pallet_account(),
+ to,
+ CreateItemData::NFT(Default::default()),
+ &ZeroBudget,
+ )?;
+
+ <ForeignReserveAssetInstanceToTokenId<T>>::insert(
+ collection_id,
+ asset_instance,
+ derivative_token_id,
+ );
+
+ <TokenIdToForeignReserveAssetInstance<T>>::insert(
+ collection_id,
+ derivative_token_id,
+ asset_instance,
+ );
+
+ Ok(())
+ }
+
+ /// Deposits an asset instance to the `to` account.
+ ///
+ /// Either transfers an existing item from the pallet's account
+ /// or creates a foreign item.
+ fn deposit_asset_instance(
+ xcm_ext: &dyn XcmExtensions<T>,
+ collection_locality: CollectionLocality,
+ asset_instance: &AssetInstance,
+ to: T::CrossAccountId,
+ ) -> XcmResult {
+ let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);
+
+ let deposit_result = match (collection_locality, token_id) {
+ (_, Some(token_id)) => {
+ let depositor = &Self::pallet_account();
+ let from = depositor;
+ let amount = 1;
+
+ xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)
+ }
+ (CollectionLocality::Foreign(collection_id), None) => {
+ Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)
+ }
+ (CollectionLocality::Local(_), None) => {
+ return Err(XcmExecutorError::InstanceConversionFailed.into());
+ }
+ };
+
+ deposit_result
+ .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))
+ }
- AssetMetadatas::<T>::try_mutate(
- AssetId::ForeignAssetId(foreign_asset_id),
- |maybe_asset_metadatas| -> DispatchResult {
- ensure!(
- maybe_asset_metadatas.is_some(),
- Error::<T>::AssetIdNotExists
- );
+ /// Withdraws an asset instance from the `from` account.
+ ///
+ /// Transfers the asset instance to the pallet's account.
+ fn withdraw_asset_instance(
+ xcm_ext: &dyn XcmExtensions<T>,
+ collection_locality: CollectionLocality,
+ asset_instance: &AssetInstance,
+ from: T::CrossAccountId,
+ ) -> XcmResult {
+ let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)
+ .ok_or(XcmExecutorError::InstanceConversionFailed)?;
+
+ let depositor = &from;
+ let to = Self::pallet_account();
+ let amount = 1;
+ xcm_ext
+ .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)
+ .map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;
+
+ Ok(())
+ }
+}
+
+impl<T: Config> TransactAsset for Pallet<T> {
+ fn can_check_in(
+ _origin: &MultiLocation,
+ _what: &MultiAsset,
+ _context: &XcmContext,
+ ) -> XcmResult {
+ Err(XcmError::Unimplemented)
+ }
+
+ fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
+
+ fn can_check_out(
+ _dest: &MultiLocation,
+ _what: &MultiAsset,
+ _context: &XcmContext,
+ ) -> XcmResult {
+ Err(XcmError::Unimplemented)
+ }
+
+ fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
+
+ fn deposit_asset(
+ what: &MultiAsset,
+ to: &MultiLocation,
+ _context: Option<&XcmContext>,
+ ) -> XcmResult {
+ let to = T::LocationToAccountId::convert_location(to)
+ .ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+ let collection_locality = Self::asset_to_collection(&what.id)?;
+ let dispatch = T::CollectionDispatch::dispatch(*collection_locality)
+ .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;
+
+ let collection = dispatch.as_dyn();
+ let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;
- // modify location
- if location != old_multi_locations {
- LocationToCurrencyIds::<T>::remove(*old_multi_locations);
- LocationToCurrencyIds::<T>::try_mutate(
- location,
- |maybe_currency_ids| -> DispatchResult {
- ensure!(
- maybe_currency_ids.is_none(),
- Error::<T>::MultiLocationExisted
- );
- // *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));
- *maybe_currency_ids = Some(foreign_asset_id);
- Ok(())
- },
- )?;
- }
- *maybe_asset_metadatas = Some(metadata.clone());
- *old_multi_locations = *location;
- Ok(())
- },
+ match what.fun {
+ Fungibility::Fungible(amount) => xcm_ext
+ .create_item(
+ &Self::pallet_account(),
+ to,
+ CreateItemData::Fungible(CreateFungibleData { value: amount }),
+ &ZeroBudget,
)
- },
- )
+ .map(|_| ())
+ .map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),
+
+ Fungibility::NonFungible(asset_instance) => {
+ Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)
+ }
+ }
+ }
+
+ fn withdraw_asset(
+ what: &MultiAsset,
+ from: &MultiLocation,
+ _maybe_context: Option<&XcmContext>,
+ ) -> Result<staging_xcm_executor::Assets, XcmError> {
+ let from = T::LocationToAccountId::convert_location(from)
+ .ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+ let collection_locality = Self::asset_to_collection(&what.id)?;
+ let dispatch = T::CollectionDispatch::dispatch(*collection_locality)
+ .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;
+
+ let collection = dispatch.as_dyn();
+ let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;
+
+ match what.fun {
+ Fungibility::Fungible(amount) => xcm_ext
+ .burn_item(from, TokenId::default(), amount)
+ .map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,
+
+ Fungibility::NonFungible(asset_instance) => {
+ Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;
+ }
+ }
+
+ Ok(what.clone().into())
+ }
+
+ fn internal_transfer_asset(
+ what: &MultiAsset,
+ from: &MultiLocation,
+ to: &MultiLocation,
+ _context: &XcmContext,
+ ) -> Result<staging_xcm_executor::Assets, XcmError> {
+ let from = T::LocationToAccountId::convert_location(from)
+ .ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+ let to = T::LocationToAccountId::convert_location(to)
+ .ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+ let collection_locality = Self::asset_to_collection(&what.id)?;
+
+ let dispatch = T::CollectionDispatch::dispatch(*collection_locality)
+ .map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;
+ let collection = dispatch.as_dyn();
+ let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;
+
+ let depositor = &from;
+
+ let token_id;
+ let amount;
+ let map_error: fn(DispatchError) -> XcmError;
+
+ match what.fun {
+ Fungibility::Fungible(fungible_amount) => {
+ token_id = TokenId::default();
+ amount = fungible_amount;
+ map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");
+ }
+
+ Fungibility::NonFungible(asset_instance) => {
+ token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)
+ .ok_or(XcmExecutorError::InstanceConversionFailed)?;
+
+ amount = 1;
+ map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")
+ }
+ }
+
+ xcm_ext
+ .transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)
+ .map_err(map_error)?;
+
+ Ok(what.clone().into())
}
}
-pub use frame_support::{
- traits::{
- fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,
- },
- weights::{WeightToFee, WeightToFeePolynomial},
-};
+#[derive(Clone, Copy)]
+pub enum CollectionLocality {
+ Local(CollectionId),
+ Foreign(CollectionId),
+}
+
+impl Deref for CollectionLocality {
+ type Target = CollectionId;
-pub struct FreeForAll<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
- Weight,
- Currency::Balance,
- PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Local(id) => id,
+ Self::Foreign(id) => id,
+ }
+ }
+}
-impl<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > WeightTrader for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);
+impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>
+ for CurrencyIdConvert<T>
{
+ fn convert(collection_id: CollectionId) -> Option<MultiLocation> {
+ if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {
+ Some(T::SelfLocation::get())
+ } else {
+ <Pallet<T>>::collection_to_foreign_asset(collection_id)
+ .and_then(|asset_id| match asset_id {
+ AssetId::Concrete(location) => Some(location),
+ _ => None,
+ })
+ .or_else(|| {
+ T::SelfLocation::get()
+ .pushed_with_interior(GeneralIndex(collection_id.0.into()))
+ .ok()
+ })
+ }
+ }
+}
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+pub enum ForeignCollectionMode {
+ NFT,
+ Fungible(u8),
+}
+
+impl From<ForeignCollectionMode> for CollectionMode {
+ fn from(value: ForeignCollectionMode) -> Self {
+ match value {
+ ForeignCollectionMode::NFT => Self::NFT,
+ ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),
+ }
+ }
+}
+
+pub struct FreeForAll;
+
+impl WeightTrader for FreeForAll {
fn new() -> Self {
- Self(Weight::default(), Zero::zero(), PhantomData)
+ Self
}
fn buy_weight(
@@ -484,17 +585,5 @@
) -> Result<Assets, XcmError> {
log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);
Ok(payment)
- }
-}
-impl<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> Drop
- for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-where
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-{
- fn drop(&mut self) {
- OnUnbalanced::on_unbalanced(Currency::issue(self.1));
}
}
pallets/fungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/fungible/src/benchmarking.rs
+++ b/pallets/fungible/src/benchmarking.rs
@@ -15,7 +15,9 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
use frame_benchmarking::{account, v2::*};
-use pallet_common::{bench_init, benchmarking::create_collection_raw};
+use pallet_common::{
+ bench_init, benchmarking::create_collection_raw, CollectionIssuer, Pallet as PalletCommon,
+};
use sp_std::prelude::*;
use up_data_structs::{budget::Unlimited, CollectionMode, MAX_ITEMS_PER_BATCH};
@@ -30,7 +32,9 @@
create_collection_raw(
owner,
CollectionMode::Fungible(0),
- |owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+ |owner: T::CrossAccountId, data| {
+ <PalletCommon<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+ },
FungibleHandle::cast,
)
}
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -19,7 +19,7 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
use pallet_common::{
weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo,
- Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
+ Error as CommonError, SelfWeightOf as PalletCommonWeightOf, XcmExtensions,
};
use sp_runtime::{ArithmeticError, DispatchError};
use sp_std::{vec, vec::Vec};
@@ -114,7 +114,7 @@
<Pallet<T>>::create_item(self, &sender, (to, fungible_data.value), nesting_budget),
<CommonWeights<T>>::create_item(&data),
),
- _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
}
}
@@ -133,7 +133,7 @@
.checked_add(data.value)
.ok_or(ArithmeticError::Overflow)?;
}
- _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
}
}
@@ -152,7 +152,7 @@
let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
let data = match data {
up_data_structs::CreateItemExData::Fungible(f) => f,
- _ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
};
with_weight(
@@ -426,10 +426,6 @@
return 0;
}
<Allowance<T>>::get((self.id, sender, spender))
- }
-
- fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {
- None
}
fn total_pieces(&self, token: TokenId) -> Option<u128> {
@@ -439,6 +435,10 @@
<TotalSupply<T>>::try_get(self.id).ok()
}
+ fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+ Some(self)
+ }
+
fn set_allowance_for_all(
&self,
_owner: T::CrossAccountId,
@@ -457,3 +457,57 @@
fail!(<Error<T>>::FungibleTokensAreAlwaysValid)
}
}
+
+impl<T: Config> XcmExtensions<T> for FungibleHandle<T> {
+ fn create_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ data: CreateItemData,
+ nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, sp_runtime::DispatchError> {
+ match &data {
+ up_data_structs::CreateItemData::Fungible(fungible_data) => {
+ <Pallet<T>>::create_multiple_items(
+ self,
+ depositor,
+ [(to, fungible_data.value)].into_iter().collect(),
+ nesting_budget,
+ )?
+ }
+ _ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+ }
+
+ Ok(TokenId::default())
+ }
+
+ fn transfer_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ from: &<T>::CrossAccountId,
+ to: &<T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(
+ token == TokenId::default(),
+ <CommonError<T>>::FungibleItemsHaveNoId
+ );
+
+ <Pallet<T>>::transfer_internal(self, depositor, from, to, amount, nesting_budget)
+ .map(|_| ())
+ .map_err(|post_info| post_info.error)
+ }
+
+ fn burn_item_internal(
+ &self,
+ from: <T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> sp_runtime::DispatchResult {
+ <Self as CommonCollectionOperations<T>>::burn_item(self, from, token, amount)
+ .map(|_| ())
+ .map_err(|post_info| post_info.error)
+ }
+}
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -95,8 +95,8 @@
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
use sp_std::{collections::btree_map::BTreeMap, vec::Vec};
use up_data_structs::{
- budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,
- Property, PropertyKey, TokenId,
+ budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, Property, PropertyKey,
+ TokenId,
};
use weights::WeightInfo;
@@ -121,8 +121,6 @@
#[pallet::error]
pub enum Error<T> {
- /// Not Fungible item data used to mint in Fungible collection.
- NotFungibleDataUsedToMintFungibleCollectionToken,
/// Tried to set data for fungible item.
FungibleItemsDontHaveData,
/// Fungible token does not support nesting.
@@ -212,24 +210,6 @@
/// Pallet implementation for fungible assets
impl<T: Config> Pallet<T> {
- /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.
- pub fn init_collection(
- owner: T::CrossAccountId,
- payer: T::CrossAccountId,
- data: CreateCollectionData<T::CrossAccountId>,
- ) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, payer, data)
- }
-
- /// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.
- pub fn init_foreign_collection(
- owner: T::CrossAccountId,
- payer: T::CrossAccountId,
- data: CreateCollectionData<T::CrossAccountId>,
- ) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_foreign_collection(owner, payer, data)
- }
-
/// Destroys a collection.
pub fn destroy_collection(
collection: FungibleHandle<T>,
@@ -293,54 +273,11 @@
let balance = <Balance<T>>::get((collection.id, owner))
.checked_sub(amount)
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-
- // Foreign collection check
- ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(owner)?;
- }
-
- // =========
-
- if balance == 0 {
- <Balance<T>>::remove((collection.id, owner));
- <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());
- } else {
- <Balance<T>>::insert((collection.id, owner), balance);
}
- <TotalSupply<T>>::insert(collection.id, total_supply);
-
- <PalletEvm<T>>::deposit_log(
- ERC20Events::Transfer {
- from: *owner.as_eth(),
- to: H160::default(),
- value: amount.into(),
- }
- .to_log(collection_id_to_address(collection.id)),
- );
- <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
- collection.id,
- TokenId::default(),
- owner.clone(),
- amount,
- ));
- Ok(())
- }
- /// Burns the specified amount of the token.
- pub fn burn_foreign(
- collection: &FungibleHandle<T>,
- owner: &T::CrossAccountId,
- amount: u128,
- ) -> DispatchResult {
- let total_supply = <TotalSupply<T>>::get(collection.id)
- .checked_sub(amount)
- .ok_or(<CommonError<T>>::TokenValueTooLow)?;
-
- let balance = <Balance<T>>::get((collection.id, owner))
- .checked_sub(amount)
- .ok_or(<CommonError<T>>::TokenValueTooLow)?;
// =========
if balance == 0 {
@@ -468,14 +405,25 @@
}
/// Minting tokens for multiple IDs.
- /// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]
- /// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]
- pub fn create_multiple_items_common(
+ /// See [`create_item`][`Pallet::create_item`] for more details.
+ pub fn create_multiple_items(
collection: &FungibleHandle<T>,
- sender: &T::CrossAccountId,
+ depositor: &T::CrossAccountId,
data: BTreeMap<T::CrossAccountId, u128>,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ if !collection.is_owner_or_admin(depositor) {
+ ensure!(
+ collection.permissions.mint_mode(),
+ <CommonError<T>>::PublicMintingNotAllowed
+ );
+ collection.check_allowlist(depositor)?;
+
+ for (owner, _) in data.iter() {
+ collection.check_allowlist(owner)?;
+ }
+ }
+
let total_supply = data
.values()
.copied()
@@ -486,7 +434,7 @@
for (to, _) in data.iter() {
<PalletStructure<T>>::check_nesting(
- sender,
+ depositor,
to,
collection.id,
TokenId::default(),
@@ -532,44 +480,7 @@
Ok(())
}
-
- /// Minting tokens for multiple IDs.
- /// See [`create_item`][`Pallet::create_item`] for more details.
- pub fn create_multiple_items(
- collection: &FungibleHandle<T>,
- sender: &T::CrossAccountId,
- data: BTreeMap<T::CrossAccountId, u128>,
- nesting_budget: &dyn Budget,
- ) -> DispatchResult {
- // Foreign collection check
- ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
- if !collection.is_owner_or_admin(sender) {
- ensure!(
- collection.permissions.mint_mode(),
- <CommonError<T>>::PublicMintingNotAllowed
- );
- collection.check_allowlist(sender)?;
-
- for (owner, _) in data.iter() {
- collection.check_allowlist(owner)?;
- }
- }
-
- Self::create_multiple_items_common(collection, sender, data, nesting_budget)
- }
-
- /// Minting tokens for multiple IDs.
- /// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.
- pub fn create_multiple_items_foreign(
- collection: &FungibleHandle<T>,
- sender: &T::CrossAccountId,
- data: BTreeMap<T::CrossAccountId, u128>,
- nesting_budget: &dyn Budget,
- ) -> DispatchResult {
- Self::create_multiple_items_common(collection, sender, data, nesting_budget)
- }
-
fn set_allowance_unchecked(
collection: &FungibleHandle<T>,
owner: &T::CrossAccountId,
@@ -795,24 +706,6 @@
nesting_budget: &dyn Budget,
) -> DispatchResult {
Self::create_multiple_items(
- collection,
- sender,
- [(data.0, data.1)].into_iter().collect(),
- nesting_budget,
- )
- }
-
- /// Creates fungible token.
- ///
- /// - `data`: Contains user who will become the owners of the tokens and amount
- /// of tokens he will receive.
- pub fn create_item_foreign(
- collection: &FungibleHandle<T>,
- sender: &T::CrossAccountId,
- data: CreateItemData<T>,
- nesting_budget: &dyn Budget,
- ) -> DispatchResult {
- Self::create_multiple_items_foreign(
collection,
sender,
[(data.0, data.1)].into_iter().collect(),
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -18,6 +18,7 @@
use pallet_common::{
bench_init,
benchmarking::{create_collection_raw, property_key, property_value},
+ CollectionIssuer, Pallet as PalletCommon,
};
use sp_std::prelude::*;
use up_data_structs::{
@@ -56,7 +57,9 @@
create_collection_raw(
owner,
CollectionMode::NFT,
- |owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+ |owner: T::CrossAccountId, data| {
+ <PalletCommon<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+ },
NonfungibleHandle::cast,
)
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -19,8 +19,8 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
use pallet_common::{
weights::WeightInfo as _, with_weight, write_token_properties_total_weight,
- CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions,
- SelfWeightOf as PalletCommonWeightOf,
+ CommonCollectionOperations, CommonWeightInfo, SelfWeightOf as PalletCommonWeightOf,
+ XcmExtensions,
};
use pallet_structure::Pallet as PalletStructure;
use sp_runtime::DispatchError;
@@ -533,10 +533,6 @@
} else {
0
}
- }
-
- fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {
- None
}
fn total_pieces(&self, token: TokenId) -> Option<u128> {
@@ -547,6 +543,10 @@
}
}
+ fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+ Some(self)
+ }
+
fn set_allowance_for_all(
&self,
owner: T::CrossAccountId,
@@ -570,3 +570,53 @@
)
}
}
+
+impl<T: Config> XcmExtensions<T> for NonfungibleHandle<T> {
+ fn token_has_children(&self, token: TokenId) -> bool {
+ <Pallet<T>>::token_has_children(self.id, token)
+ }
+
+ fn create_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ data: up_data_structs::CreateItemData,
+ nesting_budget: &dyn Budget,
+ ) -> Result<TokenId, sp_runtime::DispatchError> {
+ <Pallet<T>>::create_multiple_items(
+ self,
+ depositor,
+ vec![map_create_data::<T>(data, &to)?],
+ nesting_budget,
+ )?;
+
+ Ok(self.last_token_id())
+ }
+
+ fn transfer_item_internal(
+ &self,
+ depositor: &<T>::CrossAccountId,
+ from: &<T>::CrossAccountId,
+ to: &<T>::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+ <Pallet<T>>::transfer_internal(self, depositor, from, to, token, nesting_budget)
+ .map(|_| ())
+ .map_err(|post_info| post_info.error)
+ }
+
+ fn burn_item_internal(
+ &self,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> sp_runtime::DispatchResult {
+ ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+ <Pallet<T>>::burn(self, &from, token)
+ }
+}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -117,8 +117,8 @@
use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};
use up_data_structs::{
budget::Budget, mapping::TokenAddressMapping, AccessMode, AuxPropertyValue, CollectionId,
- CreateCollectionData, CreateNftExData, CustomDataLimit, PropertiesPermissionMap, Property,
- PropertyKey, PropertyKeyPermission, PropertyScope, PropertyValue, TokenChild, TokenId,
+ CreateNftExData, CustomDataLimit, PropertiesPermissionMap, Property, PropertyKey,
+ PropertyKeyPermission, PropertyScope, PropertyValue, TokenChild, TokenId,
TokenProperties as TokenPropertiesT,
};
use weights::WeightInfo;
@@ -383,19 +383,6 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- /// Create NFT collection
- ///
- /// `init_collection` will take non-refundable deposit for collection creation.
- ///
- /// - `data`: Contains settings for collection limits and permissions.
- pub fn init_collection(
- owner: T::CrossAccountId,
- payer: T::CrossAccountId,
- data: CreateCollectionData<T::CrossAccountId>,
- ) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, payer, data)
- }
-
/// Destroy NFT collection
///
/// `destroy_collection` will throw error if collection contains any tokens.
pallets/refungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -20,6 +20,7 @@
use pallet_common::{
bench_init,
benchmarking::{create_collection_raw, property_key, property_value},
+ CollectionIssuer, Pallet as PalletCommon,
};
use sp_std::prelude::*;
use up_data_structs::{
@@ -61,7 +62,9 @@
create_collection_raw(
owner,
CollectionMode::ReFungible,
- |owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+ |owner: T::CrossAccountId, data| {
+ <PalletCommon<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+ },
RefungibleHandle::cast,
)
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -103,7 +103,7 @@
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};
use up_data_structs::{
- budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,
+ budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId,
CreateRefungibleExMultipleOwners, PropertiesPermissionMap, Property, PropertyKey,
PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TokenOwnerError,
TokenProperties as TokenPropertiesT, MAX_REFUNGIBLE_PIECES,
@@ -296,19 +296,6 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- /// Create RFT collection
- ///
- /// `init_collection` will take non-refundable deposit for collection creation.
- ///
- /// - `data`: Contains settings for collection limits and permissions.
- pub fn init_collection(
- owner: T::CrossAccountId,
- payer: T::CrossAccountId,
- data: CreateCollectionData<T::CrossAccountId>,
- ) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, payer, data)
- }
-
/// Destroy RFT collection
///
/// `destroy_collection` will throw error if collection contains any tokens.
pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -16,7 +16,7 @@
use frame_benchmarking::v2::{account, benchmarks, BenchmarkError};
use frame_support::traits::{fungible::Balanced, tokens::Precision, Get};
-use pallet_common::Config as CommonConfig;
+use pallet_common::{CollectionIssuer, Config as CommonConfig};
use pallet_evm::account::CrossAccountId;
use sp_std::vec;
use up_data_structs::{
@@ -44,7 +44,7 @@
.unwrap();
T::CollectionDispatch::create(
caller_cross.clone(),
- caller_cross.clone(),
+ CollectionIssuer::User(caller_cross.clone()),
CreateCollectionData {
mode: CollectionMode::NFT,
..Default::default()
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -26,7 +26,7 @@
dispatch::CollectionDispatch,
erc::{static_property::key, CollectionHelpersEvents},
eth::{self, collection_id_to_address, map_eth_to_id},
- CollectionById, CollectionHandle, Pallet as PalletCommon,
+ CollectionById, CollectionHandle, CollectionIssuer, Pallet as PalletCommon,
};
use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
use pallet_evm_coder_substrate::{
@@ -110,8 +110,12 @@
let collection_helpers_address =
T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
- let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
- .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+ let collection_id = T::CollectionDispatch::create(
+ caller,
+ CollectionIssuer::User(collection_helpers_address),
+ data,
+ )
+ .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
}
@@ -240,8 +244,12 @@
let collection_helpers_address =
T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
- let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
- .map_err(dispatch_to_evm::<T>)?;
+ let collection_id = T::CollectionDispatch::create(
+ caller,
+ CollectionIssuer::User(collection_helpers_address),
+ data,
+ )
+ .map_err(dispatch_to_evm::<T>)?;
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
@@ -274,8 +282,12 @@
check_sent_amount_equals_collection_creation_price::<T>(value)?;
let collection_helpers_address =
T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
- let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
- .map_err(dispatch_to_evm::<T>)?;
+ let collection_id = T::CollectionDispatch::create(
+ caller,
+ CollectionIssuer::User(collection_helpers_address),
+ data,
+ )
+ .map_err(dispatch_to_evm::<T>)?;
let address = pallet_common::eth::collection_id_to_address(collection_id);
Ok(address)
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -199,7 +199,7 @@
type CollectionFlags is uint8;
library CollectionFlagsLib {
- /// Tokens in foreign collections can be transferred, but not burnt
+ /// A collection of foreign assets
CollectionFlags constant foreignField = CollectionFlags.wrap(128);
/// Supports ERC721Metadata
CollectionFlags constant erc721metadataField = CollectionFlags.wrap(64);
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -93,7 +93,8 @@
use frame_system::{ensure_root, ensure_signed};
use pallet_common::{
dispatch::{dispatch_tx, CollectionDispatch},
- CollectionHandle, CommonWeightInfo, Pallet as PalletCommon, RefungibleExtensionsWeightInfo,
+ CollectionHandle, CollectionIssuer, CommonWeightInfo, Pallet as PalletCommon,
+ RefungibleExtensionsWeightInfo,
};
use pallet_evm::account::CrossAccountId;
use pallet_structure::weights::WeightInfo as StructureWeightInfo;
@@ -401,7 +402,11 @@
// =========
let sender = T::CrossAccountId::from_sub(sender);
- let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;
+ let _id = T::CollectionDispatch::create(
+ sender.clone(),
+ CollectionIssuer::User(sender),
+ data,
+ )?;
Ok(())
}
primitives/data-structs/src/budget.rsdiffbeforeafterboth--- a/primitives/data-structs/src/budget.rs
+++ b/primitives/data-structs/src/budget.rs
@@ -36,3 +36,10 @@
true
}
}
+
+pub struct ZeroBudget;
+impl Budget for ZeroBudget {
+ fn consume_custom(&self, _calls: u32) -> bool {
+ false
+ }
+}
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -378,7 +378,7 @@
#[derive(AbiCoderFlags, Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)]
#[bondrewd(enforce_bytes = 1)]
pub struct CollectionFlags {
- /// Tokens in foreign collections can be transferred, but not burnt
+ /// A collection of foreign assets
#[bondrewd(bits = "0..1")]
pub foreign: bool,
/// Supports ERC721Metadata
runtime/common/config/orml.rsdiffbeforeafterboth--- a/runtime/common/config/orml.rs
+++ b/runtime/common/config/orml.rs
@@ -14,31 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-use frame_support::{
- parameter_types,
- traits::{Contains, Everything},
-};
+use frame_support::{parameter_types, traits::Everything};
use frame_system::EnsureSigned;
use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key};
-use pallet_foreign_assets::{CurrencyId, NativeCurrency};
+use pallet_foreign_assets::CurrencyIdConvert;
use sp_runtime::traits::Convert;
-use sp_std::{vec, vec::Vec};
use staging_xcm::latest::{Junction::*, Junctions::*, MultiLocation, Weight};
use staging_xcm_executor::XcmExecutor;
use up_common::{
constants::*,
types::{AccountId, Balance},
};
+use up_data_structs::CollectionId;
use crate::{
- runtime_common::config::{
- pallets::TreasuryAccountId,
- substrate::{MaxLocks, MaxReserves},
- xcm::{
- xcm_assets::CurrencyIdConvert, SelfLocation, UniversalLocation, Weigher,
- XcmExecutorConfig,
- },
- },
+ runtime_common::config::xcm::{SelfLocation, UniversalLocation, Weigher, XcmExecutorConfig},
RelayChainBlockNumberProvider, Runtime, RuntimeEvent,
};
@@ -59,29 +49,6 @@
};
}
-parameter_type_with_key! {
- pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance {
- match currency_id {
- CurrencyId::NativeAssetId(symbol) => match symbol {
- NativeCurrency::Here => 0,
- NativeCurrency::Parent=> 0,
- },
- _ => 100_000
- }
- };
-}
-
-pub fn get_all_module_accounts() -> Vec<AccountId> {
- vec![TreasuryAccountId::get()]
-}
-
-pub struct DustRemovalWhitelist;
-impl Contains<AccountId> for DustRemovalWhitelist {
- fn contains(a: &AccountId) -> bool {
- get_all_module_accounts().contains(a)
- }
-}
-
pub struct AccountIdToMultiLocation;
impl Convert<AccountId, MultiLocation> for AccountIdToMultiLocation {
fn convert(account: AccountId) -> MultiLocation {
@@ -91,18 +58,6 @@
})
.into()
}
-}
-
-pub struct CurrencyHooks;
-impl orml_traits::currency::MutationHooks<AccountId, CurrencyId, Balance> for CurrencyHooks {
- type OnDust = orml_tokens::TransferDust<Runtime, TreasuryAccountId>;
- type OnSlash = ();
- type PreTransfer = ();
- type PostTransfer = ();
- type PreDeposit = ();
- type PostDeposit = ();
- type OnNewTokenAccount = ();
- type OnKilledTokenAccount = ();
}
impl orml_vesting::Config for Runtime {
@@ -113,29 +68,13 @@
type WeightInfo = ();
type MaxVestingSchedules = MaxVestingSchedules;
type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-impl orml_tokens::Config for Runtime {
- type RuntimeEvent = RuntimeEvent;
- type Balance = Balance;
- type Amount = Amount;
- type CurrencyId = CurrencyId;
- type WeightInfo = ();
- type ExistentialDeposits = ExistentialDeposits;
- type CurrencyHooks = CurrencyHooks;
- type MaxLocks = MaxLocks;
- type MaxReserves = MaxReserves;
- // TODO: Add all module accounts
- type DustRemovalWhitelist = DustRemovalWhitelist;
- /// The id type for named reserves.
- type ReserveIdentifier = ();
}
impl orml_xtokens::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
- type CurrencyId = CurrencyId;
- type CurrencyIdConvert = CurrencyIdConvert;
+ type CurrencyId = CollectionId;
+ type CurrencyIdConvert = CurrencyIdConvert<Self>;
type AccountIdToMultiLocation = AccountIdToMultiLocation;
type SelfLocation = SelfLocation;
type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
runtime/common/config/pallets/foreign_asset.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/foreign_asset.rs
+++ b/runtime/common/config/pallets/foreign_asset.rs
@@ -1,10 +1,52 @@
-use up_common::types::AccountId;
+use frame_support::{parameter_types, PalletId};
+#[cfg(not(feature = "governance"))]
+use frame_system::EnsureRoot;
+use pallet_evm::account::CrossAccountId;
+use sp_core::H160;
+use staging_xcm::prelude::*;
+use staging_xcm_builder::AccountKey20Aliases;
+
+#[cfg(feature = "governance")]
+use crate::runtime_common::config::governance;
+use crate::{
+ runtime_common::config::{
+ ethereum::CrossAccountId as ConfigCrossAccountId,
+ xcm::{LocationToAccountId, SelfLocation},
+ },
+ RelayNetwork, Runtime, RuntimeEvent,
+};
+
+parameter_types! {
+ pub ForeignAssetPalletId: PalletId = PalletId(*b"frgnasts");
+}
+
+pub struct LocationToCrossAccountId;
+impl staging_xcm_executor::traits::ConvertLocation<ConfigCrossAccountId>
+ for LocationToCrossAccountId
+{
+ fn convert_location(location: &MultiLocation) -> Option<ConfigCrossAccountId> {
+ LocationToAccountId::convert_location(location)
+ .map(ConfigCrossAccountId::from_sub)
+ .or_else(|| {
+ let eth_address =
+ AccountKey20Aliases::<RelayNetwork, H160>::convert_location(location)?;
-use crate::{Balances, Runtime, RuntimeEvent};
+ Some(ConfigCrossAccountId::from_eth(eth_address))
+ })
+ }
+}
impl pallet_foreign_assets::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- type Currency = Balances;
- type RegisterOrigin = frame_system::EnsureRoot<AccountId>;
+
+ #[cfg(feature = "governance")]
+ type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember;
+
+ #[cfg(not(feature = "governance"))]
+ type ForceRegisterOrigin = EnsureRoot<Self::AccountId>;
+
+ type PalletId = ForeignAssetPalletId;
+ type SelfLocation = SelfLocation;
+ type LocationToAccountId = LocationToCrossAccountId;
type WeightInfo = pallet_foreign_assets::weights::SubstrateWeight<Self>;
}
runtime/common/config/xcm.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/config/xcm.rs
@@ -0,0 +1,254 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+use cumulus_primitives_core::ParaId;
+use frame_support::{
+ parameter_types,
+ traits::{ConstU32, Everything, Get, Nothing, ProcessMessageError},
+};
+use frame_system::EnsureRoot;
+use orml_traits::location::AbsoluteReserveProvider;
+use orml_xcm_support::MultiNativeAsset;
+use pallet_foreign_assets::FreeForAll;
+use pallet_xcm::XcmPassthrough;
+use polkadot_parachain_primitives::primitives::Sibling;
+use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
+use sp_std::marker::PhantomData;
+use staging_xcm::{
+ latest::{prelude::*, MultiLocation, Weight},
+ v3::Instruction,
+};
+use staging_xcm_builder::{
+ AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative,
+ SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
+ SignedToAccountId32, SovereignSignedViaLocation,
+};
+use staging_xcm_executor::{
+ traits::{Properties, ShouldExecute},
+ XcmExecutor,
+};
+use up_common::types::AccountId;
+
+#[cfg(feature = "governance")]
+use crate::runtime_common::config::governance;
+use crate::{
+ xcm_barrier::Barrier, AllPalletsWithSystem, Balances, ForeignAssets, ParachainInfo,
+ ParachainSystem, PolkadotXcm, RelayNetwork, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
+ XcmpQueue,
+};
+
+parameter_types! {
+ pub const RelayLocation: MultiLocation = MultiLocation::parent();
+ pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
+ pub UniversalLocation: InteriorMultiLocation = (
+ GlobalConsensus(crate::RelayNetwork::get()),
+ Parachain(ParachainInfo::get().into()),
+ ).into();
+ pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
+
+ // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
+ pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1000); // ?
+ pub const MaxInstructions: u32 = 100;
+}
+
+/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used
+/// when determining ownership of accounts for asset transacting and when attempting to use XCM
+/// `Transact` in order to determine the dispatch Origin.
+pub type LocationToAccountId = (
+ // The parent (Relay-chain) origin converts to the default `AccountId`.
+ ParentIsPreset<AccountId>,
+ // Sibling parachain origins convert to AccountId via the `ParaId::into`.
+ SiblingParachainConvertsVia<Sibling, AccountId>,
+ // Straight up local `AccountId32` origins just alias directly to `AccountId`.
+ AccountId32Aliases<RelayNetwork, AccountId>,
+);
+
+/// No local origins on this chain are allowed to dispatch XCM sends/executions.
+pub type LocalOriginToLocation = (SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>,);
+
+/// The means for routing XCM messages which are not for local execution into the right message
+/// queues.
+pub type XcmRouter = (
+ // Two routers - use UMP to communicate with the relay chain:
+ cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
+ // ..and XCMP to communicate with the sibling chains.
+ XcmpQueue,
+);
+
+/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
+/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
+/// biases the kind of local `Origin` it will become.
+pub type XcmOriginToTransactDispatchOrigin = (
+ // Sovereign account converter; this attempts to derive an `AccountId` from the origin location
+ // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
+ // foreign chains who want to have a local sovereign account on this chain which they control.
+ SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
+ // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
+ // recognised.
+ RelayChainAsNative<RelayOrigin, RuntimeOrigin>,
+ // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
+ // recognised.
+ SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
+ // Native signed account converter; this just converts an `AccountId32` origin into a normal
+ // `Origin::Signed` origin of the same 32-byte value.
+ SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
+ // Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
+ XcmPassthrough<RuntimeOrigin>,
+);
+
+pub trait TryPass {
+ fn try_pass<Call>(
+ origin: &MultiLocation,
+ message: &mut [Instruction<Call>],
+ ) -> Result<(), ProcessMessageError>;
+}
+
+#[impl_trait_for_tuples::impl_for_tuples(30)]
+impl TryPass for Tuple {
+ fn try_pass<Call>(
+ origin: &MultiLocation,
+ message: &mut [Instruction<Call>],
+ ) -> Result<(), ProcessMessageError> {
+ for_tuples!( #(
+ Tuple::try_pass(origin, message)?;
+ )* );
+
+ Ok(())
+ }
+}
+
+/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
+/// If it passes the Deny, and matches one of the Allow cases then it is let through.
+pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
+where
+ Deny: TryPass,
+ Allow: ShouldExecute;
+
+impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
+where
+ Deny: TryPass,
+ Allow: ShouldExecute,
+{
+ fn should_execute<Call>(
+ origin: &MultiLocation,
+ message: &mut [Instruction<Call>],
+ max_weight: Weight,
+ properties: &mut Properties,
+ ) -> Result<(), ProcessMessageError> {
+ Deny::try_pass(origin, message)?;
+ Allow::should_execute(origin, message, max_weight, properties)
+ }
+}
+
+pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
+
+pub type IsReserve = MultiNativeAsset<AbsoluteReserveProvider>;
+
+pub type Trader = FreeForAll;
+
+pub struct XcmExecutorConfig<T>(PhantomData<T>);
+impl<T> staging_xcm_executor::Config for XcmExecutorConfig<T>
+where
+ T: pallet_configuration::Config,
+{
+ type RuntimeCall = RuntimeCall;
+ type XcmSender = XcmRouter;
+ // How to withdraw and deposit an asset.
+ type AssetTransactor = ForeignAssets;
+ type OriginConverter = XcmOriginToTransactDispatchOrigin;
+ type IsReserve = IsReserve;
+ type IsTeleporter = (); // Teleportation is disabled
+ type UniversalLocation = UniversalLocation;
+ type Barrier = Barrier;
+ type Weigher = Weigher;
+ type Trader = Trader;
+ type ResponseHandler = PolkadotXcm;
+ type SubscriptionService = PolkadotXcm;
+ type PalletInstancesInfo = AllPalletsWithSystem;
+ type MaxAssetsIntoHolding = ConstU32<8>;
+
+ type AssetTrap = PolkadotXcm;
+ type AssetClaims = PolkadotXcm;
+ type AssetLocker = ();
+ type AssetExchanger = ();
+ type FeeManager = ();
+ type MessageExporter = ();
+ type UniversalAliases = Nothing;
+ type CallDispatcher = RuntimeCall;
+ type SafeCallFilter = Nothing;
+ type Aliasers = Nothing;
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+parameter_types! {
+ pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
+}
+
+impl pallet_xcm::Config for Runtime {
+ type RuntimeEvent = RuntimeEvent;
+ type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
+ type XcmRouter = XcmRouter;
+ type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
+ type XcmExecuteFilter = Everything;
+ type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+ type XcmTeleportFilter = Everything;
+ type XcmReserveTransferFilter = Everything;
+ type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
+ type RuntimeOrigin = RuntimeOrigin;
+ type RuntimeCall = RuntimeCall;
+ const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
+ type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
+ type UniversalLocation = UniversalLocation;
+ type Currency = Balances;
+ type CurrencyMatcher = ();
+ type TrustedLockers = ();
+ type SovereignAccountOf = LocationToAccountId;
+ type MaxLockers = ConstU32<8>;
+ type WeightInfo = crate::weights::xcm::SubstrateWeight<Runtime>;
+ type AdminOrigin = EnsureRoot<AccountId>;
+ type MaxRemoteLockConsumers = ConstU32<0>;
+ type RemoteLockConsumerIdentifier = ();
+ #[cfg(feature = "runtime-benchmarks")]
+ type ReachableDest = ReachableDest;
+}
+
+impl cumulus_pallet_xcm::Config for Runtime {
+ type RuntimeEvent = RuntimeEvent;
+ type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+}
+impl cumulus_pallet_xcmp_queue::Config for Runtime {
+ type WeightInfo = ();
+ type RuntimeEvent = RuntimeEvent;
+ type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+ type ChannelInfo = ParachainSystem;
+ type VersionWrapper = PolkadotXcm;
+ type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+
+ #[cfg(feature = "governance")]
+ type ControllerOrigin = governance::RootOrTechnicalCommitteeMember;
+
+ #[cfg(not(feature = "governance"))]
+ type ControllerOrigin = frame_system::EnsureRoot<AccountId>;
+
+ type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
+ type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
+}
+
+impl cumulus_pallet_dmp_queue::Config for Runtime {
+ type RuntimeEvent = RuntimeEvent;
+ type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+ type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+}
runtime/common/config/xcm/foreignassets.rsdiffbeforeafterboth--- a/runtime/common/config/xcm/foreignassets.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use frame_support::{parameter_types, traits::Get};
-use orml_traits::location::AbsoluteReserveProvider;
-use orml_xcm_support::MultiNativeAsset;
-use pallet_foreign_assets::{
- AssetId, AssetIdMapping, CurrencyId, ForeignAssetId, FreeForAll, NativeCurrency, TryAsForeign,
- XcmForeignAssetIdMapping,
-};
-use sp_runtime::traits::{Convert, MaybeEquivalence};
-use sp_std::marker::PhantomData;
-use staging_xcm::latest::{prelude::*, MultiAsset, MultiLocation};
-use staging_xcm_builder::{ConvertedConcreteId, FungiblesAdapter, NoChecking};
-use staging_xcm_executor::traits::{JustTry, TransactAsset};
-use up_common::types::{AccountId, Balance};
-
-use super::{LocationToAccountId, RelayLocation};
-use crate::{Balances, ForeignAssets, ParachainInfo, PolkadotXcm, Runtime};
-
-parameter_types! {
- pub CheckingAccount: AccountId = PolkadotXcm::check_account();
-}
-
-pub struct AsInnerId<ConvertAssetId>(PhantomData<(AssetId, ConvertAssetId)>);
-impl<ConvertAssetId: MaybeEquivalence<AssetId, AssetId>> MaybeEquivalence<MultiLocation, AssetId>
- for AsInnerId<ConvertAssetId>
-{
- fn convert(id: &MultiLocation) -> Option<AssetId> {
- log::trace!(
- target: "xcm::AsInnerId::Convert",
- "AsInnerId {:?}",
- id
- );
-
- let parent = MultiLocation::parent();
- let here = MultiLocation::here();
- let self_location = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
-
- if *id == parent {
- return ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Parent));
- }
-
- if *id == here || *id == self_location {
- return ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Here));
- }
-
- match XcmForeignAssetIdMapping::<Runtime>::get_currency_id(*id) {
- Some(AssetId::ForeignAssetId(foreign_asset_id)) => {
- ConvertAssetId::convert(&AssetId::ForeignAssetId(foreign_asset_id))
- }
- _ => None,
- }
- }
-
- fn convert_back(asset_id: &AssetId) -> Option<MultiLocation> {
- log::trace!(
- target: "xcm::AsInnerId::Reverse",
- "AsInnerId",
- );
-
- let parent_id =
- ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Parent)).unwrap();
- let here_id =
- ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Here)).unwrap();
-
- if *asset_id == parent_id {
- return Some(MultiLocation::parent());
- }
-
- if *asset_id == here_id {
- return Some(MultiLocation::new(
- 1,
- X1(Parachain(ParachainInfo::get().into())),
- ));
- }
-
- let fid = <AssetId as TryAsForeign<AssetId, ForeignAssetId>>::try_as_foreign(*asset_id)?;
- XcmForeignAssetIdMapping::<Runtime>::get_multi_location(fid)
- }
-}
-
-/// Means for transacting assets besides the native currency on this chain.
-pub type FungiblesTransactor = FungiblesAdapter<
- // Use this fungibles implementation:
- ForeignAssets,
- // Use this currency when it is a fungible asset matching the given location or name:
- ConvertedConcreteId<AssetId, Balance, AsInnerId<JustTry>, JustTry>,
- // Convert an XCM MultiLocation into a local account id:
- LocationToAccountId,
- // Our chain's account ID type (we can't get away without mentioning it explicitly):
- AccountId,
- // No Checking for teleported assets since we disallow teleports at all.
- NoChecking,
- // The account to use for tracking teleports.
- CheckingAccount,
->;
-
-/// Means for transacting assets on this chain.
-pub struct AssetTransactor;
-impl TransactAsset for AssetTransactor {
- fn can_check_in(
- _origin: &MultiLocation,
- _what: &MultiAsset,
- _context: &XcmContext,
- ) -> XcmResult {
- Err(XcmError::Unimplemented)
- }
-
- fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
-
- fn can_check_out(
- _dest: &MultiLocation,
- _what: &MultiAsset,
- _context: &XcmContext,
- ) -> XcmResult {
- Err(XcmError::Unimplemented)
- }
-
- fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
-
- fn deposit_asset(
- what: &MultiAsset,
- who: &MultiLocation,
- context: Option<&XcmContext>,
- ) -> XcmResult {
- FungiblesTransactor::deposit_asset(what, who, context)
- }
-
- fn withdraw_asset(
- what: &MultiAsset,
- who: &MultiLocation,
- maybe_context: Option<&XcmContext>,
- ) -> Result<staging_xcm_executor::Assets, XcmError> {
- FungiblesTransactor::withdraw_asset(what, who, maybe_context)
- }
-
- fn internal_transfer_asset(
- what: &MultiAsset,
- from: &MultiLocation,
- to: &MultiLocation,
- context: &XcmContext,
- ) -> Result<staging_xcm_executor::Assets, XcmError> {
- FungiblesTransactor::internal_transfer_asset(what, from, to, context)
- }
-}
-
-pub type IsReserve = MultiNativeAsset<AbsoluteReserveProvider>;
-
-pub type Trader<T> = FreeForAll<
- pallet_configuration::WeightToFee<T, Balance>,
- RelayLocation,
- AccountId,
- Balances,
- (),
->;
-
-pub struct CurrencyIdConvert;
-impl Convert<AssetId, Option<MultiLocation>> for CurrencyIdConvert {
- fn convert(id: AssetId) -> Option<MultiLocation> {
- match id {
- AssetId::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new(
- 1,
- X1(Parachain(ParachainInfo::get().into())),
- )),
- AssetId::NativeAssetId(NativeCurrency::Parent) => Some(MultiLocation::parent()),
- AssetId::ForeignAssetId(foreign_asset_id) => {
- XcmForeignAssetIdMapping::<Runtime>::get_multi_location(foreign_asset_id)
- }
- }
- }
-}
-
-impl Convert<MultiLocation, Option<CurrencyId>> for CurrencyIdConvert {
- fn convert(location: MultiLocation) -> Option<CurrencyId> {
- if location == MultiLocation::here()
- || location == MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())))
- {
- return Some(AssetId::NativeAssetId(NativeCurrency::Here));
- }
-
- if location == MultiLocation::parent() {
- return Some(AssetId::NativeAssetId(NativeCurrency::Parent));
- }
-
- if let Some(currency_id) = XcmForeignAssetIdMapping::<Runtime>::get_currency_id(location) {
- return Some(currency_id);
- }
-
- None
- }
-}
runtime/common/config/xcm/mod.rsdiffbeforeafterboth--- a/runtime/common/config/xcm/mod.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use cumulus_primitives_core::ParaId;
-use frame_support::{
- parameter_types,
- traits::{ConstU32, Everything, Get, Nothing, ProcessMessageError},
-};
-use frame_system::EnsureRoot;
-use pallet_xcm::XcmPassthrough;
-use polkadot_parachain_primitives::primitives::Sibling;
-use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
-use sp_std::marker::PhantomData;
-use staging_xcm::{
- latest::{prelude::*, MultiLocation, Weight},
- v3::Instruction,
-};
-use staging_xcm_builder::{
- AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative,
- SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
- SignedToAccountId32, SovereignSignedViaLocation,
-};
-use staging_xcm_executor::{
- traits::{Properties, ShouldExecute},
- XcmExecutor,
-};
-use up_common::types::AccountId;
-
-use crate::{
- xcm_barrier::Barrier, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem,
- PolkadotXcm, RelayNetwork, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmpQueue,
-};
-
-#[cfg(feature = "foreign-assets")]
-pub mod foreignassets;
-
-#[cfg(not(feature = "foreign-assets"))]
-pub mod nativeassets;
-
-#[cfg(feature = "foreign-assets")]
-pub use foreignassets as xcm_assets;
-#[cfg(not(feature = "foreign-assets"))]
-pub use nativeassets as xcm_assets;
-use xcm_assets::{AssetTransactor, IsReserve, Trader};
-
-#[cfg(feature = "governance")]
-use crate::runtime_common::config::governance;
-
-parameter_types! {
- pub const RelayLocation: MultiLocation = MultiLocation::parent();
- pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
- pub UniversalLocation: InteriorMultiLocation = (
- GlobalConsensus(crate::RelayNetwork::get()),
- Parachain(ParachainInfo::get().into()),
- ).into();
- pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
-
- // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
- pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1000); // ?
- pub const MaxInstructions: u32 = 100;
-}
-
-/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used
-/// when determining ownership of accounts for asset transacting and when attempting to use XCM
-/// `Transact` in order to determine the dispatch Origin.
-pub type LocationToAccountId = (
- // The parent (Relay-chain) origin converts to the default `AccountId`.
- ParentIsPreset<AccountId>,
- // Sibling parachain origins convert to AccountId via the `ParaId::into`.
- SiblingParachainConvertsVia<Sibling, AccountId>,
- // Straight up local `AccountId32` origins just alias directly to `AccountId`.
- AccountId32Aliases<RelayNetwork, AccountId>,
-);
-
-/// No local origins on this chain are allowed to dispatch XCM sends/executions.
-pub type LocalOriginToLocation = (SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>,);
-
-/// The means for routing XCM messages which are not for local execution into the right message
-/// queues.
-pub type XcmRouter = (
- // Two routers - use UMP to communicate with the relay chain:
- cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
- // ..and XCMP to communicate with the sibling chains.
- XcmpQueue,
-);
-
-/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
-/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
-/// biases the kind of local `Origin` it will become.
-pub type XcmOriginToTransactDispatchOrigin = (
- // Sovereign account converter; this attempts to derive an `AccountId` from the origin location
- // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
- // foreign chains who want to have a local sovereign account on this chain which they control.
- SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
- // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
- // recognised.
- RelayChainAsNative<RelayOrigin, RuntimeOrigin>,
- // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
- // recognised.
- SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
- // Native signed account converter; this just converts an `AccountId32` origin into a normal
- // `Origin::Signed` origin of the same 32-byte value.
- SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
- // Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
- XcmPassthrough<RuntimeOrigin>,
-);
-
-pub trait TryPass {
- fn try_pass<Call>(
- origin: &MultiLocation,
- message: &mut [Instruction<Call>],
- ) -> Result<(), ProcessMessageError>;
-}
-
-#[impl_trait_for_tuples::impl_for_tuples(30)]
-impl TryPass for Tuple {
- fn try_pass<Call>(
- origin: &MultiLocation,
- message: &mut [Instruction<Call>],
- ) -> Result<(), ProcessMessageError> {
- for_tuples!( #(
- Tuple::try_pass(origin, message)?;
- )* );
-
- Ok(())
- }
-}
-
-/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
-/// If it passes the Deny, and matches one of the Allow cases then it is let through.
-pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
-where
- Deny: TryPass,
- Allow: ShouldExecute;
-
-impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
-where
- Deny: TryPass,
- Allow: ShouldExecute,
-{
- fn should_execute<Call>(
- origin: &MultiLocation,
- message: &mut [Instruction<Call>],
- max_weight: Weight,
- properties: &mut Properties,
- ) -> Result<(), ProcessMessageError> {
- Deny::try_pass(origin, message)?;
- Allow::should_execute(origin, message, max_weight, properties)
- }
-}
-
-pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
-
-pub struct XcmExecutorConfig<T>(PhantomData<T>);
-impl<T> staging_xcm_executor::Config for XcmExecutorConfig<T>
-where
- T: pallet_configuration::Config,
-{
- type RuntimeCall = RuntimeCall;
- type XcmSender = XcmRouter;
- // How to withdraw and deposit an asset.
- type AssetTransactor = AssetTransactor;
- type OriginConverter = XcmOriginToTransactDispatchOrigin;
- type IsReserve = IsReserve;
- type IsTeleporter = (); // Teleportation is disabled
- type UniversalLocation = UniversalLocation;
- type Barrier = Barrier;
- type Weigher = Weigher;
- type Trader = Trader<T>;
- type ResponseHandler = PolkadotXcm;
- type SubscriptionService = PolkadotXcm;
- type PalletInstancesInfo = AllPalletsWithSystem;
- type MaxAssetsIntoHolding = ConstU32<8>;
-
- type AssetTrap = PolkadotXcm;
- type AssetClaims = PolkadotXcm;
- type AssetLocker = ();
- type AssetExchanger = ();
- type FeeManager = ();
- type MessageExporter = ();
- type UniversalAliases = Nothing;
- type CallDispatcher = RuntimeCall;
- type SafeCallFilter = Nothing;
- type Aliasers = Nothing;
-}
-
-#[cfg(feature = "runtime-benchmarks")]
-parameter_types! {
- pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
-}
-
-impl pallet_xcm::Config for Runtime {
- type RuntimeEvent = RuntimeEvent;
- type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
- type XcmRouter = XcmRouter;
- type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
- type XcmExecuteFilter = Everything;
- type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
- type XcmTeleportFilter = Everything;
- type XcmReserveTransferFilter = Everything;
- type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
- type RuntimeOrigin = RuntimeOrigin;
- type RuntimeCall = RuntimeCall;
- const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
- type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
- type UniversalLocation = UniversalLocation;
- type Currency = Balances;
- type CurrencyMatcher = ();
- type TrustedLockers = ();
- type SovereignAccountOf = LocationToAccountId;
- type MaxLockers = ConstU32<8>;
- type WeightInfo = crate::weights::xcm::SubstrateWeight<Runtime>;
- type AdminOrigin = EnsureRoot<AccountId>;
- type MaxRemoteLockConsumers = ConstU32<0>;
- type RemoteLockConsumerIdentifier = ();
- #[cfg(feature = "runtime-benchmarks")]
- type ReachableDest = ReachableDest;
-}
-
-impl cumulus_pallet_xcm::Config for Runtime {
- type RuntimeEvent = RuntimeEvent;
- type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-}
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
- type WeightInfo = ();
- type RuntimeEvent = RuntimeEvent;
- type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
- type ChannelInfo = ParachainSystem;
- type VersionWrapper = PolkadotXcm;
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-
- #[cfg(feature = "governance")]
- type ControllerOrigin = governance::RootOrTechnicalCommitteeMember;
-
- #[cfg(not(feature = "governance"))]
- type ControllerOrigin = frame_system::EnsureRoot<AccountId>;
-
- type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
- type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
- type RuntimeEvent = RuntimeEvent;
- type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
runtime/common/config/xcm/nativeassets.rsdiffbeforeafterboth--- a/runtime/common/config/xcm/nativeassets.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use cumulus_primitives_core::XcmContext;
-use frame_support::{
- traits::{tokens::currency::Currency as CurrencyT, Get, OnUnbalanced as OnUnbalancedT},
- weights::WeightToFeePolynomial,
-};
-use pallet_foreign_assets::{AssetIds, NativeCurrency};
-use sp_runtime::traits::{CheckedConversion, Convert, Zero};
-use sp_std::marker::PhantomData;
-use staging_xcm::latest::{
- AssetId::Concrete, Error as XcmError, Fungibility::Fungible as XcmFungible, Junction::*,
- Junctions::*, MultiAsset, MultiLocation, Weight,
-};
-use staging_xcm_builder::{CurrencyAdapter, NativeAsset};
-use staging_xcm_executor::{
- traits::{MatchesFungible, WeightTrader},
- Assets,
-};
-use up_common::types::{AccountId, Balance};
-
-use super::{LocationToAccountId, RelayLocation};
-use crate::{Balances, ParachainInfo};
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
- fn matches_fungible(a: &MultiAsset) -> Option<B> {
- let paraid = Parachain(ParachainInfo::parachain_id().into());
- match (&a.id, &a.fun) {
- (
- Concrete(MultiLocation {
- parents: 1,
- interior: X1(loc),
- }),
- XcmFungible(ref amount),
- ) if paraid == *loc => CheckedConversion::checked_from(*amount),
- (
- Concrete(MultiLocation {
- parents: 0,
- interior: Here,
- }),
- XcmFungible(ref amount),
- ) => CheckedConversion::checked_from(*amount),
- _ => None,
- }
- }
-}
-
-/// Means for transacting assets on this chain.
-pub type LocalAssetTransactor = CurrencyAdapter<
- // Use this currency:
- Balances,
- // Use this currency when it is a fungible asset matching the given location or name:
- OnlySelfCurrency,
- // 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):
- AccountId,
- // We don't track any teleports.
- (),
->;
-
-pub type AssetTransactor = LocalAssetTransactor;
-
-pub type IsReserve = NativeAsset;
-
-pub struct UsingOnlySelfCurrencyComponents<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
- Weight,
- Currency::Balance,
- PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
-impl<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > WeightTrader
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn new() -> Self {
- // FIXME: benchmark
- Self(Weight::from_parts(0, 0), Zero::zero(), PhantomData)
- }
-
- fn buy_weight(
- &mut self,
- _weight: Weight,
- payment: Assets,
- _xcm: &XcmContext,
- ) -> Result<Assets, XcmError> {
- Ok(payment)
- }
-}
-impl<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > Drop
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn drop(&mut self) {
- OnUnbalanced::on_unbalanced(Currency::issue(self.1));
- }
-}
-
-pub type Trader<T> = UsingOnlySelfCurrencyComponents<
- pallet_configuration::WeightToFee<T, Balance>,
- RelayLocation,
- AccountId,
- Balances,
- (),
->;
-
-pub struct CurrencyIdConvert;
-impl Convert<AssetIds, Option<MultiLocation>> for CurrencyIdConvert {
- fn convert(id: AssetIds) -> Option<MultiLocation> {
- match id {
- AssetIds::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new(
- 1,
- X1(Parachain(ParachainInfo::get().into())),
- )),
- _ => None,
- }
- }
-}
runtime/common/construct_runtime.rsdiffbeforeafterboth--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -47,7 +47,7 @@
Vesting: orml_vesting = 37,
XTokens: orml_xtokens = 38,
- Tokens: orml_tokens = 39,
+ // [REMOVED] Tokens: orml_tokens = 39,
// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
#[cfg(feature = "governance")]
runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -17,11 +17,9 @@
use frame_support::{dispatch::DispatchResult, ensure, fail};
use pallet_balances_adapter::NativeFungibleHandle;
pub use pallet_common::dispatch::CollectionDispatch;
-#[cfg(not(feature = "refungible"))]
-use pallet_common::unsupported;
use pallet_common::{
- erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle,
- CommonCollectionOperations,
+ erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle, CollectionIssuer,
+ CommonCollectionOperations, Pallet as PalletCommon,
};
use pallet_evm::{PrecompileHandle, PrecompileResult};
use pallet_fungible::{FungibleHandle, Pallet as PalletFungible};
@@ -70,27 +68,25 @@
fn create(
sender: T::CrossAccountId,
- payer: T::CrossAccountId,
+ issuer: CollectionIssuer<T::CrossAccountId>,
data: CreateCollectionData<T::CrossAccountId>,
) -> Result<CollectionId, DispatchError> {
- let id = match data.mode {
- CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, payer, data)?,
+ match data.mode {
CollectionMode::Fungible(decimal_points) => {
// check params
ensure!(
decimal_points <= MAX_DECIMAL_POINTS,
pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
);
- <PalletFungible<T>>::init_collection(sender, payer, data)?
}
-
- #[cfg(feature = "refungible")]
- CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, payer, data)?,
#[cfg(not(feature = "refungible"))]
CollectionMode::ReFungible => return unsupported!(T),
+
+ _ => {}
};
- Ok(id)
+
+ <PalletCommon<T>>::init_collection(sender, issuer, data)
}
fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
runtime/opal/Cargo.tomldiffbeforeafterboth--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -151,7 +151,6 @@
'up-rpc/std',
'up-sponsorship/std',
- "orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
"orml-xcm-support/std",
@@ -179,7 +178,6 @@
'frame-system/try-runtime',
'frame-try-runtime',
'frame-try-runtime?/try-runtime',
- 'orml-tokens/try-runtime',
'orml-vesting/try-runtime',
'orml-xtokens/try-runtime',
'pallet-app-promotion/try-runtime',
@@ -254,7 +252,6 @@
frame-support = { workspace = true }
frame-system = { workspace = true }
frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
orml-xcm-support = { workspace = true }
runtime/quartz/Cargo.tomldiffbeforeafterboth--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -150,7 +150,6 @@
'up-rpc/std',
'up-sponsorship/std',
- "orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
"orml-xcm-support/std",
@@ -175,7 +174,6 @@
'frame-support/try-runtime',
'frame-system/try-runtime',
'frame-try-runtime',
- 'orml-tokens/try-runtime',
'orml-vesting/try-runtime',
'orml-xtokens/try-runtime',
'pallet-app-promotion/try-runtime',
@@ -242,7 +240,6 @@
frame-support = { workspace = true }
frame-system = { workspace = true }
frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
orml-xcm-support = { workspace = true }
runtime/tests/src/tests.rsdiffbeforeafterboth--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -2357,7 +2357,8 @@
#[test]
fn total_number_collections_bound_neg() {
new_test_ext().execute_with(|| {
- let origin1 = RuntimeOrigin::signed(1);
+ let user = 1;
+ let origin1 = RuntimeOrigin::signed(user);
for i in 1..=COLLECTION_NUMBER_LIMIT {
create_test_collection(&CollectionMode::NFT, CollectionId(i));
@@ -2376,6 +2377,7 @@
};
// 11-th collection in chain. Expects error
+ add_balance(user, CollectionCreationPrice::get() as u64 + 1);
assert_noop!(
Unique::create_collection_ex(origin1, data),
CommonError::<Test>::TotalCollectionsLimitExceeded
runtime/unique/Cargo.tomldiffbeforeafterboth--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -147,7 +147,6 @@
'up-rpc/std',
'up-sponsorship/std',
- "orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
"orml-xcm-support/std",
@@ -173,7 +172,6 @@
'frame-support/try-runtime',
'frame-system/try-runtime',
'frame-try-runtime',
- 'orml-tokens/try-runtime',
'orml-vesting/try-runtime',
'orml-xtokens/try-runtime',
'pallet-app-promotion/try-runtime',
@@ -245,7 +243,6 @@
frame-support = { workspace = true }
frame-system = { workspace = true }
frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
orml-xcm-support = { workspace = true }