diff --git a/.env b/.env index e868c6bcc4..aa7bc4b705 100644 --- a/.env +++ b/.env @@ -16,7 +16,7 @@ POLKADEX_BUILD_BRANCH=v1.1.0 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 diff --git a/.github/workflows/ci-develop.yml b/.github/workflows/ci-develop.yml index e55f503371..e9f200efe0 100644 --- a/.github/workflows/ci-develop.yml +++ b/.github/workflows/ci-develop.yml @@ -35,6 +35,11 @@ jobs: 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') diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index 106c1a0940..1434295f45 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -31,6 +31,10 @@ jobs: 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 diff --git a/.github/workflows/xnft.yml b/.github/workflows/xnft.yml new file mode 100644 index 0000000000..4d2752e580 --- /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 diff --git a/Cargo.lock b/Cargo.lock index cdcbda15c6..3c3939c788 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2567,15 +2567,15 @@ dependencies = [ [[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 @@ dependencies = [ [[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 @@ dependencies = [ [[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 @@ dependencies = [ "impl-trait-for-tuples", "log", "num_enum", - "orml-tokens", "orml-traits", "orml-vesting", "orml-xcm-support", @@ -6705,27 +6704,10 @@ dependencies = [ "num-traits", ] -[[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 @@ dependencies = [ [[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 @@ dependencies = [ [[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 @@ dependencies = [ [[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 @@ dependencies = [ [[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 @@ dependencies = [ "frame-support", "frame-system", "log", - "orml-tokens", "pallet-balances", "pallet-common", "pallet-fungible", @@ -10150,7 +10131,6 @@ dependencies = [ "impl-trait-for-tuples", "log", "num_enum", - "orml-tokens", "orml-traits", "orml-vesting", "orml-xcm-support", @@ -13695,9 +13675,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[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 @@ dependencies = [ "impl-trait-for-tuples", "log", "num_enum", - "orml-tokens", "orml-traits", "orml-vesting", "orml-xcm-support", diff --git a/Cargo.toml b/Cargo.toml index caf2499167..73410c52b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,7 +199,6 @@ frame-try-runtime = { default-features = false, version = "0.31.0" } 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" } diff --git a/js-packages/playgrounds/types.ts b/js-packages/playgrounds/types.ts index ce1dd77b30..d2519e3025 100644 --- a/js-packages/playgrounds/types.ts +++ b/js-packages/playgrounds/types.ts @@ -160,7 +160,7 @@ export enum CollectionFlag { External = 1, /// Supports ERC721Metadata Erc721metadata = 64, - /// Tokens in foreign collections can be transferred, but not burnt + /// A collection of foreign assets Foreign = 128, } diff --git a/js-packages/playgrounds/types.xcm.ts b/js-packages/playgrounds/types.xcm.ts index 161bc78e61..c46a39ec27 100644 --- a/js-packages/playgrounds/types.xcm.ts +++ b/js-packages/playgrounds/types.xcm.ts @@ -27,10 +27,3 @@ export interface DemocracyStandardAccountVote { conviction: number, }, } - -export interface IForeignAssetMetadata { - name?: number | Uint8Array, - symbol?: string, - decimals?: number, - minimalBalance?: bigint, -} \ No newline at end of file diff --git a/js-packages/playgrounds/unique.xcm.ts b/js-packages/playgrounds/unique.xcm.ts index 2f1b7bbd7f..bb0bdc6dd7 100644 --- a/js-packages/playgrounds/unique.xcm.ts +++ b/js-packages/playgrounds/unique.xcm.ts @@ -2,7 +2,7 @@ import {ApiPromise, WsProvider} from '@polkadot/api'; 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 @@ class PolkadexXcmHelperGroup extends HelperGroup { } export class ForeignAssetsGroup extends HelperGroup { - 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 @@ export class AssetsGroup extends HelperGroup { 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]) diff --git a/js-packages/tests/eth/api/CollectionHelpers.sol b/js-packages/tests/eth/api/CollectionHelpers.sol index 7af99d9e38..b68ba2fe13 100644 --- a/js-packages/tests/eth/api/CollectionHelpers.sol +++ b/js-packages/tests/eth/api/CollectionHelpers.sol @@ -132,7 +132,7 @@ struct CreateCollectionData { 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); diff --git a/js-packages/tests/pallet-presence.test.ts b/js-packages/tests/pallet-presence.test.ts index 78bc4b85e7..05c333b3eb 100644 --- a/js-packages/tests/pallet-presence.test.ts +++ b/js-packages/tests/pallet-presence.test.ts @@ -47,7 +47,6 @@ const requiredPallets = [ 'nonfungible', 'charging', 'configuration', - 'tokens', 'xtokens', 'maintenance', ]; diff --git a/js-packages/tests/xcm/xcm.types.ts b/js-packages/tests/xcm/xcm.types.ts index 36e70dfed6..a9de81522e 100644 --- a/js-packages/tests/xcm/xcm.types.ts +++ b/js-packages/tests/xcm/xcm.types.ts @@ -505,4 +505,27 @@ export class XcmTestHelper { 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; + } + }); + } } diff --git a/js-packages/tests/xcm/xcmOpal.test.ts b/js-packages/tests/xcm/xcmOpal.test.ts index 354636e14e..46b6531746 100644 --- 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 relayUrl = config.relayUrl; 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 WESTMINT_DECIMALS = 12; 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { 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}; + + 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'); + } - 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); 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { PalletInstance: STATEMINE_PALLET_INSTANCE, }, { - GeneralIndex: ASSET_ID, + GeneralIndex: USDT_ASSET_ID, }, ]}, }, @@ -217,18 +248,16 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { }); - // 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { // 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { const currencies: any = [ [ - { - NativeAssetId: 'Parent', - }, + relayCollectionId, 50_000_000_000_000_000n, ], ]; diff --git a/js-packages/tests/xcm/xcmQuartz.test.ts b/js-packages/tests/xcm/xcmQuartz.test.ts index 1a2553a51f..fe9f6efa9c 100644 --- a/js-packages/tests/xcm/xcmQuartz.test.ts +++ b/js-packages/tests/xcm/xcmQuartz.test.ts @@ -19,8 +19,7 @@ import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, us 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; @@ -39,6 +38,8 @@ const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n; 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { let balanceBobRelayTokenBefore: bigint; let balanceBobRelayTokenAfter: bigint; + let usdtCollectionId: number; + let relayCollectionId: number; before(async () => { await usingPlaygrounds(async (helper, privateKey) => { @@ -65,6 +68,8 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { // 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { }); 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { 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}; + + 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'); + } - 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); balanceQuartzBefore = await helper.balance.getSubstrate(alice.address); + usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId); }); @@ -248,8 +258,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { // 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { const relayFee = 400_000_000_000_000n; const currencies: [any, bigint][] = [ [ - { - ForeignAssetId: 0, - }, + usdtCollectionId, TRANSFER_AMOUNT, ], [ - { - NativeAssetId: 'Parent', - }, + relayCollectionId, relayFee, ], ]; @@ -321,7 +326,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { 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 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { const currencies: any = [ [ - { - NativeAssetId: 'Parent', - }, + relayCollectionId, TRANSFER_AMOUNT_RELAY, ], ]; @@ -1018,9 +1021,7 @@ describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => { }); itSub('Should connect and send QTZ to Moonriver', async ({helper}) => { - const currencyId = { - NativeAssetId: 'Here', - }; + const currencyId = 0; const dest = { V2: { parents: 1, diff --git a/js-packages/tests/xcm/xcmUnique.test.ts b/js-packages/tests/xcm/xcmUnique.test.ts index b965fbace2..af2778c83a 100644 --- a/js-packages/tests/xcm/xcmUnique.test.ts +++ b/js-packages/tests/xcm/xcmUnique.test.ts @@ -20,7 +20,7 @@ import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usi import {Event} from '@unique/playgrounds/unique.dev.js'; import {hexToString, nToBigInt} from '@polkadot/util'; import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js'; - +import {XcmTestHelper} from './xcm.types.js'; const STATEMINT_PALLET_INSTANCE = 50; @@ -50,6 +50,8 @@ const USDT_ASSET_METADATA_DESCRIPTION = 'USDT'; const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n; const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n; +const testHelper = new XcmTestHelper('unique'); + describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { let alice: IKeyringPair; let bob: IKeyringPair; @@ -68,6 +70,8 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { let balanceBobRelayTokenBefore: bigint; let balanceBobRelayTokenAfter: bigint; + let usdtCollectionId: number; + let relayCollectionId: number; before(async () => { await usingPlaygrounds(async (helper, privateKey) => { @@ -76,6 +80,8 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { // 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) => { @@ -85,21 +91,25 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { }); await usingStatemintPlaygrounds(statemintUrl, 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, @@ -107,6 +117,8 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { USDT_ASSET_AMOUNT, ); + const sovereignFundingAmount = 3_500_000_000n; + // funding parachain sovereing account on Statemint. // The sovereign account should be created before any action // (the assets pallet on Statemint check if the sovereign account exists) @@ -117,31 +129,30 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { await usingPlaygrounds(async (helper) => { const location = { - V2: { - parents: 1, - interior: {X3: [ - { - Parachain: STATEMINT_CHAIN, - }, - { - PalletInstance: STATEMINT_PALLET_INSTANCE, - }, - { - GeneralIndex: USDT_ASSET_ID, - }, - ]}, - }, + parents: 1, + interior: {X3: [ + { + Parachain: STATEMINT_CHAIN, + }, + { + PalletInstance: STATEMINT_PALLET_INSTANCE, + }, + { + GeneralIndex: USDT_ASSET_ID, + }, + ]}, }; + const assetId = {Concrete: location}; + + 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 Unique'); + } - 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); balanceUniqueBefore = await helper.balance.getSubstrate(alice.address); + usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId); }); @@ -259,8 +270,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { // 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}); balanceUniqueAfter = await helper.balance.getSubstrate(alice.address); @@ -299,15 +309,11 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { const relayFee = 400_000_000_000_000n; const currencies: [any, bigint][] = [ [ - { - ForeignAssetId: 0, - }, + usdtCollectionId, TRANSFER_AMOUNT, ], [ - { - NativeAssetId: 'Parent', - }, + relayCollectionId, relayFee, ], ]; @@ -332,7 +338,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { itSub('Should connect and send Relay token to Unique', 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 = { @@ -380,7 +386,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { 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 wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore; @@ -420,9 +426,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { const currencies: any = [ [ - { - NativeAssetId: 'Parent', - }, + relayCollectionId, TRANSFER_AMOUNT_RELAY, ], ]; @@ -1275,9 +1279,7 @@ describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => { }); itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => { - const currencyId = { - NativeAssetId: 'Here', - }; + const currencyId = 0; const dest = { V2: { parents: 1, diff --git a/node/cli/src/chain_spec.rs b/node/cli/src/chain_spec.rs index 6e5e9bf3f7..46baba03d4 100644 --- a/node/cli/src/chain_spec.rs +++ b/node/cli/src/chain_spec.rs @@ -167,7 +167,6 @@ macro_rules! testnet_genesis { .map(|k| (k, 1 << 100)) .collect(), }, - tokens: TokensConfig { balances: vec![] }, sudo: SudoConfig { key: Some($root_key), }, @@ -231,7 +230,6 @@ macro_rules! testnet_genesis { .map(|k| (k, 1 << 100)) .collect(), }, - tokens: TokensConfig { balances: vec![] }, sudo: SudoConfig { key: Some($root_key), }, diff --git a/pallets/balances-adapter/src/common.rs b/pallets/balances-adapter/src/common.rs index f6dbac1be3..6b594966f8 100644 --- 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 @@ impl CommonCollectionOperations for NativeFungibleHandle { 0 } - fn refungible_extensions(&self) -> Option<&dyn pallet_common::RefungibleExtensions> { - None + fn xcm_extensions(&self) -> Option<&dyn pallet_common::XcmExtensions> { + Some(self) } fn set_allowance_for_all( @@ -360,3 +366,72 @@ impl CommonCollectionOperations for NativeFungibleHandle { fail!(>::UnsupportedOperation); } } + +impl pallet_common::XcmExtensions for NativeFungibleHandle { + fn create_item_internal( + &self, + _depositor: &::CrossAccountId, + to: ::CrossAccountId, + data: up_data_structs::CreateItemData, + _nesting_budget: &dyn Budget, + ) -> Result { + 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!(>::NotFungibleDataUsedToMintFungibleCollectionToken) + } + } + } + + fn transfer_item_internal( + &self, + _depositor: &::CrossAccountId, + from: &::CrossAccountId, + to: &::CrossAccountId, + token: TokenId, + amount: u128, + _nesting_budget: &dyn Budget, + ) -> sp_runtime::DispatchResult { + ensure!( + token == TokenId::default(), + >::FungibleItemsHaveNoId + ); + + >::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(), + >::FungibleItemsHaveNoId + ); + + T::Mutate::burn_from( + from.as_sub(), + amount + .try_into() + .map_err(|_| sp_runtime::ArithmeticError::Overflow)?, + Precision::Exact, + Fortitude::Polite, + )?; + + Ok(()) + } +} diff --git a/pallets/common/src/benchmarking.rs b/pallets/common/src/benchmarking.rs index bb4bef2097..b09281f875 100644 --- a/pallets/common/src/benchmarking.rs +++ b/pallets/common/src/benchmarking.rs @@ -33,7 +33,7 @@ use up_data_structs::{ 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 @@ fn create_collection( create_collection_raw( owner, CollectionMode::NFT, - |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), + |owner: T::CrossAccountId, data| { + >::init_collection(owner.clone(), CollectionIssuer::User(owner), data) + }, |h| h, ) } diff --git a/pallets/common/src/dispatch.rs b/pallets/common/src/dispatch.rs index 52009d2be5..2a1bfa813c 100644 --- a/pallets/common/src/dispatch.rs +++ b/pallets/common/src/dispatch.rs @@ -11,7 +11,7 @@ use sp_runtime::DispatchError; 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 @@ pub trait CollectionDispatch { /// 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, data: CreateCollectionData, ) -> Result; diff --git a/pallets/common/src/lib.rs b/pallets/common/src/lib.rs index 50c4420332..bb55570935 100644 --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -80,15 +80,16 @@ use sp_runtime::{traits::Zero, ArithmeticError, DispatchError, DispatchResult}; 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 @@ pub mod pallet { /// 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 @@ impl<'a, T> LazyValue<'a, T> { } } +/// An issuer of a collection. +pub enum CollectionIssuer { + /// A user who creates the collection. + User(CrossAccountId), + + /// The internal mechanisms are creating the collection. + Internals, +} + fn check_token_permissions( collection_admin_permitted: bool, token_owner_permitted: bool, @@ -1124,33 +1137,36 @@ impl Pallet { /// 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, data: CreateCollectionData, ) -> Result { - ensure!(data.flags.is_allowed_for_user(), >::NoPermission); - Self::init_collection_internal(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, - mut data: CreateCollectionData, - ) -> Result { - data.flags.foreign = true; - let id = Self::init_collection_internal(owner, payer, data)?; - Ok(id) - } + match issuer { + CollectionIssuer::User(payer) => { + ensure!(data.flags.is_allowed_for_user(), >::NoPermission); + + // Take a (non-refundable) deposit of collection creation + let mut imbalance = ::Currency>>::zero(); + imbalance.subsume(::Currency::deposit( + &T::TreasuryAccountId::get(), + T::CollectionCreationPrice::get(), + Precision::Exact, + )?); + let credit = ::Currency::settle( + payer.as_sub(), + imbalance, + Preservation::Preserve, + ) + .map_err(|_| Error::::NotSufficientFounds)?; + + debug_assert!(credit.peek().is_zero()); + } + CollectionIssuer::Internals => {} + } - fn init_collection_internal( - owner: T::CrossAccountId, - payer: T::CrossAccountId, - data: CreateCollectionData, - ) -> Result { { ensure!( data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize, @@ -1225,21 +1241,6 @@ impl Pallet { ); >::insert(id, admin_amount); - // Take a (non-refundable) deposit of collection creation - { - let mut imbalance = ::Currency>>::zero(); - imbalance.subsume(::Currency::deposit( - &T::TreasuryAccountId::get(), - T::CollectionCreationPrice::get(), - Precision::Exact, - )?); - let credit = - ::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve) - .map_err(|_| Error::::NotSufficientFounds)?; - - debug_assert!(credit.peek().is_zero()) - } - >::put(created_count); >::deposit_event(Event::CollectionCreated( id, @@ -2293,7 +2294,14 @@ pub trait CommonCollectionOperations { ) -> u128; /// Get extension for RFT collection. - fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions>; + fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions> { + None + } + + /// Get XCM extensions. + fn xcm_extensions(&self) -> Option<&dyn XcmExtensions> { + None + } /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf. /// * `owner` - Token owner @@ -2333,6 +2341,117 @@ where ) -> DispatchResultWithPostInfo; } +/// XCM extensions for fungible and NFT collections +pub trait XcmExtensions +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 { + 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; + + /// 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. diff --git a/pallets/foreign-assets/Cargo.toml b/pallets/foreign-assets/Cargo.toml index a1074908d1..7d51138071 100644 --- a/pallets/foreign-assets/Cargo.toml +++ b/pallets/foreign-assets/Cargo.toml @@ -12,7 +12,6 @@ frame-benchmarking = { workspace = true, optional = true } 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 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "orml-tokens/std", "pallet-balances/std", "pallet-common/std", "pallet-fungible/std", diff --git a/pallets/foreign-assets/src/benchmarking.rs b/pallets/foreign-assets/src/benchmarking.rs index f8bbbb4519..ba5632c22b 100644 --- a/pallets/foreign-assets/src/benchmarking.rs +++ b/pallets/foreign-assets/src/benchmarking.rs @@ -16,89 +16,37 @@ #![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; - -fn bounded>>(slice: &[u8]) -> T { - T::try_from(slice.to_vec()) - .map_err(|_| "slice doesn't fit") - .unwrap() -} +use super::{Call, Config, ForeignCollectionMode, Pallet}; #[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< - <::Currency as Currency<::AccountId>>::Balance, - > = AssetMetadata { - name: bounded(b"name"), - symbol: bounded(b"symbol"), - decimals: 18, - minimal_balance: 1u32.into(), - }; - let mut balance: <::Currency as Currency< - ::AccountId, - >>::Balance = 4_000_000_000u32.into(); - balance = balance * balance; - ::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::(); + let token_prefix = create_data::(); + 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< - <::Currency as Currency<::AccountId>>::Balance, - > = AssetMetadata { - name: bounded(b"name"), - symbol: bounded(b"symbol"), - decimals: 18, - minimal_balance: 1u32.into(), - }; - let metadata2: AssetMetadata< - <::Currency as Currency<::AccountId>>::Balance, - > = AssetMetadata { - name: bounded(b"name2"), - symbol: bounded(b"symbol2"), - decimals: 18, - minimal_balance: 1u32.into(), - }; - let mut balance: <::Currency as Currency< - ::AccountId, - >>::Balance = 4_000_000_000u32.into(); - balance = balance * balance; - ::Currency::make_free_balance_be(&owner, balance); - Pallet::::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(()) - } } diff --git a/pallets/foreign-assets/src/impl_fungibles.rs b/pallets/foreign-assets/src/impl_fungibles.rs deleted file mode 100644 index 23c167fe7e..0000000000 --- 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 . - -//! 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 fungibles::Inspect<::AccountId> for Pallet -where - T: orml_tokens::Config, - BalanceOf: From<::Balance>, - BalanceOf: From<::Balance>, - ::Balance: From>, - ::Balance: From>, -{ - type AssetId = AssetId; - type Balance = BalanceOf; - - 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) => { - as fungible::Inspect>::total_issuance() - .into() - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Inspect>::total_issuance( - AssetId::NativeAssetId(NativeCurrency::Parent), - ) - .into() - } - AssetId::ForeignAssetId(fid) => { - let target_collection_id = match >::get(fid) { - Some(v) => v, - None => return Zero::zero(), - }; - let collection_handle = match >::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) => { - as fungible::Inspect>::minimum_balance() - .into() - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Inspect>::minimum_balance( - AssetId::NativeAssetId(NativeCurrency::Parent), - ) - .into() - } - AssetId::ForeignAssetId(fid) => AssetMetadatas::::get(AssetId::ForeignAssetId(fid)) - .map(|x| x.minimal_balance) - .unwrap_or_else(Zero::zero), - } - } - - fn balance(asset: Self::AssetId, who: &::AccountId) -> Self::Balance { - log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible balance"); - match asset { - AssetId::NativeAssetId(NativeCurrency::Here) => { - as fungible::Inspect>::balance(who).into() - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Inspect>::balance( - AssetId::NativeAssetId(NativeCurrency::Parent), - who, - ) - .into() - } - AssetId::ForeignAssetId(fid) => { - let target_collection_id = match >::get(fid) { - Some(v) => v, - None => return Zero::zero(), - }; - let collection_handle = match >::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: &::AccountId) -> Self::Balance { - Self::balance(asset, who) - } - - fn reducible_balance( - asset: Self::AssetId, - who: &::AccountId, - preservation: Preservation, - fortitude: Fortitude, - ) -> Self::Balance { - log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible reducible_balance"); - - match asset { - AssetId::NativeAssetId(NativeCurrency::Here) => { - as fungible::Inspect>::reducible_balance( - who, - preservation, - fortitude, - ) - .into() - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Inspect>::reducible_balance( - AssetId::NativeAssetId(NativeCurrency::Parent), - who, - preservation, - fortitude, - ) - .into() - } - _ => Self::balance(asset, who), - } - } - - fn can_deposit( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - provenance: Provenance, - ) -> DepositConsequence { - log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible can_deposit"); - - match asset { - AssetId::NativeAssetId(NativeCurrency::Here) => { - as fungible::Inspect>::can_deposit( - who, - amount.into(), - provenance, - ) - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Inspect>::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: &::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - 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: ::Balance = match value.try_into() { - Ok(val) => val, - Err(_) => { - return WithdrawConsequence::UnknownAsset; - } - }; - match as fungible::Inspect>::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: ::Balance = match value.try_into() { - Ok(val) => val, - Err(_) => { - return WithdrawConsequence::UnknownAsset; - } - }; - match as fungibles::Inspect>::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) => >::contains_key(fid), - } - } -} - -impl fungibles::Mutate<::AccountId> for Pallet -where - T: orml_tokens::Config, - BalanceOf: From<::Balance>, - BalanceOf: From<::Balance>, - ::Balance: From>, - ::Balance: From>, - u128: From>, -{ - fn mint_into( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> Result, 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) => { - as fungible::Mutate>::mint_into( - who, - amount.into(), - ) - .map(Into::into) - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Mutate>::mint_into( - AssetId::NativeAssetId(NativeCurrency::Parent), - who, - amount.into(), - ) - .map(Into::into) - } - AssetId::ForeignAssetId(fid) => { - let target_collection_id = match >::get(fid) { - Some(v) => v, - None => { - return Err(DispatchError::Other( - "Associated collection not found for asset", - )) - } - }; - let collection = - FungibleHandle::cast(>::try_get(target_collection_id)?); - let account = T::CrossAccountId::from_sub(who.clone()); - - let amount_data: pallet_fungible::CreateItemData = - (account.clone(), amount.into()); - - pallet_fungible::Pallet::::create_item_foreign( - &collection, - &account, - amount_data, - &budget::Value::new(0), - )?; - - Ok(amount) - } - } - } - - fn burn_from( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - precision: Precision, - fortitude: Fortitude, - ) -> Result { - // 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) => { - as fungible::Mutate>::burn_from( - who, - amount.into(), - precision, - fortitude, - ) - .map(Into::into) - } - AssetId::NativeAssetId(NativeCurrency::Parent) => { - as fungibles::Mutate>::burn_from( - AssetId::NativeAssetId(NativeCurrency::Parent), - who, - amount.into(), - precision, - fortitude, - ) - .map(Into::into) - } - AssetId::ForeignAssetId(fid) => { - let target_collection_id = match >::get(fid) { - Some(v) => v, - None => { - return Err(DispatchError::Other( - "Associated collection not found for asset", - )) - } - }; - let collection = - FungibleHandle::cast(>::try_get(target_collection_id)?); - pallet_fungible::Pallet::::burn_foreign( - &collection, - &T::CrossAccountId::from_sub(who.clone()), - amount.into(), - )?; - - Ok(amount) - } - } - } - - fn transfer( - asset: Self::AssetId, - source: &::AccountId, - dest: &::AccountId, - amount: Self::Balance, - preservation: Preservation, - ) -> Result { - // 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 as fungible::Mutate>::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 as fungibles::Mutate>::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 >::get(fid) { - Some(v) => v, - None => { - return Err(DispatchError::Other( - "Associated collection not found for asset", - )) - } - }; - let collection = - FungibleHandle::cast(>::try_get(target_collection_id)?); - - pallet_fungible::Pallet::::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 fungibles::Unbalanced<::AccountId> for Pallet -where - T: orml_tokens::Config, - BalanceOf: From<::Balance>, - BalanceOf: From<::Balance>, - ::Balance: From>, - ::Balance: From>, - u128: From>, -{ - fn handle_dust(_dust: fungibles::Dust<::AccountId, Self>) { - ensure_balanced!(); - } - fn write_balance( - _asset: Self::AssetId, - _who: &::AccountId, - _amount: Self::Balance, - ) -> Result, DispatchError> { - ensure_balanced!(); - } - fn set_total_issuance(_asset: Self::AssetId, _amount: Self::Balance) { - ensure_balanced!(); - } -} diff --git a/pallets/foreign-assets/src/lib.rs b/pallets/foreign-assets/src/lib.rs index 5a24db092c..5eae9ef20b 100644 --- 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 . -//! # Foreign assets -//! -//! - [`Config`] -//! - [`Call`] -//! - [`Pallet`] -//! -//! ## 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. +//! # Foreign Assets //! //! ## 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::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader}, + Assets, +}; +use up_data_structs::{ + budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix, + CreateCollectionData, CreateFungibleData, CreateItemData, TokenId, }; -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 { - fn try_as_foreign(asset: T) -> Option; -} - -impl TryAsForeign for AssetId { - fn try_as_foreign(asset: AssetId) -> Option { - match asset { - Self::ForeignAssetId(id) => Some(id), - _ => None, - } - } -} - -pub type ForeignAssetId = u32; -pub type CurrencyId = AssetId; -mod impl_fungibles; pub mod weights; #[cfg(feature = "runtime-benchmarks")] @@ -129,43 +54,11 @@ mod benchmarking; pub use module::*; pub use weights::WeightInfo; -/// Type alias for currency balance. -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - -/// A mapping between ForeignAssetId and AssetMetadata. -pub trait AssetIdMapping { - /// Returns the AssetMetadata associated with a given ForeignAssetId. - fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option; - /// Returns the MultiLocation associated with a given ForeignAssetId. - fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option; - /// Returns the CurrencyId associated with a given MultiLocation. - fn get_currency_id(multi_location: MultiLocation) -> Option; -} - -pub struct XcmForeignAssetIdMapping(sp_std::marker::PhantomData); - -impl AssetIdMapping>> - for XcmForeignAssetIdMapping -{ - fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option>> { - log::trace!(target: "fassets::asset_metadatas", "call"); - Pallet::::asset_metadatas(AssetId::ForeignAssetId(foreign_asset_id)) - } - - fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option { - log::trace!(target: "fassets::get_multi_location", "call"); - Pallet::::foreign_asset_locations(foreign_asset_id) - } - - fn get_currency_id(multi_location: MultiLocation) -> Option { - log::trace!(target: "fassets::get_currency_id", "call"); - Pallet::::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 @@ pub mod module { frame_system::Config + pallet_common::Config + pallet_fungible::Config - + orml_tokens::Config + pallet_balances::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Currency type for withdraw and balance storage. - type Currency: Currency; + /// Origin for force registering of a foreign asset. + type ForceRegisterOrigin: EnsureOrigin; - /// Required origin for registering asset. - type RegisterOrigin: EnsureOrigin; + /// The ID of the foreign assets pallet. + type PalletId: Get; - /// Weight information for the extrinsics in this module. - type WeightInfo: WeightInfo; - } + /// Self-location of this parachain. + type SelfLocation: Get; - pub type AssetName = BoundedVec>; - pub type AssetSymbol = BoundedVec>; + /// The converter from a MultiLocation to a CrossAccountId. + type LocationToAccountId: ConvertLocation; - #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] - pub struct AssetMetadata { - pub name: AssetName, - pub symbol: AssetSymbol, - pub decimals: u8, - pub minimal_balance: Balance, + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; } #[pallet::error] pub enum Error { - /// 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 mod module { pub enum Event { /// The foreign asset registered. ForeignAssetRegistered { - asset_id: ForeignAssetId, - asset_address: MultiLocation, - metadata: AssetMetadata>, - }, - /// The foreign asset updated. - ForeignAssetUpdated { - asset_id: ForeignAssetId, - asset_address: MultiLocation, - metadata: AssetMetadata>, - }, - /// The asset registered. - AssetRegistered { - asset_id: AssetId, - metadata: AssetMetadata>, - }, - /// The asset updated. - AssetUpdated { - asset_id: AssetId, - metadata: AssetMetadata>, + collection_id: CollectionId, + asset_id: Box, }, } - /// 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 = StorageValue<_, ForeignAssetId, ValueQuery>; - /// The storages for MultiLocations. - /// - /// ForeignAssetLocations: map ForeignAssetId => Option - #[pallet::storage] - #[pallet::getter(fn foreign_asset_locations)] - pub type ForeignAssetLocations = - StorageMap<_, Twox64Concat, ForeignAssetId, staging_xcm::v3::MultiLocation, OptionQuery>; + #[pallet::getter(fn foreign_asset_to_collection)] + pub type ForeignAssetToCollection = + StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>; - /// The storages for CurrencyIds. - /// - /// LocationToCurrencyIds: map MultiLocation => Option + /// The corresponding foreign assets of collections. #[pallet::storage] - #[pallet::getter(fn location_to_currency_ids)] - pub type LocationToCurrencyIds = - StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, ForeignAssetId, OptionQuery>; + #[pallet::getter(fn collection_to_foreign_asset)] + pub type CollectionToForeignAsset = + StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>; - /// The storages for AssetMetadatas. - /// - /// AssetMetadatas: map AssetIds => Option + /// The correponding NFT token id of reserve NFTs #[pallet::storage] - #[pallet::getter(fn asset_metadatas)] - pub type AssetMetadatas = - StorageMap<_, Twox64Concat, AssetId, AssetMetadata>, OptionQuery>; - - /// The storages for assets to fungible collection binding - /// + #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)] + pub type ForeignReserveAssetInstanceToTokenId = StorageDoubleMap< + Hasher1 = Twox64Concat, + Key1 = CollectionId, + Hasher2 = Blake2_128Concat, + Key2 = staging_xcm::v3::AssetInstance, + Value = TokenId, + QueryKind = OptionQuery, + >; + + /// The correponding reserve NFT of a token ID #[pallet::storage] - #[pallet::getter(fn asset_binding)] - pub type AssetBinding = - StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>; + #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)] + pub type TokenIdToForeignReserveAssetInstance = StorageDoubleMap< + Hasher1 = Twox64Concat, + Key1 = CollectionId, + Hasher2 = Blake2_128Concat, + Key2 = TokenId, + Value = staging_xcm::v3::AssetInstance, + QueryKind = OptionQuery, + >; #[pallet::pallet] pub struct Pallet(_); @@ -284,196 +149,432 @@ pub mod module { impl Pallet { #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::register_foreign_asset())] - pub fn register_foreign_asset( + pub fn force_register_foreign_asset( origin: OriginFor, - owner: T::AccountId, - location: Box, - metadata: Box>>, + versioned_asset_id: Box, + 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::::BadLocation)?; - - let md = metadata.clone(); - let name: Vec = md.name.into_iter().map(|x| x as u16).collect::>(); - let mut description: Vec = "Foreign assets collection for " - .encode_utf16() - .collect::>(); - description.append(&mut name.clone()); - - let data: CreateCollectionData = 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 = - >::init_foreign_collection(owner.clone(), owner, data)?; - let foreign_asset_id = - Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?; + .map_err(|()| Error::::BadForeignAssetId)?; - Self::deposit_event(Event::::ForeignAssetRegistered { - asset_id: foreign_asset_id, - asset_address: location, - metadata: *metadata, - }); - Ok(()) - } + ensure!( + !>::contains_key(asset_id), + >::ForeignAssetAlreadyRegistered, + ); - #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::update_foreign_asset())] - pub fn update_foreign_asset( - origin: OriginFor, - foreign_asset_id: ForeignAssetId, - location: Box, - metadata: Box>>, - ) -> DispatchResult { - T::RegisterOrigin::ensure_origin(origin)?; + let foreign_collection_owner = Self::pallet_account(); - let location: MultiLocation = (*location) + let description: CollectionDescription = "Foreign Assets Collection" + .encode_utf16() + .collect::>() .try_into() - .map_err(|()| Error::::BadLocation)?; - Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?; + .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() + }, + )?; + + >::insert(asset_id, collection_id); + >::insert(collection_id, asset_id); - Self::deposit_event(Event::::ForeignAssetUpdated { - asset_id: foreign_asset_id, - asset_address: location, - metadata: *metadata, + Self::deposit_event(Event::::ForeignAssetRegistered { + collection_id, + asset_id: versioned_asset_id, }); + Ok(()) } } } impl Pallet { - fn get_next_foreign_asset_id() -> Result { - NextForeignAssetId::::try_mutate(|current| -> Result { - 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>, - bounded_collection_id: CollectionId, - ) -> Result { - let foreign_asset_id = Self::get_next_foreign_asset_id()?; - LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { - ensure!( - maybe_currency_ids.is_none(), - Error::::MultiLocationExisted - ); - *maybe_currency_ids = Some(foreign_asset_id); - // *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); - - ForeignAssetLocations::::try_mutate( - foreign_asset_id, - |maybe_location| -> DispatchResult { - ensure!(maybe_location.is_none(), Error::::MultiLocationExisted); - *maybe_location = Some(*location); - - AssetMetadatas::::try_mutate( - AssetId::ForeignAssetId(foreign_asset_id), - |maybe_asset_metadatas| -> DispatchResult { - ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); - *maybe_asset_metadatas = Some(metadata.clone()); - Ok(()) - }, - ) - }, - )?; + /// 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()` that also corresponds to the native token of this parachain. + /// * It is `../Parachain()/GeneralIndex()` that corresponds + /// to the collection with the ID equal to ``. The `` must be in the valid range, + /// otherwise `None` is returned. + /// * It is `GeneralIndex()`. Same as the last one above. + /// + /// If the multilocation doesn't match the patterns listed above, + /// or the `` 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 { + let AssetId::Concrete(asset_location) = asset_id else { + return None; + }; + + let self_location = T::SelfLocation::get(); + + 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::::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult { - *collection_id = Some(bounded_collection_id); - Ok(()) - }) - })?; + let collection_id = CollectionId((*collection_id).try_into().ok()?); + + 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 { + 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()) + } + + /// 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()`. + /// + /// If the asset instance is not in the valid format or the `` 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 { + match asset_instance { + AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)), + _ => None, + } + } - Ok(foreign_asset_id) + /// 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 { + 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) + } + } } - fn do_update_foreign_asset( - foreign_asset_id: ForeignAssetId, - location: &MultiLocation, - metadata: &AssetMetadata>, + /// Creates a foreign item in the the collection. + fn create_foreign_asset_instance( + xcm_ext: &dyn XcmExtensions, + collection_id: CollectionId, + asset_instance: &AssetInstance, + to: T::CrossAccountId, ) -> DispatchResult { - ForeignAssetLocations::::try_mutate( - foreign_asset_id, - |maybe_multi_locations| -> DispatchResult { - let old_multi_locations = maybe_multi_locations - .as_mut() - .ok_or(Error::::AssetIdNotExists)?; - - AssetMetadatas::::try_mutate( - AssetId::ForeignAssetId(foreign_asset_id), - |maybe_asset_metadatas| -> DispatchResult { - ensure!( - maybe_asset_metadatas.is_some(), - Error::::AssetIdNotExists - ); - - // modify location - if location != old_multi_locations { - LocationToCurrencyIds::::remove(*old_multi_locations); - LocationToCurrencyIds::::try_mutate( - location, - |maybe_currency_ids| -> DispatchResult { - ensure!( - maybe_currency_ids.is_none(), - Error::::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(()) - }, + let derivative_token_id = xcm_ext.create_item( + &Self::pallet_account(), + to, + CreateItemData::NFT(Default::default()), + &ZeroBudget, + )?; + + >::insert( + collection_id, + asset_instance, + derivative_token_id, + ); + + >::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, + 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")) + } + + /// 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, + 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 TransactAsset for Pallet { + 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)?; + + 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 { + 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 { + 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, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, ->( - Weight, - Currency::Balance, - PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, -); - -impl< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > WeightTrader for FreeForAll + fn deref(&self) -> &Self::Target { + match self { + Self::Local(id) => id, + Self::Foreign(id) => id, + } + } +} + +pub struct CurrencyIdConvert(PhantomData); +impl sp_runtime::traits::Convert> + for CurrencyIdConvert { + fn convert(collection_id: CollectionId) -> Option { + if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID { + Some(T::SelfLocation::get()) + } else { + >::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 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( @@ -486,15 +587,3 @@ impl< Ok(payment) } } -impl Drop - for FreeForAll -where - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, -{ - fn drop(&mut self) { - OnUnbalanced::on_unbalanced(Currency::issue(self.1)); - } -} diff --git a/pallets/fungible/src/benchmarking.rs b/pallets/fungible/src/benchmarking.rs index a9c3a5cdaa..9fa399ab07 100644 --- a/pallets/fungible/src/benchmarking.rs +++ b/pallets/fungible/src/benchmarking.rs @@ -15,7 +15,9 @@ // along with Unique Network. If not, see . 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 @@ fn create_collection( create_collection_raw( owner, CollectionMode::Fungible(0), - |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), + |owner: T::CrossAccountId, data| { + >::init_collection(owner.clone(), CollectionIssuer::User(owner), data) + }, FungibleHandle::cast, ) } diff --git a/pallets/fungible/src/common.rs b/pallets/fungible/src/common.rs index dd8a76f35e..88bb8b79ea 100644 --- a/pallets/fungible/src/common.rs +++ b/pallets/fungible/src/common.rs @@ -19,7 +19,7 @@ use core::marker::PhantomData; 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 @@ impl CommonCollectionOperations for FungibleHandle { >::create_item(self, &sender, (to, fungible_data.value), nesting_budget), >::create_item(&data), ), - _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), } } @@ -133,7 +133,7 @@ impl CommonCollectionOperations for FungibleHandle { .checked_add(data.value) .ok_or(ArithmeticError::Overflow)?; } - _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), } } @@ -152,7 +152,7 @@ impl CommonCollectionOperations for FungibleHandle { let weight = >::create_multiple_items_ex(&data); let data = match data { up_data_structs::CreateItemExData::Fungible(f) => f, - _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), }; with_weight( @@ -428,10 +428,6 @@ impl CommonCollectionOperations for FungibleHandle { >::get((self.id, sender, spender)) } - fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions> { - None - } - fn total_pieces(&self, token: TokenId) -> Option { if token != TokenId::default() { return None; @@ -439,6 +435,10 @@ impl CommonCollectionOperations for FungibleHandle { >::try_get(self.id).ok() } + fn xcm_extensions(&self) -> Option<&dyn XcmExtensions> { + Some(self) + } + fn set_allowance_for_all( &self, _owner: T::CrossAccountId, @@ -457,3 +457,57 @@ impl CommonCollectionOperations for FungibleHandle { fail!(>::FungibleTokensAreAlwaysValid) } } + +impl XcmExtensions for FungibleHandle { + fn create_item_internal( + &self, + depositor: &::CrossAccountId, + to: ::CrossAccountId, + data: CreateItemData, + nesting_budget: &dyn Budget, + ) -> Result { + match &data { + up_data_structs::CreateItemData::Fungible(fungible_data) => { + >::create_multiple_items( + self, + depositor, + [(to, fungible_data.value)].into_iter().collect(), + nesting_budget, + )? + } + _ => fail!(>::NotFungibleDataUsedToMintFungibleCollectionToken), + } + + Ok(TokenId::default()) + } + + fn transfer_item_internal( + &self, + depositor: &::CrossAccountId, + from: &::CrossAccountId, + to: &::CrossAccountId, + token: TokenId, + amount: u128, + nesting_budget: &dyn Budget, + ) -> sp_runtime::DispatchResult { + ensure!( + token == TokenId::default(), + >::FungibleItemsHaveNoId + ); + + >::transfer_internal(self, depositor, from, to, amount, nesting_budget) + .map(|_| ()) + .map_err(|post_info| post_info.error) + } + + fn burn_item_internal( + &self, + from: ::CrossAccountId, + token: TokenId, + amount: u128, + ) -> sp_runtime::DispatchResult { + >::burn_item(self, from, token, amount) + .map(|_| ()) + .map_err(|post_info| post_info.error) + } +} diff --git a/pallets/fungible/src/lib.rs b/pallets/fungible/src/lib.rs index 1aadc416fc..00d427d736 100644 --- a/pallets/fungible/src/lib.rs +++ b/pallets/fungible/src/lib.rs @@ -95,8 +95,8 @@ use sp_core::H160; 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 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// 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 @@ impl Deref for FungibleHandle { /// Pallet implementation for fungible assets impl Pallet { - /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise. - pub fn init_collection( - owner: T::CrossAccountId, - payer: T::CrossAccountId, - data: CreateCollectionData, - ) -> Result { - >::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, - ) -> Result { - >::init_foreign_collection(owner, payer, data) - } - /// Destroys a collection. pub fn destroy_collection( collection: FungibleHandle, @@ -294,9 +274,6 @@ impl Pallet { .checked_sub(amount) .ok_or(>::TokenValueTooLow)?; - // Foreign collection check - ensure!(!collection.flags.foreign, >::NoPermission); - if collection.permissions.access() == AccessMode::AllowList { collection.check_allowlist(owner)?; } @@ -328,46 +305,6 @@ impl Pallet { Ok(()) } - /// Burns the specified amount of the token. - pub fn burn_foreign( - collection: &FungibleHandle, - owner: &T::CrossAccountId, - amount: u128, - ) -> DispatchResult { - let total_supply = >::get(collection.id) - .checked_sub(amount) - .ok_or(>::TokenValueTooLow)?; - - let balance = >::get((collection.id, owner)) - .checked_sub(amount) - .ok_or(>::TokenValueTooLow)?; - // ========= - - if balance == 0 { - >::remove((collection.id, owner)); - >::unnest_if_nested(owner, collection.id, TokenId::default()); - } else { - >::insert((collection.id, owner), balance); - } - >::insert(collection.id, total_supply); - - >::deposit_log( - ERC20Events::Transfer { - from: *owner.as_eth(), - to: H160::default(), - value: amount.into(), - } - .to_log(collection_id_to_address(collection.id)), - ); - >::deposit_event(CommonEvent::ItemDestroyed( - collection.id, - TokenId::default(), - owner.clone(), - amount, - )); - Ok(()) - } - /// Transfers the specified amount of tokens. Will check that /// the transfer is allowed for the token. /// @@ -468,14 +405,25 @@ impl Pallet { } /// 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, - sender: &T::CrossAccountId, + depositor: &T::CrossAccountId, data: BTreeMap, nesting_budget: &dyn Budget, ) -> DispatchResult { + if !collection.is_owner_or_admin(depositor) { + ensure!( + collection.permissions.mint_mode(), + >::PublicMintingNotAllowed + ); + collection.check_allowlist(depositor)?; + + for (owner, _) in data.iter() { + collection.check_allowlist(owner)?; + } + } + let total_supply = data .values() .copied() @@ -486,7 +434,7 @@ impl Pallet { for (to, _) in data.iter() { >::check_nesting( - sender, + depositor, to, collection.id, TokenId::default(), @@ -533,43 +481,6 @@ impl Pallet { Ok(()) } - /// Minting tokens for multiple IDs. - /// See [`create_item`][`Pallet::create_item`] for more details. - pub fn create_multiple_items( - collection: &FungibleHandle, - sender: &T::CrossAccountId, - data: BTreeMap, - nesting_budget: &dyn Budget, - ) -> DispatchResult { - // Foreign collection check - ensure!(!collection.flags.foreign, >::NoPermission); - - if !collection.is_owner_or_admin(sender) { - ensure!( - collection.permissions.mint_mode(), - >::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, - sender: &T::CrossAccountId, - data: BTreeMap, - nesting_budget: &dyn Budget, - ) -> DispatchResult { - Self::create_multiple_items_common(collection, sender, data, nesting_budget) - } - fn set_allowance_unchecked( collection: &FungibleHandle, owner: &T::CrossAccountId, @@ -802,24 +713,6 @@ impl Pallet { ) } - /// 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, - sender: &T::CrossAccountId, - data: CreateItemData, - nesting_budget: &dyn Budget, - ) -> DispatchResult { - Self::create_multiple_items_foreign( - collection, - sender, - [(data.0, data.1)].into_iter().collect(), - nesting_budget, - ) - } - /// Returns 10 tokens owners in no particular order /// /// There is no direct way to get token holders in ascending order, diff --git a/pallets/nonfungible/src/benchmarking.rs b/pallets/nonfungible/src/benchmarking.rs index 93d206f679..1a71de761d 100644 --- a/pallets/nonfungible/src/benchmarking.rs +++ b/pallets/nonfungible/src/benchmarking.rs @@ -18,6 +18,7 @@ use frame_benchmarking::v2::{account, benchmarks, BenchmarkError}; 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 @@ fn create_collection( create_collection_raw( owner, CollectionMode::NFT, - |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), + |owner: T::CrossAccountId, data| { + >::init_collection(owner.clone(), CollectionIssuer::User(owner), data) + }, NonfungibleHandle::cast, ) } diff --git a/pallets/nonfungible/src/common.rs b/pallets/nonfungible/src/common.rs index a6eb621cd3..9a05ba82d3 100644 --- a/pallets/nonfungible/src/common.rs +++ b/pallets/nonfungible/src/common.rs @@ -19,8 +19,8 @@ use core::marker::PhantomData; 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; @@ -535,10 +535,6 @@ impl CommonCollectionOperations for NonfungibleHandle { } } - fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions> { - None - } - fn total_pieces(&self, token: TokenId) -> Option { if >::contains_key((self.id, token)) { Some(1) @@ -547,6 +543,10 @@ impl CommonCollectionOperations for NonfungibleHandle { } } + fn xcm_extensions(&self) -> Option<&dyn XcmExtensions> { + Some(self) + } + fn set_allowance_for_all( &self, owner: T::CrossAccountId, @@ -570,3 +570,53 @@ impl CommonCollectionOperations for NonfungibleHandle { ) } } + +impl XcmExtensions for NonfungibleHandle { + fn token_has_children(&self, token: TokenId) -> bool { + >::token_has_children(self.id, token) + } + + fn create_item_internal( + &self, + depositor: &::CrossAccountId, + to: ::CrossAccountId, + data: up_data_structs::CreateItemData, + nesting_budget: &dyn Budget, + ) -> Result { + >::create_multiple_items( + self, + depositor, + vec![map_create_data::(data, &to)?], + nesting_budget, + )?; + + Ok(self.last_token_id()) + } + + fn transfer_item_internal( + &self, + depositor: &::CrossAccountId, + from: &::CrossAccountId, + to: &::CrossAccountId, + token: TokenId, + amount: u128, + nesting_budget: &dyn Budget, + ) -> sp_runtime::DispatchResult { + ensure!(amount == 1, >::NonfungibleItemsHaveNoAmount); + + >::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, >::NonfungibleItemsHaveNoAmount); + + >::burn(self, &from, token) + } +} diff --git a/pallets/nonfungible/src/lib.rs b/pallets/nonfungible/src/lib.rs index a5464e4696..0ea6459446 100644 --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -117,8 +117,8 @@ use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutc 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 @@ impl Pallet { // unchecked calls skips any permission checks impl Pallet { - /// 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, - ) -> Result { - >::init_collection(owner, payer, data) - } - /// Destroy NFT collection /// /// `destroy_collection` will throw error if collection contains any tokens. diff --git a/pallets/refungible/src/benchmarking.rs b/pallets/refungible/src/benchmarking.rs index 1daaf86250..38950c796b 100644 --- a/pallets/refungible/src/benchmarking.rs +++ b/pallets/refungible/src/benchmarking.rs @@ -20,6 +20,7 @@ use frame_benchmarking::v2::*; 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 @@ fn create_collection( create_collection_raw( owner, CollectionMode::ReFungible, - |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), + |owner: T::CrossAccountId, data| { + >::init_collection(owner.clone(), CollectionIssuer::User(owner), data) + }, RefungibleHandle::cast, ) } diff --git a/pallets/refungible/src/lib.rs b/pallets/refungible/src/lib.rs index 5ce0837479..47966766a6 100644 --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -103,7 +103,7 @@ use sp_core::{Get, H160}; 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 @@ impl Pallet { // unchecked calls skips any permission checks impl Pallet { - /// 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, - ) -> Result { - >::init_collection(owner, payer, data) - } - /// Destroy RFT collection /// /// `destroy_collection` will throw error if collection contains any tokens. diff --git a/pallets/structure/src/benchmarking.rs b/pallets/structure/src/benchmarking.rs index 364ceaa5e5..ea1f446caf 100644 --- 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 @@ mod benchmarks { .unwrap(); T::CollectionDispatch::create( caller_cross.clone(), - caller_cross.clone(), + CollectionIssuer::User(caller_cross.clone()), CreateCollectionData { mode: CollectionMode::NFT, ..Default::default() diff --git a/pallets/unique/src/eth/mod.rs b/pallets/unique/src/eth/mod.rs index 3a41e667b1..ed82c174a6 100644 --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -26,7 +26,7 @@ use pallet_common::{ 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 @@ fn create_collection_internal( let collection_helpers_address = T::CrossAccountId::from_eth(::ContractAddress::get()); - let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + let collection_id = T::CollectionDispatch::create( + caller, + CollectionIssuer::User(collection_helpers_address), + data, + ) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; let address = pallet_common::eth::collection_id_to_address(collection_id); Ok(address) } @@ -240,8 +244,12 @@ where let collection_helpers_address = T::CrossAccountId::from_eth(::ContractAddress::get()); - let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data) - .map_err(dispatch_to_evm::)?; + let collection_id = T::CollectionDispatch::create( + caller, + CollectionIssuer::User(collection_helpers_address), + data, + ) + .map_err(dispatch_to_evm::)?; let address = pallet_common::eth::collection_id_to_address(collection_id); Ok(address) @@ -274,8 +282,12 @@ where check_sent_amount_equals_collection_creation_price::(value)?; let collection_helpers_address = T::CrossAccountId::from_eth(::ContractAddress::get()); - let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data) - .map_err(dispatch_to_evm::)?; + let collection_id = T::CollectionDispatch::create( + caller, + CollectionIssuer::User(collection_helpers_address), + data, + ) + .map_err(dispatch_to_evm::)?; let address = pallet_common::eth::collection_id_to_address(collection_id); Ok(address) diff --git a/pallets/unique/src/eth/stubs/CollectionHelpers.sol b/pallets/unique/src/eth/stubs/CollectionHelpers.sol index c2572292c3..2b05584cbb 100644 --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -199,7 +199,7 @@ struct CreateCollectionData { 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); diff --git a/pallets/unique/src/lib.rs b/pallets/unique/src/lib.rs index d88fc872f4..64fc550ebe 100644 --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -93,7 +93,8 @@ pub mod pallet { 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 @@ pub mod pallet { // ========= 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(()) } diff --git a/primitives/data-structs/src/budget.rs b/primitives/data-structs/src/budget.rs index 80c4ce3565..7b2f5dcac4 100644 --- a/primitives/data-structs/src/budget.rs +++ b/primitives/data-structs/src/budget.rs @@ -36,3 +36,10 @@ impl Budget for Value { true } } + +pub struct ZeroBudget; +impl Budget for ZeroBudget { + fn consume_custom(&self, _calls: u32) -> bool { + false + } +} diff --git a/primitives/data-structs/src/lib.rs b/primitives/data-structs/src/lib.rs index ae1c0924ad..8853104d76 100644 --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -378,7 +378,7 @@ pub type CollectionTokenPrefix = BoundedVec. -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! { }; } -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 { - vec![TreasuryAccountId::get()] -} - -pub struct DustRemovalWhitelist; -impl Contains for DustRemovalWhitelist { - fn contains(a: &AccountId) -> bool { - get_all_module_accounts().contains(a) - } -} - pub struct AccountIdToMultiLocation; impl Convert for AccountIdToMultiLocation { fn convert(account: AccountId) -> MultiLocation { @@ -93,18 +60,6 @@ impl Convert for AccountIdToMultiLocation { } } -pub struct CurrencyHooks; -impl orml_traits::currency::MutationHooks for CurrencyHooks { - type OnDust = orml_tokens::TransferDust; - type OnSlash = (); - type PreTransfer = (); - type PostTransfer = (); - type PreDeposit = (); - type PostDeposit = (); - type OnNewTokenAccount = (); - type OnKilledTokenAccount = (); -} - impl orml_vesting::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = pallet_balances::Pallet; @@ -115,27 +70,11 @@ impl orml_vesting::Config for Runtime { type BlockNumberProvider = RelayChainBlockNumberProvider; } -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; type AccountIdToMultiLocation = AccountIdToMultiLocation; type SelfLocation = SelfLocation; type XcmExecutor = XcmExecutor>; diff --git a/runtime/common/config/pallets/foreign_asset.rs b/runtime/common/config/pallets/foreign_asset.rs index af9d78b402..60924bdea2 100644 --- 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; -use crate::{Balances, Runtime, RuntimeEvent}; +#[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 + for LocationToCrossAccountId +{ + fn convert_location(location: &MultiLocation) -> Option { + LocationToAccountId::convert_location(location) + .map(ConfigCrossAccountId::from_sub) + .or_else(|| { + let eth_address = + AccountKey20Aliases::::convert_location(location)?; + + Some(ConfigCrossAccountId::from_eth(eth_address)) + }) + } +} impl pallet_foreign_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type RegisterOrigin = frame_system::EnsureRoot; + + #[cfg(feature = "governance")] + type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember; + + #[cfg(not(feature = "governance"))] + type ForceRegisterOrigin = EnsureRoot; + + type PalletId = ForeignAssetPalletId; + type SelfLocation = SelfLocation; + type LocationToAccountId = LocationToCrossAccountId; type WeightInfo = pallet_foreign_assets::weights::SubstrateWeight; } diff --git a/runtime/common/config/xcm/mod.rs b/runtime/common/config/xcm.rs similarity index 94% rename from runtime/common/config/xcm/mod.rs rename to runtime/common/config/xcm.rs index 475b1daadc..506598e7c5 100644 --- a/runtime/common/config/xcm/mod.rs +++ b/runtime/common/config/xcm.rs @@ -20,6 +20,9 @@ use frame_support::{ 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; @@ -39,25 +42,13 @@ use staging_xcm_executor::{ }; 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; +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(); @@ -164,6 +155,10 @@ where pub type Weigher = FixedWeightBounds; +pub type IsReserve = MultiNativeAsset; + +pub type Trader = FreeForAll; + pub struct XcmExecutorConfig(PhantomData); impl staging_xcm_executor::Config for XcmExecutorConfig where @@ -172,14 +167,14 @@ where type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; // How to withdraw and deposit an asset. - type AssetTransactor = AssetTransactor; + 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 Trader = Trader; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; diff --git a/runtime/common/config/xcm/foreignassets.rs b/runtime/common/config/xcm/foreignassets.rs deleted file mode 100644 index 13f8441738..0000000000 --- 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 . - -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(PhantomData<(AssetId, ConvertAssetId)>); -impl> MaybeEquivalence - for AsInnerId -{ - fn convert(id: &MultiLocation) -> Option { - 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::::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 { - 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 = >::try_as_foreign(*asset_id)?; - XcmForeignAssetIdMapping::::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, 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 { - FungiblesTransactor::withdraw_asset(what, who, maybe_context) - } - - fn internal_transfer_asset( - what: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, - context: &XcmContext, - ) -> Result { - FungiblesTransactor::internal_transfer_asset(what, from, to, context) - } -} - -pub type IsReserve = MultiNativeAsset; - -pub type Trader = FreeForAll< - pallet_configuration::WeightToFee, - RelayLocation, - AccountId, - Balances, - (), ->; - -pub struct CurrencyIdConvert; -impl Convert> for CurrencyIdConvert { - fn convert(id: AssetId) -> Option { - 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::::get_multi_location(foreign_asset_id) - } - } - } -} - -impl Convert> for CurrencyIdConvert { - fn convert(location: MultiLocation) -> Option { - 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::::get_currency_id(location) { - return Some(currency_id); - } - - None - } -} diff --git a/runtime/common/config/xcm/nativeassets.rs b/runtime/common/config/xcm/nativeassets.rs deleted file mode 100644 index 71c4c7b7cb..0000000000 --- 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 . - -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> MatchesFungible for OnlySelfCurrency { - fn matches_fungible(a: &MultiAsset) -> Option { - 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, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, ->( - Weight, - Currency::Balance, - PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, -); -impl< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > WeightTrader - for UsingOnlySelfCurrencyComponents -{ - 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 { - Ok(payment) - } -} -impl< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > Drop - for UsingOnlySelfCurrencyComponents -{ - fn drop(&mut self) { - OnUnbalanced::on_unbalanced(Currency::issue(self.1)); - } -} - -pub type Trader = UsingOnlySelfCurrencyComponents< - pallet_configuration::WeightToFee, - RelayLocation, - AccountId, - Balances, - (), ->; - -pub struct CurrencyIdConvert; -impl Convert> for CurrencyIdConvert { - fn convert(id: AssetIds) -> Option { - match id { - AssetIds::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new( - 1, - X1(Parachain(ParachainInfo::get().into())), - )), - _ => None, - } - } -} diff --git a/runtime/common/construct_runtime.rs b/runtime/common/construct_runtime.rs index e036189771..19da6ab6df 100644 --- a/runtime/common/construct_runtime.rs +++ b/runtime/common/construct_runtime.rs @@ -47,7 +47,7 @@ macro_rules! construct_runtime { Vesting: orml_vesting = 37, XTokens: orml_xtokens = 38, - Tokens: orml_tokens = 39, + // [REMOVED] Tokens: orml_tokens = 39, // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, #[cfg(feature = "governance")] diff --git a/runtime/common/dispatch.rs b/runtime/common/dispatch.rs index 5f4cea73b1..77879f367d 100644 --- 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 @@ where fn create( sender: T::CrossAccountId, - payer: T::CrossAccountId, + issuer: CollectionIssuer, data: CreateCollectionData, ) -> Result { - let id = match data.mode { - CollectionMode::NFT => >::init_collection(sender, payer, data)?, + match data.mode { CollectionMode::Fungible(decimal_points) => { // check params ensure!( decimal_points <= MAX_DECIMAL_POINTS, pallet_unique::Error::::CollectionDecimalPointLimitExceeded ); - >::init_collection(sender, payer, data)? } - #[cfg(feature = "refungible")] - CollectionMode::ReFungible => >::init_collection(sender, payer, data)?, - #[cfg(not(feature = "refungible"))] CollectionMode::ReFungible => return unsupported!(T), + + _ => {} }; - Ok(id) + + >::init_collection(sender, issuer, data) } fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult { diff --git a/runtime/opal/Cargo.toml b/runtime/opal/Cargo.toml index 1092512b14..517c7f796f 100644 --- a/runtime/opal/Cargo.toml +++ b/runtime/opal/Cargo.toml @@ -151,7 +151,6 @@ std = [ 'up-rpc/std', 'up-sponsorship/std', - "orml-tokens/std", "orml-traits/std", "orml-vesting/std", "orml-xcm-support/std", @@ -179,7 +178,6 @@ try-runtime = [ '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-executive = { workspace = true } 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 } diff --git a/runtime/quartz/Cargo.toml b/runtime/quartz/Cargo.toml index d8b19eae5f..5ecec4362c 100644 --- a/runtime/quartz/Cargo.toml +++ b/runtime/quartz/Cargo.toml @@ -150,7 +150,6 @@ std = [ 'up-rpc/std', 'up-sponsorship/std', - "orml-tokens/std", "orml-traits/std", "orml-vesting/std", "orml-xcm-support/std", @@ -175,7 +174,6 @@ try-runtime = [ '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-executive = { workspace = true } 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 } diff --git a/runtime/tests/src/tests.rs b/runtime/tests/src/tests.rs index fe76886665..9a58acca11 100644 --- a/runtime/tests/src/tests.rs +++ b/runtime/tests/src/tests.rs @@ -2357,7 +2357,8 @@ fn create_max_collections() { #[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 @@ fn total_number_collections_bound_neg() { }; // 11-th collection in chain. Expects error + add_balance(user, CollectionCreationPrice::get() as u64 + 1); assert_noop!( Unique::create_collection_ex(origin1, data), CommonError::::TotalCollectionsLimitExceeded diff --git a/runtime/unique/Cargo.toml b/runtime/unique/Cargo.toml index 5af1444213..b23c2e0d16 100644 --- a/runtime/unique/Cargo.toml +++ b/runtime/unique/Cargo.toml @@ -147,7 +147,6 @@ std = [ 'up-rpc/std', 'up-sponsorship/std', - "orml-tokens/std", "orml-traits/std", "orml-vesting/std", "orml-xcm-support/std", @@ -173,7 +172,6 @@ try-runtime = [ '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-executive = { workspace = true } 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 }