From ff6b80b00684caa0813e3038f53837d6d14bd12e Mon Sep 17 00:00:00 2001 From: rocketman1997 <101121223+rocketman1997@users.noreply.github.com> Date: Fri, 1 Apr 2022 13:27:30 +0800 Subject: [PATCH] Asset-manager for foreign xcm related assets (#1450) * Integrate asset-manager with heiko runtime first * Add asset-manager * Integation test with statemine and some fix accordingly * Rename to xcm-gadget * Fix for llint&fmt * Add more test * Revamp local transact for foreign asset * Add integration tests for cross-chain cases * Fix lint&Allow register exist asset&Remove weight hint check * More test * Seperate xcm config&Refactor accordingly * Integrate asset-manager with vanilla runtime * Revamp transfer fee to non-reserve chain * Benchmark integration&fix * Temporary remove from heiko&Use vanilla for integration test * Integrate with kerria Co-authored-by: rocketman1997 --- .github/workflows/ci.yml | 4 + .maintain/frame-weight-template.hbs | 9 +- Cargo.lock | 332 +++++++++- Cargo.toml | 2 +- Dockerfile.release | 2 +- LICENSE_TEMPLATE | 13 + Makefile | 16 +- heiko-statemine.json | 116 ++++ integration-tests/Cargo.toml | 84 +++ integration-tests/src/kusama_call.rs | 15 + integration-tests/src/kusama_test_net.rs | 139 +++++ integration-tests/src/kusama_transfer.rs | 77 +++ integration-tests/src/lib.rs | 21 + integration-tests/src/setup.rs | 101 ++++ integration-tests/src/statemine.rs | 195 ++++++ pallets/asset-manager/Cargo.toml | 51 ++ pallets/asset-manager/src/benchmarks.rs | 131 ++++ pallets/asset-manager/src/lib.rs | 374 ++++++++++++ pallets/asset-manager/src/migrations.rs | 557 +++++++++++++++++ pallets/asset-manager/src/mock.rs | 200 +++++++ pallets/asset-manager/src/tests.rs | 731 +++++++++++++++++++++++ pallets/asset-manager/src/weights.rs | 114 ++++ pallets/crowdloans/Cargo.toml | 1 + pallets/crowdloans/src/mock.rs | 8 + pallets/liquid-staking/Cargo.toml | 1 + pallets/liquid-staking/src/mock.rs | 8 + pallets/xcm-helper/src/lib.rs | 7 + primitives/Cargo.toml | 3 + primitives/src/currency.rs | 11 +- primitives/src/lib.rs | 10 + primitives/src/ump.rs | 4 +- primitives/src/xcm_gadget.rs | 383 ++++++++++++ runtime/heiko/Cargo.toml | 5 +- runtime/heiko/src/lib.rs | 9 +- runtime/kerria/Cargo.toml | 4 + runtime/kerria/src/lib.rs | 155 ++++- runtime/parallel/src/lib.rs | 9 +- runtime/vanilla/Cargo.toml | 6 +- runtime/vanilla/src/lib.rs | 153 ++++- rustfmt.toml | 2 + 40 files changed, 3965 insertions(+), 98 deletions(-) create mode 100644 LICENSE_TEMPLATE create mode 100644 heiko-statemine.json create mode 100644 integration-tests/Cargo.toml create mode 100644 integration-tests/src/kusama_call.rs create mode 100644 integration-tests/src/kusama_test_net.rs create mode 100644 integration-tests/src/kusama_transfer.rs create mode 100644 integration-tests/src/lib.rs create mode 100644 integration-tests/src/setup.rs create mode 100644 integration-tests/src/statemine.rs create mode 100644 pallets/asset-manager/Cargo.toml create mode 100644 pallets/asset-manager/src/benchmarks.rs create mode 100644 pallets/asset-manager/src/lib.rs create mode 100644 pallets/asset-manager/src/migrations.rs create mode 100644 pallets/asset-manager/src/mock.rs create mode 100644 pallets/asset-manager/src/tests.rs create mode 100644 pallets/asset-manager/src/weights.rs create mode 100644 primitives/src/xcm_gadget.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d719cd70..eaa7d3d80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,3 +90,7 @@ jobs: - name: Check Test run: | make test + + - name: Check Integration Test + run: | + make integration-test diff --git a/.maintain/frame-weight-template.hbs b/.maintain/frame-weight-template.hbs index ffc91bab9..341e02d1d 100644 --- a/.maintain/frame-weight-template.hbs +++ b/.maintain/frame-weight-template.hbs @@ -1,14 +1,11 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// // http://www.apache.org/licenses/LICENSE-2.0 -// + // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/Cargo.lock b/Cargo.lock index a13b2534d..85685e403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1354,7 +1354,7 @@ dependencies = [ [[package]] name = "cumulus-client-cli" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "clap", "sc-cli", @@ -1364,7 +1364,7 @@ dependencies = [ [[package]] name = "cumulus-client-collator" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-client-consensus-common", "cumulus-client-network", @@ -1388,7 +1388,7 @@ dependencies = [ [[package]] name = "cumulus-client-consensus-aura" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "async-trait", "cumulus-client-consensus-common", @@ -1417,7 +1417,7 @@ dependencies = [ [[package]] name = "cumulus-client-consensus-common" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "async-trait", "cumulus-relay-chain-interface", @@ -1438,7 +1438,7 @@ dependencies = [ [[package]] name = "cumulus-client-network" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "async-trait", "cumulus-relay-chain-interface", @@ -1463,7 +1463,7 @@ dependencies = [ [[package]] name = "cumulus-client-pov-recovery" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "cumulus-relay-chain-interface", @@ -1487,7 +1487,7 @@ dependencies = [ [[package]] name = "cumulus-client-service" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-client-collator", "cumulus-client-consensus-common", @@ -1516,7 +1516,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-aura-ext" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "frame-executive", "frame-support", @@ -1534,7 +1534,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-dmp-queue" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -1552,7 +1552,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-parachain-system" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-pallet-parachain-system-proc-macro", "cumulus-primitives-core", @@ -1582,7 +1582,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-parachain-system-proc-macro" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.36", @@ -1590,10 +1590,23 @@ dependencies = [ "syn 1.0.86", ] +[[package]] +name = "cumulus-pallet-session-benchmarking" +version = "3.0.0" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "sp-runtime", + "sp-std", +] + [[package]] name = "cumulus-pallet-xcm" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -1610,7 +1623,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-xcmp-queue" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -1625,10 +1638,27 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "cumulus-ping" +version = "0.1.0" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" +dependencies = [ + "cumulus-pallet-xcm", + "cumulus-primitives-core", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-runtime", + "sp-std", + "xcm", +] + [[package]] name = "cumulus-primitives-core" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "frame-support", "parity-scale-codec", @@ -1644,7 +1674,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-parachain-inherent" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -1667,7 +1697,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-timestamp" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "sp-inherents", @@ -1678,7 +1708,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-utility" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -1695,7 +1725,7 @@ dependencies = [ [[package]] name = "cumulus-relay-chain-interface" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -1716,7 +1746,7 @@ dependencies = [ [[package]] name = "cumulus-relay-chain-local" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -1744,7 +1774,7 @@ dependencies = [ [[package]] name = "cumulus-test-relay-sproof-builder" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "parity-scale-codec", @@ -2789,6 +2819,7 @@ dependencies = [ "orml-xcm-support", "orml-xtokens", "pallet-amm", + "pallet-asset-manager", "pallet-assets", "pallet-aura", "pallet-authorship", @@ -3538,6 +3569,7 @@ dependencies = [ "orml-xcm-support", "orml-xtokens", "pallet-amm", + "pallet-asset-manager", "pallet-assets", "pallet-aura", "pallet-authorship", @@ -4995,7 +5027,7 @@ dependencies = [ [[package]] name = "orml-oracle" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "frame-support", "frame-system", @@ -5013,7 +5045,7 @@ dependencies = [ [[package]] name = "orml-oracle-rpc" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -5028,7 +5060,7 @@ dependencies = [ [[package]] name = "orml-oracle-rpc-runtime-api" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "parity-scale-codec", "sp-api", @@ -5038,7 +5070,7 @@ dependencies = [ [[package]] name = "orml-traits" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "frame-support", "impl-trait-for-tuples", @@ -5056,7 +5088,7 @@ dependencies = [ [[package]] name = "orml-utilities" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "frame-support", "parity-scale-codec", @@ -5070,7 +5102,7 @@ dependencies = [ [[package]] name = "orml-vesting" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "frame-support", "frame-system", @@ -5085,7 +5117,7 @@ dependencies = [ [[package]] name = "orml-xcm" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "frame-support", "frame-system", @@ -5099,7 +5131,7 @@ dependencies = [ [[package]] name = "orml-xcm-support" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "frame-support", "orml-traits", @@ -5113,7 +5145,7 @@ dependencies = [ [[package]] name = "orml-xtokens" version = "0.4.1-dev" -source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#aac79b3b31953381669a2ffa9b3e9bfe48e87f38" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library.git#0a5a2df3b796f1fcfec3c4c32cc853716fa6c3da" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -5170,6 +5202,43 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-asset-manager" +version = "1.8.1" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parallel-primitives", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "pallet-asset-tx-payment" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.17#22d40c761a985482f93bbbea5ba4199bdba74f8e" +dependencies = [ + "frame-support", + "frame-system", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-assets" version = "4.0.0-dev" @@ -5433,7 +5502,7 @@ dependencies = [ [[package]] name = "pallet-collator-selection" version = "3.0.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "frame-benchmarking", "frame-support", @@ -5479,6 +5548,7 @@ dependencies = [ "frame-support", "frame-system", "kusama-runtime", + "orml-traits", "orml-xcm-support", "orml-xtokens", "pallet-assets", @@ -5716,6 +5786,7 @@ dependencies = [ "frame-system", "hex", "kusama-runtime", + "orml-traits", "orml-xcm-support", "orml-xtokens", "pallet-assets", @@ -6294,6 +6365,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-uniques" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.17#22d40c761a985482f93bbbea5ba4199bdba74f8e" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-utility" version = "4.0.0-dev" @@ -6402,7 +6488,7 @@ dependencies = [ [[package]] name = "parachain-info" version = "0.1.0" -source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#76479e7fef3af7c8828a44647847b01afd5fefe5" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -6412,6 +6498,33 @@ dependencies = [ "serde", ] +[[package]] +name = "parachains-common" +version = "1.0.0" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" +dependencies = [ + "frame-executive", + "frame-support", + "frame-system", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-common", + "scale-info", + "sp-consensus-aura", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "substrate-wasm-builder", + "xcm", + "xcm-executor", +] + [[package]] name = "parallel" version = "1.8.1" @@ -6492,6 +6605,7 @@ dependencies = [ "cumulus-primitives-core", "frame-support", "frame-system", + "log", "num-bigint 0.3.3", "num-traits", "orml-oracle", @@ -6504,6 +6618,7 @@ dependencies = [ "sp-runtime", "sp-std", "xcm", + "xcm-builder", "xcm-executor", ] @@ -8696,6 +8811,72 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "runtime-integration-tests" +version = "1.8.0" +dependencies = [ + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-primitives-timestamp", + "cumulus-primitives-utility", + "cumulus-test-relay-sproof-builder", + "env_logger", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "kusama-runtime", + "log", + "orml-oracle", + "orml-traits", + "orml-vesting", + "orml-xcm", + "orml-xcm-support", + "orml-xtokens", + "pallet-amm", + "pallet-asset-manager", + "pallet-balances", + "pallet-bridge", + "pallet-collator-selection", + "pallet-crowdloans", + "pallet-currency-adapter", + "pallet-emergency-shutdown", + "pallet-farming", + "pallet-liquid-staking", + "pallet-loans", + "pallet-loans-rpc-runtime-api", + "pallet-prices", + "pallet-router", + "pallet-router-rpc-runtime-api", + "pallet-xcm", + "pallet-xcm-helper", + "parachain-info", + "parallel-primitives", + "parity-scale-codec", + "polkadot-parachain", + "polkadot-primitives", + "polkadot-runtime-common", + "polkadot-runtime-parachains", + "serde", + "serde_json", + "smallvec", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", + "statemine-runtime", + "static_assertions", + "vanilla-runtime", + "xcm", + "xcm-builder", + "xcm-emulator", + "xcm-executor", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -10933,6 +11114,70 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "statemine-runtime" +version = "2.0.0" +source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.17#db11baacc325537be74ad34517fcb28ed9ded6c6" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-ping", + "cumulus-primitives-core", + "cumulus-primitives-timestamp", + "cumulus-primitives-utility", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "kusama-runtime-constants", + "log", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-multisig", + "pallet-proxy", + "pallet-session", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-uniques", + "pallet-utility", + "pallet-xcm", + "parachain-info", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain", + "polkadot-runtime-common", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -11736,6 +11981,7 @@ dependencies = [ "orml-xcm-support", "orml-xtokens", "pallet-amm", + "pallet-asset-manager", "pallet-assets", "pallet-aura", "pallet-authorship", @@ -12465,6 +12711,30 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "xcm-emulator" +version = "0.1.0" +source = "git+https://github.com/shaunxw/xcm-simulator?rev=24ccbce563d1f99019b4cdfa2f3af4e99bac0dfc#24ccbce563d1f99019b4cdfa2f3af4e99bac0dfc" +dependencies = [ + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-test-relay-sproof-builder", + "frame-support", + "frame-system", + "parachain-info", + "parity-scale-codec", + "paste", + "polkadot-primitives", + "polkadot-runtime-parachains", + "sp-io", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "xcm-executor" version = "0.9.17" diff --git a/Cargo.toml b/Cargo.toml index a3a3ed621..bdd128d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,4 @@ overflow-checks = true panic = 'unwind' [workspace] -members = ['node/*', 'pallets/*', 'runtime/*'] +members = ['node/*', 'pallets/*', 'runtime/*', 'integration-tests'] \ No newline at end of file diff --git a/Dockerfile.release b/Dockerfile.release index 62282320b..2ce6d6fed 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -8,7 +8,7 @@ WORKDIR /parallel COPY . /parallel -RUN cargo build --$PROFILE --bin $BIN --features runtime-benchmarks --features try-runtime +RUN cargo build --workspace --exclude runtime-integration-tests --$PROFILE --bin $BIN --features runtime-benchmarks --features try-runtime # ===== SECOND STAGE ====== diff --git a/LICENSE_TEMPLATE b/LICENSE_TEMPLATE new file mode 100644 index 000000000..25e594f87 --- /dev/null +++ b/LICENSE_TEMPLATE @@ -0,0 +1,13 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. \ No newline at end of file diff --git a/Makefile b/Makefile index 26b567119..17993c3c1 100644 --- a/Makefile +++ b/Makefile @@ -30,12 +30,16 @@ submodules: build: cargo build --bin parallel +.PHONY: build-release +build-release: + cargo build --workspace --exclude runtime-integration-tests --bin parallel --release --features runtime-benchmarks --features try-runtime + .PHONY: clean clean: cargo clean -p parallel -p vanilla-runtime -p kerria-runtime -p heiko-runtime -p parallel-runtime .PHONY: ci -ci: check lint check-helper check-wasm test +ci: check lint check-helper check-wasm test integration-test .PHONY: check check: @@ -51,7 +55,11 @@ check-helper: .PHONY: test test: - SKIP_WASM_BUILD= cargo test --workspace --features runtime-benchmarks --exclude parallel --exclude parallel-runtime --exclude vanilla-runtime --exclude kerria-runtime --exclude heiko-runtime --exclude pallet-loans-rpc --exclude pallet-loans-rpc-runtime-api --exclude parallel-primitives -- --nocapture + SKIP_WASM_BUILD= cargo test --workspace --features runtime-benchmarks --exclude runtime-integration-tests --exclude parallel --exclude parallel-runtime --exclude vanilla-runtime --exclude kerria-runtime --exclude heiko-runtime --exclude pallet-loans-rpc --exclude pallet-loans-rpc-runtime-api --exclude parallel-primitives -- --nocapture + +.PHONY: integration-test +integration-test: + SKIP_WASM_BUILD= cargo test -p runtime-integration-tests -- --nocapture .PHONY: bench bench: bench-loans bench-liquid-staking bench-amm bench-amm-router bench-crowdloans bench-bridge bench-xcm-helper bench-farming @@ -89,6 +97,10 @@ bench-liquid-staking: bench-amm-router: cargo run --release --features runtime-benchmarks -- benchmark --chain=$(CHAIN) --execution=wasm --wasm-execution=compiled --pallet=pallet-router --extrinsic='*' --steps=50 --repeat=20 --heap-pages=4096 --template=./.maintain/frame-weight-template.hbs --output=./pallets/router/src/weights.rs +.PHONY: bench-asset-manager +bench-asset-manager: + cargo run --release --features runtime-benchmarks -- benchmark --chain=$(CHAIN) --execution=wasm --wasm-execution=compiled --pallet=pallet-asset-manager --extrinsic='*' --steps=50 --repeat=20 --heap-pages=4096 --template=./.maintain/frame-weight-template.hbs --output=./pallets/asset-manager/src/weights.rs + .PHONY: bench-payroll bench-payroll: cargo run --release --features runtime-benchmarks -- benchmark --chain=$(CHAIN) --execution=wasm --wasm-execution=compiled --pallet=pallet-payroll --extrinsic='*' --steps=50 --repeat=20 --heap-pages=4096 --template=./.maintain/frame-weight-template.hbs --output=./pallets/payroll/src/weights.rs diff --git a/heiko-statemine.json b/heiko-statemine.json new file mode 100644 index 000000000..bc63da4a5 --- /dev/null +++ b/heiko-statemine.json @@ -0,0 +1,116 @@ +{ + "relaychain": { + "bin": "../polkadot/target/release/polkadot", + "chain": "rococo-local", + "nodes": [ + { + "name": "alice", + "wsPort": 9944, + "rpcPort": 9933, + "port": 30333, + "basePath": "data/9944" + }, + { + "name": "bob", + "wsPort": 9945, + "rpcPort": 9934, + "port": 30334, + "basePath": "data/9945" + }, + { + "name": "charlie", + "wsPort": 9946, + "rpcPort": 9935, + "port": 30335, + "basePath": "data/9946" + }, + { + "name": "dave", + "wsPort": 9947, + "rpcPort": 9936, + "port": 30336, + "basePath": "data/9947" + } + ], + "genesis": { + "runtime": { + "runtime_genesis_config": { + "configuration": { + "config": { + "validation_upgrade_frequency": 1, + "validation_upgrade_delay": 10 + } + } + } + } + } + }, + "parachains": [ + { + "bin": "../parallel/target/release/parallel", + "chain": "heiko-dev", + "nodes": [ + { + "wsPort": 9948, + "port": 30337, + "rpcPort": 9937, + "name": "alice", + "basePath": "data/9948", + "flags": [ + "--unsafe-ws-external", + "--unsafe-rpc-external", + "--rpc-external", + "--ws-external", + "--rpc-cors=all", + "--rpc-methods=Unsafe", + "--force-authoring", + "--", + "--execution=wasm", + "--state-cache-size", + "0" + ] + } + ] + }, + { + "bin": "../cumulus/target/release/polkadot-collator", + "chain": "statemine-local", + "nodes": [ + { + "wsPort": 9950, + "port": 30339, + "name": "alice", + "basePath": "data/9950", + "flags": [ + "--unsafe-ws-external", + "--unsafe-rpc-external", + "--rpc-external", + "--ws-external", + "--rpc-cors=all", + "--rpc-methods=Unsafe", + "--force-authoring", + "--", + "--execution=wasm" + ] + } + ] + } + ], + "simpleParachains": [], + "hrmpChannels": [ + { + "sender": 1000, + "recipient": 2085, + "maxCapacity": 8, + "maxMessageSize": 512 + }, + { + "sender": 2085, + "recipient": 1000, + "maxCapacity": 8, + "maxMessageSize": 512 + } + ], + "types": {}, + "finalization": false +} \ No newline at end of file diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml new file mode 100644 index 000000000..403dac836 --- /dev/null +++ b/integration-tests/Cargo.toml @@ -0,0 +1,84 @@ +[package] +authors = ['Parallel Team'] +edition = '2021' +homepage = 'https://parallel.fi' +license = 'Apache' +name = 'runtime-integration-tests' +repository = 'https://github.com/parallel-finance/parallel' +version = '1.8.0' + + +[dependencies] +codec = { package = 'parity-scale-codec', version = '2.3.1', default-features = false, features = ['derive'] } +hex-literal = '0.3.3' +serde = { version = '1.0.136', features = ['derive'], optional = true } +serde_json = '1.0.68' +hex = { version = '0.4' } +smallvec = '1.6.1' +static_assertions = '1.1.0' + +# Substrate +frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } +frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } +pallet-balances = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } +sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } +sp-std = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } + +# Polkadot dependencies +pallet-xcm = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17'} +polkadot-parachain = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17'} +polkadot-runtime-common = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17'} +xcm = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17'} +xcm-builder = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17'} +xcm-executor = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17'} + +# Cumulus dependencies +cumulus-pallet-dmp-queue = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +cumulus-pallet-parachain-system = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +cumulus-pallet-xcm = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +cumulus-pallet-xcmp-queue = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +cumulus-primitives-core = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +cumulus-primitives-timestamp = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +cumulus-primitives-utility = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +pallet-collator-selection = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } +parachain-info = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17' } + +# ORML dependencies +orml-oracle = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git'} +orml-traits = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } +orml-vesting = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } +orml-xcm = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } +orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } +orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } + +# Parallel dependencies +pallet-asset-manager = { path = '../pallets/asset-manager' } +pallet-amm = { path = '../pallets/amm' } +pallet-bridge = { path = '../pallets/bridge' } +pallet-crowdloans = { path = '../pallets/crowdloans' } +pallet-currency-adapter = { path = '../pallets/currency-adapter' } +pallet-emergency-shutdown = { path = '../pallets/emergency-shutdown' } +pallet-farming = { path = '../pallets/farming' } +pallet-liquid-staking = { path = '../pallets/liquid-staking' } +pallet-loans = { path = '../pallets/loans' } +pallet-loans-rpc-runtime-api = { path = '../pallets/loans/rpc/runtime-api' } +pallet-prices = { path = '../pallets/prices' } +pallet-router = { path = '../pallets/router' } +pallet-router-rpc-runtime-api = { path = '../pallets/router/rpc/runtime-api' } +pallet-xcm-helper = { path = '../pallets/xcm-helper' } +primitives = { package = 'parallel-primitives', path = '../primitives' } +vanilla-runtime = { path = '../runtime/vanilla' } + +[dev-dependencies] +env_logger = '0.9.0' +log = '0.4.14' +sp-io = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17'} +sp-trie = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } +polkadot-primitives = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.17' } +polkadot-runtime-parachains = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.17' } +kusama-runtime = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.17' } +cumulus-primitives-parachain-inherent = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.17' } +cumulus-test-relay-sproof-builder = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.17' } +statemine-runtime = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.17' } +xcm-emulator = { git = 'https://github.com/shaunxw/xcm-simulator', rev = '24ccbce563d1f99019b4cdfa2f3af4e99bac0dfc' } + diff --git a/integration-tests/src/kusama_call.rs b/integration-tests/src/kusama_call.rs new file mode 100644 index 000000000..54bc13c01 --- /dev/null +++ b/integration-tests/src/kusama_call.rs @@ -0,0 +1,15 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cross-chain transact like crowdloan&liquid-staking should be moved here diff --git a/integration-tests/src/kusama_test_net.rs b/integration-tests/src/kusama_test_net.rs new file mode 100644 index 000000000..13cbac141 --- /dev/null +++ b/integration-tests/src/kusama_test_net.rs @@ -0,0 +1,139 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Relay chain and parachains emulation. + +use crate::setup::*; +use cumulus_primitives_core::ParaId; +use frame_support::traits::GenesisBuild; +use polkadot_primitives::v1::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; +use polkadot_runtime_parachains::configuration::HostConfiguration; +use primitives::AccountId; +use sp_runtime::traits::AccountIdConversion; +use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +decl_test_relay_chain! { + pub struct KusamaNet { + Runtime = kusama_runtime::Runtime, + XcmConfig = kusama_runtime::xcm_config::XcmConfig, + new_ext = kusama_ext(), + } +} + +decl_test_parachain! { + pub struct Vanilla { + Runtime = vanilla_runtime::Runtime, + Origin = vanilla_runtime::Origin, + XcmpMessageHandler = vanilla_runtime ::XcmpQueue, + DmpMessageHandler = vanilla_runtime::DmpQueue, + new_ext = para_ext(2085), + } +} + +decl_test_parachain! { + pub struct Statemine { + Runtime = statemine_runtime::Runtime, + Origin = statemine_runtime::Origin, + XcmpMessageHandler = statemine_runtime::XcmpQueue, + DmpMessageHandler = statemine_runtime::DmpQueue, + new_ext = para_ext(1000), + } +} + +decl_test_network! { + pub struct TestNet { + relay_chain = KusamaNet, + parachains = vec![ + (1000, Statemine), + (2085, Vanilla), + ], + } +} + +fn default_parachains_host_configuration() -> HostConfiguration { + HostConfiguration { + minimum_validation_upgrade_delay: 5, + validation_upgrade_cooldown: 5u32, + validation_upgrade_delay: 5, + code_retention_period: 1200, + max_code_size: MAX_CODE_SIZE, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 32 * 1024, + group_rotation_frequency: 20, + chain_availability_period: 4, + thread_availability_period: 4, + max_upward_queue_count: 8, + max_upward_queue_size: 1024 * 1024, + max_downward_message_size: 1024, + ump_service_total_weight: 4 * 1_000_000_000, + max_upward_message_size: 1024 * 1024, + max_upward_message_num_per_candidate: 5, + hrmp_sender_deposit: 0, + hrmp_recipient_deposit: 0, + hrmp_channel_max_capacity: 8, + hrmp_channel_max_total_size: 8 * 1024, + hrmp_max_parachain_inbound_channels: 4, + hrmp_max_parathread_inbound_channels: 4, + hrmp_channel_max_message_size: 1024 * 1024, + hrmp_max_parachain_outbound_channels: 4, + hrmp_max_parathread_outbound_channels: 4, + hrmp_max_message_num_per_candidate: 5, + dispute_period: 6, + no_show_slots: 2, + n_delay_tranches: 25, + needed_approvals: 2, + relay_vrf_modulo_samples: 2, + zeroth_delay_tranche_width: 0, + ..Default::default() + } +} + +pub fn kusama_ext() -> sp_io::TestExternalities { + use kusama_runtime::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (AccountId::from(ALICE), ksm(100f64)), + (ParaId::from(2085 as u32).into_account(), ksm(100f64)), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + polkadot_runtime_parachains::configuration::GenesisConfig:: { + config: default_parachains_host_configuration(), + } + .assimilate_storage(&mut t) + .unwrap(); + + >::assimilate_storage( + &pallet_xcm::GenesisConfig { + safe_xcm_version: Some(2), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { + ExtBuilder::default().parachain_id(parachain_id).build() +} diff --git a/integration-tests/src/kusama_transfer.rs b/integration-tests/src/kusama_transfer.rs new file mode 100644 index 000000000..1c1365929 --- /dev/null +++ b/integration-tests/src/kusama_transfer.rs @@ -0,0 +1,77 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cross-chain transfer tests within Kusama network. + +use cumulus_primitives_core::ParaId; +use frame_support::assert_ok; +use primitives::{tokens::*, AccountId}; +use vanilla_runtime::Assets; +use xcm::{latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation}; +use xcm_emulator::TestExt; + +use crate::{kusama_test_net::*, setup::*}; + +#[test] +fn transfer_from_relay_chain() { + KusamaNet::execute_with(|| { + assert_ok!(kusama_runtime::XcmPallet::reserve_transfer_assets( + kusama_runtime::Origin::signed(ALICE.into()), + Box::new(VersionedMultiLocation::V1(X1(Parachain(2085)).into())), + Box::new(VersionedMultiLocation::V1( + X1(Junction::AccountId32 { + id: BOB, + network: NetworkId::Any + }) + .into() + )), + Box::new(VersionedMultiAssets::V1((Here, ksm(1f64)).into())), + 0, + )); + }); + + Vanilla::execute_with(|| { + assert_eq!(Assets::balance(KSM, &AccountId::from(BOB)), 999_952_000_000); + //ksm fee in heiko is 48_000_000 + }); +} + +#[test] +fn transfer_to_relay_chain() { + use vanilla_runtime::{Origin, XTokens}; + Vanilla::execute_with(|| { + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + KSM, + ksm(1f64), + Box::new(xcm::VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Junction::AccountId32 { + id: BOB, + network: NetworkId::Any + }) + ))), + 4_000_000_000 + )); + }); + + KusamaNet::execute_with(|| { + let para_acc: AccountId = ParaId::from(2085).into_account(); + println!("heiko para account in relaychain:{:?}", para_acc); + assert_eq!( + kusama_runtime::Balances::free_balance(&AccountId::from(BOB)), + 999_893_333_340 //xcm fee in kusama is 106_666_660~=0.015$ + ); + }); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs new file mode 100644 index 000000000..63afe5a55 --- /dev/null +++ b/integration-tests/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +mod kusama_call; +mod kusama_test_net; +mod kusama_transfer; +mod setup; +mod statemine; diff --git a/integration-tests/src/setup.rs b/integration-tests/src/setup.rs new file mode 100644 index 000000000..e5cb1219f --- /dev/null +++ b/integration-tests/src/setup.rs @@ -0,0 +1,101 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use codec::Encode; +use frame_support::traits::GenesisBuild; +pub use orml_traits::{Change, GetByKey, MultiCurrency}; +use primitives::{tokens::*, AccountId, Balance}; +pub use sp_runtime::{ + traits::{AccountIdConversion, BadOrigin, Convert, Zero}, + DispatchError, DispatchResult, FixedPointNumber, MultiAddress, +}; + +pub const ALICE: [u8; 32] = [0u8; 32]; +pub const BOB: [u8; 32] = [1u8; 32]; +pub const KSM_DECIMAL: u32 = 12; + +pub fn ksm(n: f64) -> Balance { + (n as u128) * 10u128.pow(KSM_DECIMAL) +} + +pub struct ExtBuilder { + parachain_id: u32, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { parachain_id: 2085 } + } +} + +impl ExtBuilder { + #[allow(dead_code)] + pub fn parachain_id(mut self, parachain_id: u32) -> Self { + self.parachain_id = parachain_id; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + use vanilla_runtime::{Assets, Origin, Runtime, System}; + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + >::assimilate_storage( + ¶chain_info::GenesisConfig { + parachain_id: self.parachain_id.into(), + }, + &mut t, + ) + .unwrap(); + + >::assimilate_storage( + &pallet_xcm::GenesisConfig { + safe_xcm_version: Some(2), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + Assets::force_create( + Origin::root(), + KSM, + MultiAddress::Id(AccountId::from(ALICE)), + true, + 1, + ) + .unwrap(); + Assets::force_set_metadata( + Origin::root(), + KSM, + b"Kusama".to_vec(), + b"KSM".to_vec(), + 12, + false, + ) + .unwrap(); + Assets::mint( + Origin::signed(AccountId::from(ALICE)), + KSM, + MultiAddress::Id(AccountId::from(ALICE)), + ksm(100f64), + ) + .unwrap(); + }); + ext + } +} diff --git a/integration-tests/src/statemine.rs b/integration-tests/src/statemine.rs new file mode 100644 index 000000000..78e20565e --- /dev/null +++ b/integration-tests/src/statemine.rs @@ -0,0 +1,195 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{kusama_test_net::*, setup::*}; +use cumulus_primitives_core::ParaId; +use frame_support::assert_ok; +use frame_support::traits::Currency; +use polkadot_parachain::primitives::Sibling; +use primitives::ump::{XcmCall, XcmWeightFeeMisc}; +use primitives::{tokens::*, AccountId, Balance, CurrencyId}; +use sp_runtime::traits::AccountIdConversion; +use xcm::latest::prelude::*; +use xcm_emulator::TestExt; + +pub const RMRK_ASSET_ID: u32 = 8; +pub const RMRK_DECIMAL: u8 = 10; +pub const RMRK_MINIMAL_BALANCE: Balance = 10; +pub const RMRK_WEIGHT_PER_SEC: u128 = 100000000000; +pub const HEIKO_RMRK_ASSET_ID: u32 = 4187061565; +pub const STATEMINE_FEE_AMOUNT: u128 = 8_000_000_000; +pub const RELAY_FEE_AMOUNT: u128 = 106_666_660; + +pub fn rmrk(n: f64) -> Balance { + (n as u128) * 10u128.pow(RMRK_DECIMAL.into()) +} + +#[test] +fn statemine() { + use primitives::{xcm_gadget::AssetType, AssetRegistrarMetadata}; + let statemine_rmrk_asset_location = + MultiLocation::new(1, X3(Parachain(1000), PalletInstance(50), GeneralIndex(8))); + let statemine_rmrk_asset_type = AssetType::Xcm(statemine_rmrk_asset_location); + let statemine_rmrk_asset_id: CurrencyId = statemine_rmrk_asset_type.clone().into(); + let statemine_rmrk_asset_meta = AssetRegistrarMetadata { + name: b"RMRK".to_vec(), + symbol: b"RMRK".to_vec(), + decimals: RMRK_DECIMAL, + is_frozen: false, + }; + Vanilla::execute_with(|| { + use vanilla_runtime::{AssetManager, Origin}; + assert_eq!(statemine_rmrk_asset_id, HEIKO_RMRK_ASSET_ID); + let another_asset: AssetType = AssetType::Xcm(MultiLocation::new( + 1, + X3(Parachain(1000), PalletInstance(50), GeneralIndex(9)), + )); + let another_asset_id: CurrencyId = another_asset.into(); + assert_eq!(another_asset_id, 23310203); + assert_ne!(another_asset_id, statemine_rmrk_asset_id); + assert_ok!(AssetManager::register_asset( + Origin::root(), + statemine_rmrk_asset_type.clone(), + None, + Some(statemine_rmrk_asset_meta.clone()), + RMRK_MINIMAL_BALANCE, + true + )); + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + statemine_rmrk_asset_type, + RMRK_WEIGHT_PER_SEC, + )); + }); + Statemine::execute_with(|| { + use statemine_runtime::{Assets, Balances, Origin, PolkadotXcm, System}; + + let origin = Origin::signed(ALICE.into()); + + Balances::make_free_balance_be(&ALICE.into(), ksm(10f64)); + + // need to have some KSM to be able to receive user assets + Balances::make_free_balance_be(&Sibling::from(2085).into_account(), ksm(10f64)); + + Assets::force_create( + Origin::root(), + RMRK_ASSET_ID, + MultiAddress::Id(AccountId::from(ALICE)), + true, + 1, + ) + .unwrap(); + Assets::force_set_metadata( + Origin::root(), + RMRK_ASSET_ID, + b"RMRK".to_vec(), + b"RMRK".to_vec(), + RMRK_DECIMAL, + false, + ) + .unwrap(); + Assets::mint( + Origin::signed(AccountId::from(ALICE)), + RMRK_ASSET_ID, + MultiAddress::Id(AccountId::from(ALICE)), + rmrk(10f64), + ) + .unwrap(); + + System::reset_events(); + + let para_acc: AccountId = Sibling::from(2085).into_account(); + println!("heiko para account in sibling chain:{:?}", para_acc); + + assert_ok!(PolkadotXcm::reserve_transfer_assets( + origin.clone(), + Box::new(MultiLocation::new(1, X1(Parachain(2085))).into()), + Box::new( + Junction::AccountId32 { + id: BOB, + network: NetworkId::Any + } + .into() + .into() + ), + Box::new((X2(PalletInstance(50), GeneralIndex(8)), rmrk(2f64)).into()), + 0 + )); + }); + // Rerun the Statemine::execute to actually send the egress message via XCM + Statemine::execute_with(|| {}); + Vanilla::execute_with(|| { + use vanilla_runtime::{Assets, Origin, XTokens, XcmHelper}; + assert_eq!( + Assets::balance(statemine_rmrk_asset_id, &AccountId::from(BOB)), + 19940000000 + ); //rmrk fee in heiko is 60_000_000 which is 0.006rmrk~=0.09$ + assert_ok!(Assets::mint( + Origin::signed(AccountId::from(ALICE)), + KSM, + MultiAddress::Id(AccountId::from(BOB)), + ksm(1f64), + )); //mint some ksm to BOB to pay for the xcm fee + assert_ok!(XcmHelper::update_xcm_weight_fee( + Origin::root(), + XcmCall::TransferToSiblingchain(Box::new((1, Parachain(1000)).into())), + XcmWeightFeeMisc { + weight: (STATEMINE_FEE_AMOUNT / 2) as u64, + fee: STATEMINE_FEE_AMOUNT / 2, + } + )); // set xcm transfer fee + assert_ok!(XTokens::transfer_multicurrencies( + Origin::signed(BOB.into()), + vec![ + (KSM, STATEMINE_FEE_AMOUNT), + (HEIKO_RMRK_ASSET_ID, rmrk(1f64)), + ], + 0, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(1000), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + (STATEMINE_FEE_AMOUNT / 2) as u64 + )); + }); + KusamaNet::execute_with(|| { + let heiko_sovereign: AccountId = ParaId::from(2085u32).into_account(); + let statemine_sovereign: AccountId = ParaId::from(1000u32).into_account(); + assert_eq!( + ksm(100f64) - STATEMINE_FEE_AMOUNT / 2, + kusama_runtime::Balances::free_balance(&heiko_sovereign) + ); //4_000_000_000 deducted from heiko_sovereign + assert_eq!( + STATEMINE_FEE_AMOUNT / 2 - RELAY_FEE_AMOUNT, + kusama_runtime::Balances::free_balance(&statemine_sovereign) + ); // 4_000_000_000-106_666_660 reserved into statemine_sovereign + }); + Statemine::execute_with(|| { + use statemine_runtime::Assets; + // recipient receive rmrk in statemine + assert_eq!( + rmrk(1f64), + Assets::balance(RMRK_ASSET_ID, &AccountId::from(BOB)) + ); + }); +} diff --git a/pallets/asset-manager/Cargo.toml b/pallets/asset-manager/Cargo.toml new file mode 100644 index 000000000..2622b9ef9 --- /dev/null +++ b/pallets/asset-manager/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = 'pallet-asset-manager' +authors = [ 'Parallel Team' ] +edition = '2021' +version = '1.8.1' + +[dependencies] +log = { version = '0.4', default-features = false } +serde = { version = '1.0.124', optional = true } + +# Primitives +parallel-primitives = { path = '../../primitives', default-features = false } + +# Substrate +frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17', default-features = false } +frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17', default-features = false } +parity-scale-codec = { version = '2.0.0', default-features = false, features = [ 'derive' ] } +scale-info = { version = '1.0', default-features = false, features = [ 'derive' ] } +sp-io = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17', default-features = false } +sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17', default-features = false } +sp-std = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17', default-features = false } + +# Polkadot +xcm = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.17', default-features = false } + +# Benchmarks +frame-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17', optional = true, default-features = false } + +[dev-dependencies] +pallet-balances = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } +sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.17' } + +[features] +default = [ 'std' ] +std = [ + 'frame-support/std', + 'frame-system/std', + 'parity-scale-codec/std', + 'scale-info/std', + 'serde', + 'sp-io/std', + 'sp-runtime/std', + 'sp-std/std', + 'xcm/std', + 'parallel-primitives/std', +] + +runtime-benchmarks = [ + 'frame-benchmarking', +] +try-runtime = [ 'frame-support/try-runtime' ] diff --git a/pallets/asset-manager/src/benchmarks.rs b/pallets/asset-manager/src/benchmarks.rs new file mode 100644 index 000000000..5af3fb833 --- /dev/null +++ b/pallets/asset-manager/src/benchmarks.rs @@ -0,0 +1,131 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "runtime-benchmarks")] +use crate::{Call, Config, Pallet}; +use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; +use frame_system::RawOrigin; +use xcm::latest::prelude::*; + +benchmarks! { + // This where clause allows us to create assetTypes + where_clause { where T::AssetType: From } + register_asset { + // does not really matter what we register + let asset_type = T::AssetType::default(); + let metadata = T::AssetRegistrarMetadata::default(); + let amount = 1u32.into(); + let asset_id: T::AssetId = asset_type.clone().into(); + + }: _(RawOrigin::Root, asset_type.clone(), None, Some(metadata), amount, true) + verify { + assert_eq!(Pallet::::asset_id_type(asset_id), Some(asset_type)); + } + + set_asset_units_per_second { + let asset_type: T::AssetType = MultiLocation::new(0, X1(GeneralIndex(0 as u128))).into(); + let metadata = T::AssetRegistrarMetadata::default(); + let amount = 1u32.into(); + Pallet::::register_asset(RawOrigin::Root.into(), asset_type.clone(), None, Some(metadata), amount, true)?; + Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type, 1)?; + + // does not really matter what we register, as long as it is different than the previous + let asset_type = T::AssetType::default(); + let metadata = T::AssetRegistrarMetadata::default(); + let amount = 1u32.into(); + let asset_id: T::AssetId = asset_type.clone().into(); + Pallet::::register_asset(RawOrigin::Root.into(), asset_type.clone(), None, Some(metadata), amount, true)?; + + }: _(RawOrigin::Root, asset_type.clone(), 1) + verify { + assert!(Pallet::::supported_fee_payment_assets().contains(&asset_type)); + assert_eq!(Pallet::::asset_type_units_per_second(asset_type), Some(1)); + } + + change_existing_asset_type { + let asset_type: T::AssetType = MultiLocation::new(0, X1(GeneralIndex(0 as u128))).into(); + let metadata = T::AssetRegistrarMetadata::default(); + let amount = 1u32.into(); + Pallet::::register_asset(RawOrigin::Root.into(), asset_type.clone(), None, Some(metadata), amount, true)?; + Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type, 1)?; + + let new_asset_type = T::AssetType::default(); + let asset_type_to_be_changed: T::AssetType = MultiLocation::new( + 0, + X1(GeneralIndex(0 as u128)) + ).into(); + let asset_id_to_be_changed = asset_type_to_be_changed.into(); + + }: _(RawOrigin::Root, asset_id_to_be_changed, new_asset_type.clone()) + verify { + assert_eq!(Pallet::::asset_id_type(asset_id_to_be_changed), Some(new_asset_type.clone())); + assert_eq!(Pallet::::asset_type_units_per_second(&new_asset_type), Some(1)); + assert!(Pallet::::supported_fee_payment_assets().contains(&new_asset_type)); + } + + remove_supported_asset { + let asset_type: T::AssetType = MultiLocation::new(0, X1(GeneralIndex(0 as u128))).into(); + let metadata = T::AssetRegistrarMetadata::default(); + let amount = 1u32.into(); + Pallet::::register_asset(RawOrigin::Root.into(), asset_type.clone(), None,Some(metadata), amount, true)?; + Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type, 1)?; + let asset_type_to_be_removed: T::AssetType = MultiLocation::new( + 0, + X1(GeneralIndex(0 as u128)) + ).into(); + // We try to remove the last asset type + }: _(RawOrigin::Root, asset_type_to_be_removed.clone()) + verify { + assert!(!Pallet::::supported_fee_payment_assets().contains(&asset_type_to_be_removed)); + assert_eq!(Pallet::::asset_type_units_per_second(asset_type_to_be_removed), None); + } + + remove_existing_asset_type { + let asset_type: T::AssetType = MultiLocation::new(0, X1(GeneralIndex(0 as u128))).into(); + let metadata = T::AssetRegistrarMetadata::default(); + let amount = 1u32.into(); + Pallet::::register_asset(RawOrigin::Root.into(), asset_type.clone(), None, Some(metadata), amount, true)?; + Pallet::::set_asset_units_per_second(RawOrigin::Root.into(), asset_type, 1)?; + + let asset_type_to_be_removed: T::AssetType = MultiLocation::new( + 0, + X1(GeneralIndex(0 as u128)) + ).into(); + let asset_id: T::AssetId = asset_type_to_be_removed.clone().into(); + }: _(RawOrigin::Root, asset_id) + verify { + assert!(Pallet::::asset_id_type(asset_id).is_none()); + assert!(Pallet::::asset_type_units_per_second(&asset_type_to_be_removed).is_none()); + assert!(!Pallet::::supported_fee_payment_assets().contains(&asset_type_to_be_removed)); + } +} + +#[cfg(test)] +mod tests { + use crate::mock::Test; + use sp_io::TestExternalities; + + pub fn new_test_ext() -> TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + TestExternalities::new(t) + } +} + +impl_benchmark_test_suite!( + Pallet, + crate::benchmarks::tests::new_test_ext(), + crate::mock::Test +); diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs new file mode 100644 index 000000000..2f7a5da96 --- /dev/null +++ b/pallets/asset-manager/src/lib.rs @@ -0,0 +1,374 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! TODO Doc comments for the pallet +//! # Asset Manager Pallet +//! +//! This pallet allows to register new assets if certain conditions are met +//! The main goal of this pallet is to allow moonbeam to register XCM assets +//! The assumption is we work with AssetTypes, which can then be comperted to AssetIds +//! +//! This pallet has three storage items: AssetIdType, which holds a mapping from AssetId->AssetType +//! AssetTypeUnitsPerSecond: an AssetType->u128 mapping that holds how much each AssetType should be +//! charged per unit of second, in the case such an Asset is received as a XCM asset. Finally, +//! AssetTypeId holds a mapping from AssetType -> AssetId. +//! +//! This pallet has three extrinsics: register_asset, which registers an Asset in this pallet and +//! creates the asset as dictated by the AssetRegistrar trait. set_asset_units_per_second: which +//! sets the unit per second that should be charged for a particular asset. +//! change_existing_asset_type: which allows to update the correspondence between AssetId and +//! AssetType +#![cfg_attr(not(feature = "std"), no_std)] +use frame_support::pallet; +pub use pallet::*; +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod benchmarks; +pub mod migrations; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +pub mod tests; +pub mod weights; + +#[pallet] +pub mod pallet { + + use crate::weights::WeightInfo; + use frame_support::{pallet_prelude::*, PalletId}; + use frame_system::pallet_prelude::*; + use parallel_primitives as primitives; + use parity_scale_codec::HasCompact; + use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned}; + use sp_std::vec::Vec; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + /// The AssetManagers's pallet id + pub const PALLET_ID: PalletId = PalletId(*b"asstmngr"); + + // The registrar trait. We need to comply with this + pub trait AssetRegistrar { + // How to create an asset + fn create_asset( + asset: T::AssetId, + min_balance: T::Balance, + metadata: T::AssetRegistrarMetadata, + // Wether or not an asset-receiving account increments the sufficient counter + is_sufficient: bool, + ) -> DispatchResult; + } + + // We implement this trait to be able to get the AssetType and units per second registered + impl primitives::xcm_gadget::AssetTypeGetter for Pallet { + fn get_asset_type(asset_id: T::AssetId) -> Option { + AssetIdType::::get(asset_id) + } + + fn get_asset_id(asset_type: T::AssetType) -> Option { + AssetTypeId::::get(asset_type) + } + } + + impl primitives::xcm_gadget::UnitsToWeightRatio for Pallet { + fn payment_is_supported(asset_type: T::AssetType) -> bool { + SupportedFeePaymentAssets::::get() + .binary_search(&asset_type) + .is_ok() + } + fn get_units_per_second(asset_type: T::AssetType) -> Option { + AssetTypeUnitsPerSecond::::get(asset_type) + } + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + + /// The Asset Id. This will be used to register the asset in Assets + type AssetId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen; + + /// The Asset Metadata we want to store + type AssetRegistrarMetadata: Member + Parameter + Default; + + /// The Asset Kind. + type AssetType: Parameter + Member + Ord + PartialOrd + Into + Default; + + /// The units in which we record balances. + type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen; + + /// The trait we use to register Assets + type AssetRegistrar: AssetRegistrar; + + /// Origin that is allowed to create and modify asset information + type AssetModifierOrigin: EnsureOrigin; + + type WeightInfo: WeightInfo; + } + + /// An error that can occur while executing the mapping pallet's logic. + #[pallet::error] + pub enum Error { + ErrorCreatingAsset, + AssetAlreadyExists, + AssetDoesNotExist, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// New asset with the asset manager is registered + AssetRegistered { + asset_id: T::AssetId, + asset: T::AssetType, + }, + /// Changed the amount of units we are charging per execution second for a given asset + UnitsPerSecondUpdated { + asset_type: T::AssetType, + units_per_second: u128, + }, + /// Changed the xcm type mapping for a given asset id + AssetTypeUpdated { + asset_id: T::AssetId, + new_asset_type: T::AssetType, + }, + /// Removed all information related to an assetId + AssetRemoved { + asset_id: T::AssetId, + asset_type: T::AssetType, + }, + /// Supported asset type for fee payment removed + SupportedAssetRemoved { asset_type: T::AssetType }, + } + + /// Mapping from an asset id to asset type. + /// This is mostly used when receiving transaction specifying an asset directly, + /// like transferring an asset from this chain to another. + #[pallet::storage] + #[pallet::getter(fn asset_id_type)] + pub type AssetIdType = StorageMap<_, Blake2_128Concat, T::AssetId, T::AssetType>; + + /// Reverse mapping of AssetIdType. Mapping from an asset type to an asset id. + /// This is mostly used when receiving a multilocation XCM message to retrieve + /// the corresponding asset in which tokens should me minted. + #[pallet::storage] + #[pallet::getter(fn asset_type_id)] + pub type AssetTypeId = StorageMap<_, Blake2_128Concat, T::AssetType, T::AssetId>; + + /// Stores the units per second for local execution for a AssetType. + /// This is used to know how to charge for XCM execution in a particular + /// asset + /// Not all assets might contain units per second, hence the different storage + #[pallet::storage] + #[pallet::getter(fn asset_type_units_per_second)] + pub type AssetTypeUnitsPerSecond = + StorageMap<_, Blake2_128Concat, T::AssetType, u128>; + + // Supported fee asset payments + #[pallet::storage] + #[pallet::getter(fn supported_fee_payment_assets)] + pub type SupportedFeePaymentAssets = StorageValue<_, Vec, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Register new asset with the asset manager + #[pallet::weight(T::WeightInfo::register_asset())] + pub fn register_asset( + origin: OriginFor, + asset: T::AssetType, + asset_id: Option, + metadata: Option, + min_amount: T::Balance, + is_sufficient: bool, + ) -> DispatchResult { + T::AssetModifierOrigin::ensure_origin(origin)?; + + let asset_exist = asset_id.is_some(); + let asset_id: T::AssetId = match asset_id { + Some(asset_id) => asset_id, + None => asset.clone().into(), + }; + ensure!( + AssetIdType::::get(&asset_id).is_none(), + Error::::AssetAlreadyExists + ); + if !asset_exist && metadata.is_some() { + T::AssetRegistrar::create_asset( + asset_id, + min_amount, + metadata.unwrap(), + is_sufficient, + ) + .map_err(|_| Error::::ErrorCreatingAsset)?; + } + AssetIdType::::insert(&asset_id, &asset); + AssetTypeId::::insert(&asset, &asset_id); + + Self::deposit_event(Event::AssetRegistered { asset_id, asset }); + Ok(()) + } + + /// Change the amount of units we are charging per execution second for a given AssetType + #[pallet::weight(T::WeightInfo::set_asset_units_per_second())] + pub fn set_asset_units_per_second( + origin: OriginFor, + asset_type: T::AssetType, + units_per_second: u128, + ) -> DispatchResult { + T::AssetModifierOrigin::ensure_origin(origin)?; + + ensure!( + AssetTypeId::::get(&asset_type).is_some(), + Error::::AssetDoesNotExist + ); + + // Grab supported assets + let mut supported_assets = SupportedFeePaymentAssets::::get(); + + // Only if the asset is not supported we need to push it + if let Err(index) = supported_assets.binary_search(&asset_type) { + supported_assets.insert(index, asset_type.clone()); + SupportedFeePaymentAssets::::put(supported_assets); + } + + AssetTypeUnitsPerSecond::::insert(&asset_type, &units_per_second); + + Self::deposit_event(Event::UnitsPerSecondUpdated { + asset_type, + units_per_second, + }); + Ok(()) + } + + /// Change the xcm type mapping for a given assetId + /// We also change this if the previous units per second where pointing at the old + /// assetType + #[pallet::weight(T::WeightInfo::change_existing_asset_type())] + pub fn change_existing_asset_type( + origin: OriginFor, + asset_id: T::AssetId, + new_asset_type: T::AssetType, + ) -> DispatchResult { + T::AssetModifierOrigin::ensure_origin(origin)?; + + // Grab supported assets + let mut supported_assets = SupportedFeePaymentAssets::::get(); + + let previous_asset_type = + AssetIdType::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + + // Insert new asset type info + AssetIdType::::insert(&asset_id, &new_asset_type); + AssetTypeId::::insert(&new_asset_type, &asset_id); + + // Remove previous asset type info + AssetTypeId::::remove(&previous_asset_type); + + // Change AssetTypeUnitsPerSecond + if let Some(units) = AssetTypeUnitsPerSecond::::get(&previous_asset_type) { + // Only if the old asset is supported we need to remove it + if let Ok(index) = supported_assets.binary_search(&previous_asset_type) { + supported_assets.remove(index); + } + + // Only if the new asset is not supported we need to push it + if let Err(index) = supported_assets.binary_search(&new_asset_type) { + supported_assets.insert(index, new_asset_type.clone()); + } + + // Insert supported fee payment assets + SupportedFeePaymentAssets::::put(supported_assets); + + // Remove previous asset type info + AssetTypeUnitsPerSecond::::remove(&previous_asset_type); + AssetTypeUnitsPerSecond::::insert(&new_asset_type, units); + } + + Self::deposit_event(Event::AssetTypeUpdated { + asset_id, + new_asset_type, + }); + Ok(()) + } + + #[pallet::weight(T::WeightInfo::remove_supported_asset())] + pub fn remove_supported_asset( + origin: OriginFor, + asset_type: T::AssetType, + ) -> DispatchResult { + T::AssetModifierOrigin::ensure_origin(origin)?; + + // Grab supported assets + let mut supported_assets = SupportedFeePaymentAssets::::get(); + + // Only if the old asset is supported we need to remove it + if let Ok(index) = supported_assets.binary_search(&asset_type) { + supported_assets.remove(index); + } + + // Insert + SupportedFeePaymentAssets::::put(supported_assets); + + // Remove + AssetTypeUnitsPerSecond::::remove(&asset_type); + + Self::deposit_event(Event::SupportedAssetRemoved { asset_type }); + Ok(()) + } + + /// Remove a given assetId -> assetType association + #[pallet::weight(T::WeightInfo::remove_existing_asset_type())] + pub fn remove_existing_asset_type( + origin: OriginFor, + asset_id: T::AssetId, + ) -> DispatchResult { + T::AssetModifierOrigin::ensure_origin(origin)?; + + // Grab supported assets + let mut supported_assets = SupportedFeePaymentAssets::::get(); + + let asset_type = + AssetIdType::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + + // Remove from AssetIdType + AssetIdType::::remove(&asset_id); + // Remove from AssetTypeId + AssetTypeId::::remove(&asset_type); + // Remove previous asset type units per second + AssetTypeUnitsPerSecond::::remove(&asset_type); + + // Only if the old asset is supported we need to remove it + if let Ok(index) = supported_assets.binary_search(&asset_type) { + supported_assets.remove(index); + } + + // Insert + SupportedFeePaymentAssets::::put(supported_assets); + + Self::deposit_event(Event::AssetRemoved { + asset_id, + asset_type, + }); + Ok(()) + } + } + + impl Pallet { + /// The account ID of AssetManager + pub fn account_id() -> T::AccountId { + PALLET_ID.into_account() + } + } +} diff --git a/pallets/asset-manager/src/migrations.rs b/pallets/asset-manager/src/migrations.rs new file mode 100644 index 000000000..47b726529 --- /dev/null +++ b/pallets/asset-manager/src/migrations.rs @@ -0,0 +1,557 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{AssetIdType, AssetTypeId, AssetTypeUnitsPerSecond, Config, SupportedFeePaymentAssets}; +use frame_support::{ + pallet_prelude::PhantomData, + storage::migration::storage_key_iter, + traits::{Get, OnRuntimeUpgrade}, + weights::Weight, + Blake2_128Concat, +}; +use sp_std::convert::TryInto; +//TODO sometimes this is unused, sometimes its necessary +use sp_std::vec::Vec; +use xcm::latest::prelude::*; + +/// Migration that changes the mapping AssetId -> units_per_second to +/// a mapping of the form AssetType -> units_per_second +/// It does so by removing the AssetTypeUnitsPerSecond storage and +/// populating the new AssetTypeUnitsPerSecond +pub struct UnitsWithAssetType(PhantomData); +impl OnRuntimeUpgrade for UnitsWithAssetType { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdUnitsPerSecond"; + + // We want to test that: + // There are no entries in the new storage beforehand + // The same number of mappings exist before and after + // As long as there are some mappings stored, one representative key maps to the + // same value after the migration. + // There are no entries in the old storage afterward + + // Assert new storage is empty + assert!(AssetTypeUnitsPerSecond::::iter().next().is_none()); + + // Check number of entries, and set it aside in temp storage + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + let mapping_count = stored_data.len(); + Self::set_temp_storage(mapping_count as u32, "mapping_count"); + + // Read an example pair from old storage and set it aside in temp storage + if mapping_count > 0 { + let example_pair = stored_data + .iter() + .next() + .expect("We already confirmed that there was at least one item stored"); + + Self::set_temp_storage(example_pair, "example_pair"); + } + + Ok(()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: "UnitsWithAssetType", "actually running it"); + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdUnitsPerSecond"; + + // Read all the data into memory. + // https://crates.parity.io/frame_support/storage/migration/fn.storage_key_iter.html + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .drain() + .collect(); + + let migrated_count: Weight = stored_data + .len() + .try_into() + .expect("There are between 0 and 2**64 mappings stored."); + + log::info!(target: "UnitsWithAssetType", "Migrating {:?} elements", migrated_count); + + // Write to the new storage with removed and added fields + for (asset_id, units) in stored_data { + // Read the assetType for the assetId + if let Some(asset_type) = AssetIdType::::get(&asset_id) { + // Populate with assetType as key + AssetTypeUnitsPerSecond::::insert(&asset_type, units) + } + } + + log::info!(target: "UnitsWithAssetType", "almost done"); + + // Return the weight used. For each migrated mapping there is a read to get it into + // memory, a read to get assetType and + // a write to clear the old stored value, and a write to re-store it. + let db_weights = T::DbWeight::get(); + migrated_count.saturating_mul(2 * db_weights.write + 2 * db_weights.read) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdUnitsPerSecond"; + // Assert that old storage is empty + assert!(storage_key_iter::( + pallet_prefix, + storage_item_prefix + ) + .next() + .is_none()); + + // Check number of entries matches what was set aside in pre_upgrade + let old_mapping_count: u64 = Self::get_temp_storage("mapping_count") + .expect("We stored a mapping count; it should be there; qed"); + let new_mapping_count = AssetTypeUnitsPerSecond::::iter().count() as u64; + assert_eq!(old_mapping_count, new_mapping_count); + + // Check that our example pair is still well-mapped after the migration + if new_mapping_count > 0 { + let (asset_id, units): (T::AssetId, u128) = + Self::get_temp_storage("example_pair").expect("qed"); + + let asset_type = + AssetIdType::::get(asset_id).expect("AssetIdType should have the assetType"); + + let migrated_units = AssetTypeUnitsPerSecond::::get(asset_type).expect("qed"); + // Check units are identical + assert_eq!(migrated_units, units); + } + + Ok(()) + } +} + +/// Migration that reads data from the AssetIdType mapping (AssetId -> AssetType) +/// and populates the reverse mapping AssetTypeId (AssetType -> AssetId) +pub struct PopulateAssetTypeIdStorage(PhantomData); +impl OnRuntimeUpgrade for PopulateAssetTypeIdStorage { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + + // We want to test that: + // The new storage item is empty + // The same number of mappings exist before and after + // As long as there are some mappings stored, + // there will exist the reserve mapping in the new storage + + // Assert new storage is empty + assert!(AssetTypeId::::iter().next().is_none()); + + // Check number of entries, and set it aside in temp storage + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + let mapping_count = stored_data.len(); + Self::set_temp_storage(mapping_count as u32, "mapping_count"); + + // Read an example pair from old storage and set it aside in temp storage + if mapping_count > 0 { + let example_pair = stored_data + .iter() + .next() + .expect("We already confirmed that there was at least one item stored"); + + Self::set_temp_storage(example_pair, "example_pair"); + } + + Ok(()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: "PopulateAssetTypeIdStorage", "actually running it"); + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + + // Read all the data into memory. + // https://crates.parity.io/frame_support/storage/migration/fn.storage_key_iter.html + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + + let migrated_count: Weight = stored_data + .len() + .try_into() + .expect("There are between 0 and 2**64 mappings stored."); + + log::info!( + target: "PopulateAssetTypeIdStorage", + "Migrating {:?} elements", + migrated_count + ); + + // Write to the new storage + for (asset_id, asset_type) in stored_data { + // Populate with assetType as key + AssetTypeId::::insert(&asset_type, asset_id) + } + + log::info!(target: "PopulateAssetTypeIdStorage", "almost done"); + + // Return the weight used. For each migrated mapping there is a read to get it into + // memory, and a write to populate the new storage. + let db_weights = T::DbWeight::get(); + migrated_count.saturating_mul(db_weights.write + db_weights.read) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + // Check number of entries matches what was set aside in pre_upgrade + let mapping_count: u64 = Self::get_temp_storage("mapping_count") + .expect("We stored a mapping count; it should be there; qed"); + let new_mapping_count = AssetTypeId::::iter().count() as u64; + assert_eq!(mapping_count, new_mapping_count); + + // Check that our example pair is still well-mapped after the migration + if new_mapping_count > 0 { + let (asset_id, asset_type): (T::AssetId, T::AssetType) = + Self::get_temp_storage("example_pair").expect("qed"); + + let stored_asset_id = + AssetTypeId::::get(asset_type).expect("AssetTypeId should have the assetId"); + + // Check assetIds are identical + assert_eq!(asset_id, stored_asset_id); + } + + Ok(()) + } +} + +/// Migration that reads the existing AssetTypes looking for old Statemine prefixes of +/// the form (Parachain, GeneralIndex) and changes them for the new prefix +/// (Parachain, PalletInstance, GeneralIndex) +pub struct ChangeStateminePrefixes( + PhantomData<(T, StatemineParaIdInfo, StatemineAssetsInstanceInfo)>, +); +impl OnRuntimeUpgrade + for ChangeStateminePrefixes +where + T: Config, + StatemineParaIdInfo: Get, + StatemineAssetsInstanceInfo: Get, + T::AssetType: Into> + From, +{ + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + + // We want to test that: + // If there exists an assetType matching the Statemine, it gets overwritten + + // Check number of entries, and set it aside in temp storage + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + + let statemine_para_id = StatemineParaIdInfo::get(); + + let mut found = false; + + for (asset_id, asset_type) in stored_data { + let location: Option = asset_type.clone().into(); + match location { + Some(MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), GeneralIndex(_)), + }) if para_id == statemine_para_id => { + // We are going to note that we found at least one entry matching + found = true; + // And we are going to record its data + Self::set_temp_storage((asset_id, asset_type), "example_pair"); + break; + } + _ => continue, + } + } + Self::set_temp_storage(found, "matching_type_found"); + + Ok(()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: "ChangeStateminePrefixes", "actually running it"); + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + + // Read all the data into memory. + // https://crates.parity.io/frame_support/storage/migration/fn.storage_key_iter.html + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + + let read_count: Weight = stored_data + .len() + .try_into() + .expect("There are between 0 and 2**64 mappings stored."); + + log::info!(target: "ChangeStateminePrefixes", "Evaluating {:?} elements", read_count); + + let db_weights = T::DbWeight::get(); + + let mut used_weight = read_count.saturating_mul(db_weights.read); + let statemine_para_id = StatemineParaIdInfo::get(); + let statemine_assets_pallet = StatemineAssetsInstanceInfo::get(); + // Write to the new storage + for (asset_id, asset_type) in stored_data { + let location: Option = asset_type.clone().into(); + match location { + Some(MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), GeneralIndex(index)), + }) if para_id == statemine_para_id => { + let new_location = MultiLocation { + parents: 1, + interior: X3( + Parachain(para_id), + PalletInstance(statemine_assets_pallet), + GeneralIndex(index), + ), + }; + let new_asset_type: T::AssetType = new_location.into(); + // Insert new asset type previous asset type + AssetIdType::::insert(&asset_id, &new_asset_type); + + // This is checked in case AssetManagerPopulateAssetTypeIdStorage runs first + if AssetTypeId::::get(&asset_type) == Some(asset_id) { + // We need to update AssetTypeId too + AssetTypeId::::remove(&asset_type); + AssetTypeId::::insert(&new_asset_type, asset_id); + + // Update weight due to this branch + used_weight = used_weight.saturating_add(2 * db_weights.write); + } + + // This is checked in case UnitsWithAssetType runs first + if let Some(units) = AssetTypeUnitsPerSecond::::take(&asset_type) { + // We need to update AssetTypeUnitsPerSecond too + AssetTypeUnitsPerSecond::::insert(&new_asset_type, units); + + // Update weight due to this branch + used_weight = used_weight.saturating_add(2 * db_weights.write); + } + + // Update used weight + used_weight = + used_weight.saturating_add(db_weights.write + 2 * db_weights.read); + } + _ => continue, + } + } + + log::info!(target: "ChangeStateminePrefixes", "almost done"); + + // Return the weight used. + used_weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + // Check if we found a matching type + let found: bool = Self::get_temp_storage("matching_type_found") + .expect("We stored a matching_type_found and should be here; qed"); + + let statemine_para_id = StatemineParaIdInfo::get(); + let statemine_assets_pallet = StatemineAssetsInstanceInfo::get(); + + // Check that our example pair suffered the correct migration + if found { + let (asset_id, asset_type): (T::AssetId, T::AssetType) = + Self::get_temp_storage("example_pair").expect("qed"); + let location: Option = asset_type.into(); + + match location { + Some(MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), GeneralIndex(index)), + }) if para_id == statemine_para_id => { + let stored_asset_type = + AssetIdType::::get(asset_id).expect("This entry should be updated"); + + let expected_new_asset_type: T::AssetType = MultiLocation { + parents: 1, + interior: X3( + Parachain(para_id), + PalletInstance(statemine_assets_pallet), + GeneralIndex(index), + ), + } + .into(); + + // Check assetTypes are identical + assert_eq!(stored_asset_type, expected_new_asset_type); + } + _ => panic!("This should never have entered this path"), + } + } + + Ok(()) + } +} + +pub struct PopulateSupportedFeePaymentAssets(PhantomData); +impl OnRuntimeUpgrade for PopulateSupportedFeePaymentAssets { + fn on_runtime_upgrade() -> Weight { + log::trace!( + target: "PopulateSupportedFeePaymentAssets", + "Running PopulateSupportedFeePaymentAssets migration" + ); + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetTypeUnitsPerSecond"; + + log::trace!( + target: "PopulateSupportedFeePaymentAssets", + "grabbing from AssetTypeUnitsPerSecond" + ); + + // Read all the data into memory. + // https://crates.parity.io/frame_support/storage/migration/fn.storage_key_iter.html + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + + let migrated_count: Weight = stored_data + .len() + .try_into() + .expect("There are between 0 and 2**64 mappings stored."); + + log::trace!( + target: "PopulateSupportedFeePaymentAssets", + "PopulateSupportedFeePaymentAssets pushing {:?} elements to SupportedFeePaymentAssets", + migrated_count + ); + + // Collect in a vec + let mut supported_assets: Vec = Vec::new(); + for (asset_type, _) in stored_data { + supported_assets.push(asset_type); + } + + supported_assets.sort(); + + // Push value + SupportedFeePaymentAssets::::put(&supported_assets); + + log::trace!( + target: "PopulateSupportedFeePaymentAssets", + "SupportedFeePaymentAssets populated now having {:?} elements", + supported_assets.len() + ); + + // Return the weight used. For each migrated mapping there is a read to get it into + // memory + // A final one write makes it push to the new storage item + let db_weights = T::DbWeight::get(); + let weight = migrated_count.saturating_mul(db_weights.read); + weight.saturating_add(db_weights.write) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetTypeUnitsPerSecond"; + + // We want to test that: + // There are no entries in the new storage beforehand + // The same number of mappings exist before and after + // As long as there are some mappings stored, one representative key maps to the + // same value after the migration. + // There are no entries in the old storage afterward + + // Assert new storage is empty + // Because the pallet and item prefixes are the same, the old storage is still at this + // key. However, the values can't be decoded so the assertion passes. + assert!(SupportedFeePaymentAssets::::get().len() == 0); + + // Check number of entries, and set it aside in temp storage + let stored_data: Vec<_> = storage_key_iter::( + pallet_prefix, + storage_item_prefix, + ) + .collect(); + let mapping_count = stored_data.len(); + Self::set_temp_storage(mapping_count as u64, "mapping_count"); + + // Read an example pair from old storage and set it aside in temp storage + if mapping_count > 0 { + let example_key = stored_data + .iter() + .next() + .expect("We already confirmed that there was at least one item stored") + .clone() + .0; + + Self::set_temp_storage(example_key, "example_pair"); + } + + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + + // Check number of entries matches what was set aside in pre_upgrade + let old_mapping_count: u64 = Self::get_temp_storage("mapping_count") + .expect("We stored a mapping count; it should be there; qed"); + let new_mapping_count = SupportedFeePaymentAssets::::get().len() as u64; + assert_eq!(old_mapping_count, new_mapping_count); + + // Check that our example pair is still well-mapped after the migration + if new_mapping_count > 0 { + let asset_type: T::AssetType = Self::get_temp_storage("example_pair").expect("qed"); + let migrated_info = SupportedFeePaymentAssets::::get(); + // Check that the asset_id exists in migrated_info + assert!(migrated_info.contains(&asset_type)); + } + + Ok(()) + } +} diff --git a/pallets/asset-manager/src/mock.rs b/pallets/asset-manager/src/mock.rs new file mode 100644 index 000000000..87afa45e8 --- /dev/null +++ b/pallets/asset-manager/src/mock.rs @@ -0,0 +1,200 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate as pallet_asset_manager; +use parity_scale_codec::{Decode, Encode}; + +use frame_support::{construct_runtime, parameter_types, traits::Everything, RuntimeDebug}; +use frame_system::EnsureRoot; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::traits::Hash as THash; +use sp_runtime::DispatchError; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use xcm::latest::prelude::*; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + AssetManager: pallet_asset_manager::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const AssetDeposit: u64 = 1; + pub const ApprovalDeposit: u64 = 1; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: u64 = 1; + pub const MetadataDepositPerByte: u64 = 1; +} + +parameter_types! { + pub const StatemineParaIdInfo: u32 = 1000u32; + pub const StatemineAssetsInstanceInfo: u8 = 50u8; +} + +pub type AssetId = u32; +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum MockAssetType { + MockAsset(AssetId), + Xcm(MultiLocation), +} + +impl Default for MockAssetType { + fn default() -> Self { + Self::MockAsset(0) + } +} + +impl From for AssetId { + fn from(asset: MockAssetType) -> AssetId { + match asset { + MockAssetType::MockAsset(id) => id, + MockAssetType::Xcm(id) => { + let mut result: [u8; 4] = [0u8; 4]; + let hash: H256 = id.using_encoded(::Hashing::hash); + result.copy_from_slice(&hash.as_fixed_bytes()[0..4]); + u32::from_le_bytes(result) + } + } + } +} + +impl From for MockAssetType { + fn from(location: MultiLocation) -> Self { + Self::Xcm(location) + } +} + +impl Into> for MockAssetType { + fn into(self) -> Option { + match self { + Self::Xcm(location) => Some(location), + _ => None, + } + } +} + +pub struct MockAssetPalletRegistrar; + +impl AssetRegistrar for MockAssetPalletRegistrar { + fn create_asset( + _asset: u32, + min_balance: u64, + _metadata: u32, + _is_sufficient: bool, + ) -> Result<(), DispatchError> { + if min_balance == 0 { + return Err(DispatchError::from(Error::::ErrorCreatingAsset)); + } + Ok(()) + } +} + +impl Config for Test { + type Event = Event; + type Balance = u64; + type AssetId = u32; + type AssetRegistrarMetadata = u32; + type AssetType = MockAssetType; + type AssetRegistrar = MockAssetPalletRegistrar; + type AssetModifierOrigin = EnsureRoot; + type WeightInfo = (); +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let Event::AssetManager(inner) = e { + Some(inner) + } else { + None + } + }) + .collect::>() +} + +pub fn expect_events(e: Vec>) { + assert_eq!(events(), e); +} diff --git a/pallets/asset-manager/src/tests.rs b/pallets/asset-manager/src/tests.rs new file mode 100644 index 000000000..5c24163ec --- /dev/null +++ b/pallets/asset-manager/src/tests.rs @@ -0,0 +1,731 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for AssetManager Pallet +use crate::*; +use mock::*; + +use frame_support::{ + assert_noop, assert_ok, + storage::migration::{put_storage_value, storage_key_iter}, + Blake2_128Concat, +}; +use xcm::latest::prelude::*; + +#[test] +fn registering_works() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_eq!( + AssetManager::asset_id_type(1).unwrap(), + MockAssetType::MockAsset(1) + ); + assert_eq!( + AssetManager::asset_type_id(MockAssetType::MockAsset(1)).unwrap(), + 1 + ); + expect_events(vec![crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }]) + }); +} + +#[test] +fn test_asset_exists_error() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_eq!( + AssetManager::asset_id_type(1).unwrap(), + MockAssetType::MockAsset(1) + ); + assert_noop!( + AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + ), + Error::::AssetAlreadyExists + ); + }); +} + +#[test] +fn test_asset_create_failed_error() { + new_test_ext().execute_with(|| { + assert_noop!( + AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 0u32.into(), + true + ), + Error::::ErrorCreatingAsset + ); + }); +} + +#[test] +fn test_root_can_change_units_per_second() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + )); + + assert_eq!( + AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), + 200 + ); + assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); + + expect_events(vec![ + crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }, + crate::Event::UnitsPerSecondUpdated { + asset_type: MockAssetType::MockAsset(1), + units_per_second: 200, + }, + ]) + }); +} + +#[test] +fn test_regular_user_cannot_call_extrinsics() { + new_test_ext().execute_with(|| { + assert_noop!( + AssetManager::register_asset( + Origin::signed(1), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + ), + sp_runtime::DispatchError::BadOrigin + ); + + assert_noop!( + AssetManager::set_asset_units_per_second( + Origin::signed(1), + MockAssetType::MockAsset(1), + 200u128.into(), + ), + sp_runtime::DispatchError::BadOrigin + ); + + assert_noop!( + AssetManager::change_existing_asset_type( + Origin::signed(1), + 1, + MockAssetType::MockAsset(2), + ), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_root_can_change_asset_id_type() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + )); + + assert_ok!(AssetManager::change_existing_asset_type( + Origin::root(), + 1, + MockAssetType::MockAsset(2), + )); + + // New one contains the new asset type units per second + assert_eq!( + AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(2)).unwrap(), + 200 + ); + + // Old one does not contain units per second + assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); + + // New associations are stablished + assert_eq!( + AssetManager::asset_id_type(1).unwrap(), + MockAssetType::MockAsset(2) + ); + assert_eq!( + AssetManager::asset_type_id(MockAssetType::MockAsset(2)).unwrap(), + 1 + ); + + // Old ones are deleted + assert!(AssetManager::asset_type_id(MockAssetType::MockAsset(1)).is_none()); + + expect_events(vec![ + crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }, + crate::Event::UnitsPerSecondUpdated { + asset_type: MockAssetType::MockAsset(1), + units_per_second: 200, + }, + crate::Event::AssetTypeUpdated { + asset_id: 1, + new_asset_type: MockAssetType::MockAsset(2), + }, + ]) + }); +} + +#[test] +fn test_change_units_per_second_after_setting_it_once() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true, + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + )); + + assert_eq!( + AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), + 200 + ); + assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 100u128.into(), + )); + + assert_eq!( + AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), + 100 + ); + assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); + + expect_events(vec![ + crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }, + crate::Event::UnitsPerSecondUpdated { + asset_type: MockAssetType::MockAsset(1), + units_per_second: 200, + }, + crate::Event::UnitsPerSecondUpdated { + asset_type: MockAssetType::MockAsset(1), + units_per_second: 100, + }, + ]); + }); +} + +#[test] +fn test_root_can_change_units_per_second_and_then_remove() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true, + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + )); + + assert_eq!( + AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), + 200 + ); + assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); + + assert_ok!(AssetManager::remove_supported_asset( + Origin::root(), + MockAssetType::MockAsset(1), + )); + + assert!( + !AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1)) + ); + + expect_events(vec![ + crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }, + crate::Event::UnitsPerSecondUpdated { + asset_type: MockAssetType::MockAsset(1), + units_per_second: 200, + }, + crate::Event::SupportedAssetRemoved { + asset_type: MockAssetType::MockAsset(1), + }, + ]); + }); +} + +#[test] +fn test_weight_hint_error() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true, + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + )); + + assert_ok!(AssetManager::remove_supported_asset( + Origin::root(), + MockAssetType::MockAsset(1) + )); + }); +} + +#[test] +fn test_asset_id_non_existent_error() { + new_test_ext().execute_with(|| { + assert_noop!( + AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + ), + Error::::AssetDoesNotExist + ); + assert_noop!( + AssetManager::change_existing_asset_type( + Origin::root(), + 1, + MockAssetType::MockAsset(2), + ), + Error::::AssetDoesNotExist + ); + }); +} + +#[test] +fn test_populate_supported_fee_payment_assets_works() { + new_test_ext().execute_with(|| { + use frame_support::StorageHasher; + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetTypeUnitsPerSecond"; + use frame_support::traits::OnRuntimeUpgrade; + use parity_scale_codec::Encode; + + put_storage_value( + pallet_prefix, + storage_item_prefix, + &Blake2_128Concat::hash(&MockAssetType::MockAsset(1).encode()), + 10u128, + ); + + assert_noop!( + AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + ), + Error::::AssetDoesNotExist + ); + + assert!(AssetManager::supported_fee_payment_assets().len() == 0); + + // We run the migration + crate::migrations::PopulateSupportedFeePaymentAssets::::on_runtime_upgrade(); + + assert!(AssetManager::supported_fee_payment_assets().len() == 1); + assert!(AssetManager::supported_fee_payment_assets().contains(&MockAssetType::MockAsset(1))); + }); +} + +#[test] +fn test_asset_manager_units_with_asset_type_migration_works() { + new_test_ext().execute_with(|| { + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdUnitsPerSecond"; + use frame_support::traits::OnRuntimeUpgrade; + use frame_support::StorageHasher; + use parity_scale_codec::Encode; + + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + // We populate the previous storage with assetId as key + put_storage_value( + pallet_prefix, + storage_item_prefix, + &Blake2_128Concat::hash(&1u32.encode()), + 200u128, + ); + + // We run the migration + crate::migrations::UnitsWithAssetType::::on_runtime_upgrade(); + + // After migration, units per second should be indexed by AssetType + assert_eq!( + AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).unwrap(), + 200 + ); + + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + + // Assert that old storage is empty + assert!(storage_key_iter::( + pallet_prefix, + storage_item_prefix + ) + .next() + .is_none()); + }); +} + +#[test] +fn test_asset_manager_populate_asset_type_id_storage_migration_works() { + new_test_ext().execute_with(|| { + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + use frame_support::traits::OnRuntimeUpgrade; + use frame_support::StorageHasher; + use parity_scale_codec::Encode; + + // We populate AssetIdType manually + put_storage_value( + pallet_prefix, + storage_item_prefix, + &Blake2_128Concat::hash(&1u32.encode()), + MockAssetType::MockAsset(1), + ); + + // We run the migration + crate::migrations::PopulateAssetTypeIdStorage::::on_runtime_upgrade(); + + // After migration, the new storage item should be populated + assert_eq!( + AssetManager::asset_type_id(MockAssetType::MockAsset(1)).unwrap(), + 1 + ); + }); +} + +#[test] +fn test_asset_manager_change_statemine_prefixes() { + new_test_ext().execute_with(|| { + let pallet_prefix: &[u8] = b"AssetManager"; + let storage_item_prefix: &[u8] = b"AssetIdType"; + use frame_support::traits::OnRuntimeUpgrade; + use frame_support::StorageHasher; + use parity_scale_codec::Encode; + + let statemine_para_id = mock::StatemineParaIdInfo::get(); + let statemine_assets_pallet = mock::StatemineAssetsInstanceInfo::get(); + + let statemine_multilocation = MockAssetType::Xcm(MultiLocation { + parents: 1, + interior: X2(Parachain(statemine_para_id), GeneralIndex(1)), + }); + + let statemine_multilocation_2 = MockAssetType::Xcm(MultiLocation { + parents: 1, + interior: X2(Parachain(statemine_para_id), GeneralIndex(2)), + }); + + let statemine_multilocation_3 = MockAssetType::Xcm(MultiLocation { + parents: 1, + interior: X2(Parachain(statemine_para_id), GeneralIndex(3)), + }); + + let asset_id: mock::AssetId = statemine_multilocation.clone().into(); + + // We are gonna test three cases: + // Case 1: AssetManagerPopulateAssetTypeIdStorage has not executed yet + // (only AssetIdType is populated) + // Case 2: AssetManagerPopulateAssetTypeIdStorage has already executed + // Case 3: AssetManagerUnitsWithAssetType has already executed + + // To mimic case 1, we populate AssetIdType manually but not AssetTypeId + put_storage_value( + pallet_prefix, + storage_item_prefix, + &Blake2_128Concat::hash(&asset_id.encode()), + statemine_multilocation.clone(), + ); + + // Assert the storage item is well populated + assert_eq!( + AssetManager::asset_id_type(asset_id).unwrap(), + statemine_multilocation + ); + + // To mimic case 2, we can simply register the asset through the extrinsic + assert_ok!(AssetManager::register_asset( + Origin::root(), + statemine_multilocation_2.clone(), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + // To mimic case 3, we can simply register the asset through the extrinsic + // But we also need to set units per second + assert_ok!(AssetManager::register_asset( + Origin::root(), + statemine_multilocation_3.clone(), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + statemine_multilocation_3.clone(), + 1u128, + )); + + // We run the migration + crate::migrations::ChangeStateminePrefixes::< + Test, + mock::StatemineParaIdInfo, + mock::StatemineAssetsInstanceInfo, + >::on_runtime_upgrade(); + + // Check case 1 + let expected_statemine_multilocation = MockAssetType::Xcm(MultiLocation { + parents: 1, + interior: X3( + Parachain(statemine_para_id), + PalletInstance(statemine_assets_pallet), + GeneralIndex(1), + ), + }); + + // After migration, the storage item should have been upgraded + assert_eq!( + AssetManager::asset_id_type(asset_id).unwrap(), + expected_statemine_multilocation + ); + + // Check case 2 + let expected_statemine_multilocation_2 = MockAssetType::Xcm(MultiLocation { + parents: 1, + interior: X3( + Parachain(statemine_para_id), + PalletInstance(statemine_assets_pallet), + GeneralIndex(2), + ), + }); + + let asset_id_2: mock::AssetId = statemine_multilocation_2.clone().into(); + + // After migration, both storage items should have been upgraded + assert_eq!( + AssetManager::asset_id_type(asset_id_2).unwrap(), + expected_statemine_multilocation_2 + ); + + assert_eq!( + AssetManager::asset_type_id(expected_statemine_multilocation_2).unwrap(), + asset_id_2 + ); + + // And the previous one should be cleaned + assert!(AssetManager::asset_type_id(&statemine_multilocation_2).is_none()); + + // Check case 3 + let expected_statemine_multilocation_3 = MockAssetType::Xcm(MultiLocation { + parents: 1, + interior: X3( + Parachain(statemine_para_id), + PalletInstance(statemine_assets_pallet), + GeneralIndex(3), + ), + }); + + let asset_id_3: mock::AssetId = statemine_multilocation_3.clone().into(); + + // After migration, both storage items should have been upgraded + assert_eq!( + AssetManager::asset_id_type(asset_id_3).unwrap(), + expected_statemine_multilocation_3 + ); + + assert_eq!( + AssetManager::asset_type_id(&expected_statemine_multilocation_3).unwrap(), + asset_id_3 + ); + + // The previous one should be cleaned + assert!(AssetManager::asset_type_id(&statemine_multilocation_3).is_none()); + + // Units per second updated + assert_eq!( + AssetManager::asset_type_units_per_second(&expected_statemine_multilocation_3).unwrap(), + 1 + ); + assert!(AssetManager::asset_type_units_per_second(&statemine_multilocation_3).is_none()); + }); +} + +#[test] +fn test_root_can_remove_asset_association() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_ok!(AssetManager::set_asset_units_per_second( + Origin::root(), + MockAssetType::MockAsset(1), + 200u128.into(), + )); + + assert_ok!(AssetManager::remove_existing_asset_type(Origin::root(), 1,)); + + // Mappings are deleted + assert!(AssetManager::asset_type_id(MockAssetType::MockAsset(1)).is_none()); + assert!(AssetManager::asset_id_type(1).is_none()); + + // Units per second removed + assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); + + expect_events(vec![ + crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }, + crate::Event::UnitsPerSecondUpdated { + asset_type: MockAssetType::MockAsset(1), + units_per_second: 200, + }, + crate::Event::AssetRemoved { + asset_id: 1, + asset_type: MockAssetType::MockAsset(1), + }, + ]) + }); +} + +#[test] +fn test_removing_without_asset_units_per_second_does_not_panic() { + new_test_ext().execute_with(|| { + assert_ok!(AssetManager::register_asset( + Origin::root(), + MockAssetType::MockAsset(1), + None, + Some(0u32.into()), + 1u32.into(), + true + )); + + assert_ok!(AssetManager::remove_existing_asset_type(Origin::root(), 1,)); + + // Mappings are deleted + assert!(AssetManager::asset_type_id(MockAssetType::MockAsset(1)).is_none()); + assert!(AssetManager::asset_id_type(1).is_none()); + + // Units per second removed + assert!(AssetManager::asset_type_units_per_second(MockAssetType::MockAsset(1)).is_none()); + + expect_events(vec![ + crate::Event::AssetRegistered { + asset_id: 1, + asset: MockAssetType::MockAsset(1), + }, + crate::Event::AssetRemoved { + asset_id: 1, + asset_type: MockAssetType::MockAsset(1), + }, + ]) + }); +} diff --git a/pallets/asset-manager/src/weights.rs b/pallets/asset-manager/src/weights.rs new file mode 100644 index 000000000..eaeef6d2e --- /dev/null +++ b/pallets/asset-manager/src/weights.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_asset_manager +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-03-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("parallel"), DB CACHE: 1024 + +// Executed Command: +// target/release/parallel +// benchmark +// --chain=parallel +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet-asset-manager +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs +// --output=./pallets/asset-manager/src/weights.rs + +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::all)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_asset_manager. +pub trait WeightInfo { + fn register_asset() -> Weight; + fn set_asset_units_per_second() -> Weight; + fn change_existing_asset_type() -> Weight; + fn remove_supported_asset() -> Weight; + fn remove_existing_asset_type() -> Weight; +} + +/// Weights for pallet_asset_manager using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn register_asset() -> Weight { + (79_401_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + fn set_asset_units_per_second() -> Weight { + (40_601_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn change_existing_asset_type() -> Weight { + (52_401_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(8 as Weight)) + } + fn remove_supported_asset() -> Weight { + (33_100_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn remove_existing_asset_type() -> Weight { + (41_901_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn register_asset() -> Weight { + (79_401_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } + fn set_asset_units_per_second() -> Weight { + (40_601_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn change_existing_asset_type() -> Weight { + (52_401_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + } + fn remove_supported_asset() -> Weight { + (33_100_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn remove_existing_asset_type() -> Weight { + (41_901_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } +} diff --git a/pallets/crowdloans/Cargo.toml b/pallets/crowdloans/Cargo.toml index 2d12389b1..4fdae06c2 100644 --- a/pallets/crowdloans/Cargo.toml +++ b/pallets/crowdloans/Cargo.toml @@ -29,6 +29,7 @@ cumulus-pallet-xcmp-queue = { git = 'https://github.com/paritytech/cumulus kusama-runtime = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17', features = ['runtime-benchmarks'] } orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } +orml-traits = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } pallet-babe = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17' } pallet-balances = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17' } pallet-session = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17' } diff --git a/pallets/crowdloans/src/mock.rs b/pallets/crowdloans/src/mock.rs index e102a4e29..f81b8ebf6 100644 --- a/pallets/crowdloans/src/mock.rs +++ b/pallets/crowdloans/src/mock.rs @@ -11,6 +11,7 @@ use frame_support::{ PalletId, }; use frame_system::{EnsureRoot, EnsureSignedBy}; +use orml_traits::parameter_type_with_key; use orml_xcm_support::IsNativeConcrete; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::IsSystem; @@ -321,6 +322,12 @@ parameter_types! { pub const MaxAssetsForTransfer: usize = 2; } +parameter_type_with_key! { + pub ParachainMinFee: |_location: MultiLocation| -> u128 { + u128::MAX + }; +} + impl orml_xtokens::Config for Test { type Event = Event; type Balance = Balance; @@ -333,6 +340,7 @@ impl orml_xtokens::Config for Test { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/pallets/liquid-staking/Cargo.toml b/pallets/liquid-staking/Cargo.toml index a73312131..b38e89097 100644 --- a/pallets/liquid-staking/Cargo.toml +++ b/pallets/liquid-staking/Cargo.toml @@ -53,6 +53,7 @@ parachain-info = { git = 'https://github.com/paritytech/cumulus hex = '0.4.3' orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } +orml-traits = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git' } [features] default = ['std'] diff --git a/pallets/liquid-staking/src/mock.rs b/pallets/liquid-staking/src/mock.rs index f5c024b1c..fd27be289 100644 --- a/pallets/liquid-staking/src/mock.rs +++ b/pallets/liquid-staking/src/mock.rs @@ -11,6 +11,7 @@ use frame_support::{ PalletId, }; use frame_system::{EnsureRoot, EnsureSignedBy}; +use orml_traits::parameter_type_with_key; use orml_xcm_support::IsNativeConcrete; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::{IsSystem, Sibling}; @@ -269,6 +270,12 @@ parameter_types! { pub const MaxAssetsForTransfer: usize = 2; } +parameter_type_with_key! { + pub ParachainMinFee: |_location: MultiLocation| -> u128 { + u128::MAX + }; +} + impl orml_xtokens::Config for Test { type Event = Event; type Balance = Balance; @@ -281,6 +288,7 @@ impl orml_xtokens::Config for Test { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/pallets/xcm-helper/src/lib.rs b/pallets/xcm-helper/src/lib.rs index 2e27292f3..3eb79e82d 100644 --- a/pallets/xcm-helper/src/lib.rs +++ b/pallets/xcm-helper/src/lib.rs @@ -257,6 +257,13 @@ impl Pallet { message.0.insert(2, SetAppendix(report_error)); Ok(query_id) } + + pub fn get_xcm_weight_fee_to_sibling( + location: MultiLocation, + ) -> XcmWeightFeeMisc> { + let call = XcmCall::TransferToSiblingchain(Box::new(location)); + Self::xcm_weight_fee(call) + } } impl XcmHelper, AssetIdOf, AccountIdOf> for Pallet { diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index c9b025485..2ee554089 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -5,6 +5,7 @@ name = 'parallel-primitives' version = '1.8.1' [dependencies] +log = { version = "0.4", default-features = false } codec = { package = 'parity-scale-codec', version = '2.3.1', default-features = false } cumulus-primitives-core = { git = 'https://github.com/paritytech/cumulus.git', branch = 'polkadot-v0.9.17', default-features = false } frame-support = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17', default-features = false } @@ -20,6 +21,7 @@ sp-io = { git = 'https://github.com/paritytech/substrate.git', sp-runtime = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17', default-features = false } sp-std = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.17', default-features = false } xcm = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17', default-features = false } +xcm-builder = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17', default-features = false } xcm-executor = { git = 'https://github.com/paritytech/polkadot.git', branch = 'release-v0.9.17', default-features = false } [features] @@ -37,6 +39,7 @@ std = [ 'frame-support/std', 'xcm-executor/std', 'xcm/std', + 'xcm-builder/std', 'scale-info/std', 'cumulus-primitives-core/std', 'num-bigint/std', diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index 0affccad6..458abad80 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -56,6 +56,7 @@ pub struct MultiCurrencyAdapter< enum Error { /// Failed to match fungible. + #[allow(dead_code)] FailedToMatchFungible, /// `MultiLocation` to `AccountId` Conversion failed. AccountIdConversionFailed, @@ -163,8 +164,7 @@ impl< MultiCurrency::mint_into(currency_id, &who, amount) .map_err(|e| XcmError::FailedToTransactAsset(e.into())) } - // ignore unknown asset - _ => Ok(()), + _ => Err(XcmError::AssetNotFound), } } @@ -172,13 +172,14 @@ impl< asset: &MultiAsset, location: &MultiLocation, ) -> result::Result { + // throw AssetNotFound error here if not match in order to reach the next foreign transact in tuple + let amount: MultiCurrency::Balance = Match::matches_fungible(asset) + .ok_or(XcmError::AssetNotFound)? + .saturated_into(); let who = AccountIdConvert::convert_ref(location) .map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; let currency_id = CurrencyIdConvert::convert(asset.clone()) .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; - let amount: MultiCurrency::Balance = Match::matches_fungible(asset) - .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? - .saturated_into(); MultiCurrency::burn_from(currency_id, &who, amount) .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 81222dd4e..26d1f860d 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -18,6 +18,7 @@ pub mod currency; pub mod network; pub mod tokens; pub mod ump; +pub mod xcm_gadget; use codec::{Decode, Encode}; use frame_support::pallet_prelude::*; @@ -193,6 +194,15 @@ impl ConvertToBigUint for u128 { } } +/// Asset Registrar Metadata +#[derive(Clone, Default, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +pub struct AssetRegistrarMetadata { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub is_frozen: bool, +} + /// Get relaychain validation data pub trait ValidationDataProvider { fn validation_data() -> Option; diff --git a/primitives/src/ump.rs b/primitives/src/ump.rs index 11a3abcd4..c743b0535 100644 --- a/primitives/src/ump.rs +++ b/primitives/src/ump.rs @@ -6,6 +6,7 @@ use frame_system::Config; use scale_info::TypeInfo; use sp_runtime::{traits::StaticLookup, MultiSignature, RuntimeDebug}; use sp_std::{boxed::Box, vec::Vec}; +use xcm::latest::MultiLocation; /// A destination account for payment. #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -307,7 +308,7 @@ impl Default for XcmWeightFeeMisc { } } -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum XcmCall { Bond, BondExtra, @@ -318,6 +319,7 @@ pub enum XcmCall { Contribute, Withdraw, AddMemo, + TransferToSiblingchain(Box), } #[macro_export] diff --git a/primitives/src/xcm_gadget.rs b/primitives/src/xcm_gadget.rs new file mode 100644 index 000000000..061e05354 --- /dev/null +++ b/primitives/src/xcm_gadget.rs @@ -0,0 +1,383 @@ +// Copyright 2021 Parallel Finance Developer. +// This file is part of Parallel Finance. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +use crate::CurrencyId; +use codec::{Decode, Encode}; +use frame_support::{ + traits::{tokens::fungibles::Mutate, Get}, + weights::{constants::WEIGHT_PER_SECOND, Weight}, +}; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::traits::Convert; +use sp_runtime::traits::{BlakeTwo256, CheckedConversion, Hash as THash, Zero}; +use sp_std::{borrow::Borrow, vec::Vec}; +use sp_std::{convert::TryFrom, marker::PhantomData}; +use xcm::latest::prelude::*; +use xcm::latest::{ + AssetId as xcmAssetId, Error as XcmError, Fungibility, + Junction::{AccountId32, Parachain}, + MultiLocation, NetworkId, +}; +use xcm_builder::TakeRevenue; +use xcm_executor::traits::{FilterAssetLocation, MatchesFungible, MatchesFungibles, WeightTrader}; + +/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID +/// (must be `TryFrom/TryInto`) into a MultiLocation Value and Viceversa through +/// an intermediate generic type AssetType. +/// The trait bounds enforce is that the AssetTypeGetter trait is also implemented for +/// AssetIdInfoGetter +pub struct AsAssetType( + PhantomData<(AssetId, AssetType, AssetIdInfoGetter)>, +); +impl xcm_executor::traits::Convert + for AsAssetType +where + AssetId: Clone, + AssetType: From + Into> + Clone, + AssetIdInfoGetter: AssetTypeGetter, +{ + fn convert_ref(id: impl Borrow) -> Result { + if let Some(asset_id) = AssetIdInfoGetter::get_asset_id(id.borrow().clone().into()) { + Ok(asset_id) + } else { + Err(()) + } + } + fn reverse_ref(what: impl Borrow) -> Result { + if let Some(asset_type) = AssetIdInfoGetter::get_asset_type(what.borrow().clone()) { + if let Some(location) = asset_type.into() { + Ok(location) + } else { + Err(()) + } + } else { + Err(()) + } + } +} + +/// Instructs how to convert accountId into a MultiLocation +pub struct AccountIdToMultiLocation(sp_std::marker::PhantomData); +impl sp_runtime::traits::Convert + for AccountIdToMultiLocation +where + AccountId: Into<[u8; 32]>, +{ + fn convert(account: AccountId) -> MultiLocation { + MultiLocation { + parents: 0, + interior: X1(AccountId32 { + network: NetworkId::Any, + id: account.into(), + }), + } + } +} + +// We need to know how to charge for incoming assets +// This takes the first fungible asset, and takes whatever UnitPerSecondGetter establishes +// UnitsToWeightRatio trait, which needs to be implemented by AssetIdInfoGetter +pub struct FirstAssetTrader< + AssetType: From + Clone, + AssetIdInfoGetter: UnitsToWeightRatio, + R: TakeRevenue, +>( + Weight, + Option<(MultiLocation, u128, u128)>, + PhantomData<(AssetType, AssetIdInfoGetter, R)>, +); +impl< + AssetType: From + Clone, + AssetIdInfoGetter: UnitsToWeightRatio, + R: TakeRevenue, + > WeightTrader for FirstAssetTrader +{ + fn new() -> Self { + FirstAssetTrader(0, None, PhantomData) + } + fn buy_weight( + &mut self, + weight: Weight, + payment: xcm_executor::Assets, + ) -> Result { + let first_asset = payment + .fungible_assets_iter() + .next() + .ok_or(XcmError::TooExpensive)?; + + // We are only going to check first asset for now. This should be sufficient for simple token + // transfers. We will see later if we change this. + match (first_asset.id, first_asset.fun) { + (xcmAssetId::Concrete(id), Fungibility::Fungible(_)) => { + let asset_type: AssetType = id.clone().into(); + // Shortcut if we know the asset is not supported + // This involves the same db read per block, mitigating any attack based on + // non-supported assets + if !AssetIdInfoGetter::payment_is_supported(asset_type.clone()) { + return Err(XcmError::TooExpensive); + } + if let Some(units_per_second) = AssetIdInfoGetter::get_units_per_second(asset_type) + { + let amount = units_per_second.saturating_mul(weight as u128) + / (WEIGHT_PER_SECOND as u128); + + // We dont need to proceed if the amount is 0 + // For cases (specially tests) where the asset is very cheap with respect + // to the weight needed + if amount.is_zero() { + return Ok(payment); + } + + let required = MultiAsset { + fun: Fungibility::Fungible(amount), + id: xcmAssetId::Concrete(id.clone()), + }; + let unused = payment + .checked_sub(required) + .map_err(|_| XcmError::TooExpensive)?; + self.0 = self.0.saturating_add(weight); + + // In case the asset matches the one the trader already stored before, add + // to later refund + + // Else we are always going to substract the weight if we can, but we latter do + // not refund it + + // In short, we only refund on the asset the trader first succesfully was able + // to pay for an execution + let new_asset = match self.1.clone() { + Some((prev_id, prev_amount, units_per_second)) => { + if prev_id == id { + Some((id, prev_amount.saturating_add(amount), units_per_second)) + } else { + None + } + } + None => Some((id, amount, units_per_second)), + }; + + // Due to the trait bound, we can only refund one asset. + if let Some(new_asset) = new_asset { + self.0 = self.0.saturating_add(weight); + self.1 = Some(new_asset); + }; + Ok(unused) + } else { + Err(XcmError::TooExpensive) + } + } + _ => Err(XcmError::TooExpensive), + } + } + + fn refund_weight(&mut self, weight: Weight) -> Option { + if let Some((id, prev_amount, units_per_second)) = self.1.clone() { + let weight = weight.min(self.0); + self.0 -= weight; + let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); + self.1 = Some(( + id.clone(), + prev_amount.saturating_sub(amount), + units_per_second, + )); + Some(MultiAsset { + fun: Fungibility::Fungible(amount), + id: xcmAssetId::Concrete(id), + }) + } else { + None + } + } +} + +/// Deal with spent fees, deposit them as dictated by R +impl< + AssetType: From + Clone, + AssetIdInfoGetter: UnitsToWeightRatio, + R: TakeRevenue, + > Drop for FirstAssetTrader +{ + fn drop(&mut self) { + if let Some((id, amount, _)) = self.1.clone() { + R::take_revenue((id, amount).into()); + } + } +} + +pub trait Reserve { + /// Returns assets reserve location. + fn reserve(&self) -> Option; +} + +// Takes the chain part of a MultiAsset +impl Reserve for MultiAsset { + fn reserve(&self) -> Option { + if let xcmAssetId::Concrete(location) = self.id.clone() { + let first_interior = location.first_interior(); + let parents = location.parent_count(); + match (parents, first_interior) { + (0, Some(Parachain(id))) => Some(MultiLocation::new(0, X1(Parachain(*id)))), + (1, Some(Parachain(id))) => Some(MultiLocation::new(1, X1(Parachain(*id)))), + (1, _) => Some(MultiLocation::parent()), + _ => None, + } + } else { + None + } + } +} + +/// A `FilterAssetLocation` implementation. Filters multi native assets whose +/// reserve is same with `origin`. +pub struct MultiNativeAsset; +impl FilterAssetLocation for MultiNativeAsset { + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + if let Some(ref reserve) = asset.reserve() { + if reserve == origin { + return true; + } + } + false + } +} + +// Defines the trait to obtain a generic AssetType from a generic AssetId and viceversa +pub trait AssetTypeGetter { + // Get asset type from assetId + fn get_asset_type(asset_id: AssetId) -> Option; + + // Get assetId from assetType + fn get_asset_id(asset_type: AssetType) -> Option; +} + +// Defines the trait to obtain the units per second of a give asset_type for local execution +// This parameter will be used to charge for fees upon asset_type deposit +pub trait UnitsToWeightRatio { + // Whether payment in a particular asset_type is suppotrted + fn payment_is_supported(asset_type: AssetType) -> bool; + // Get units per second from asset type + fn get_units_per_second(asset_type: AssetType) -> Option; +} + +/// XCM fee depositor to which we implement the TakeRevenue trait +/// It receives a fungibles::Mutate implemented argument, a matcher to convert MultiAsset into +/// AssetId and amount, and the fee receiver account +pub struct XcmFeesToAccount( + PhantomData<(Assets, Matcher, AccountId, ReceiverAccount)>, +); +impl< + Assets: Mutate, + Matcher: MatchesFungibles, + AccountId: Clone, + ReceiverAccount: Get, + > TakeRevenue for XcmFeesToAccount +{ + fn take_revenue(revenue: MultiAsset) { + match Matcher::matches_fungibles(&revenue) { + Ok((asset_id, amount)) => { + if !amount.is_zero() { + let ok = Assets::mint_into(asset_id, &ReceiverAccount::get(), amount).is_ok(); + debug_assert!(ok, "`mint_into` cannot generally fail; qed"); + } + } + Err(_) => log::debug!( + target: "xcm", + "take revenue failed matching fungible" + ), + } + } +} + +// Multi IsConcrete Implementation. Allows us to route both pre and post 0.9.16 anchoring versions +// of our native token to the same currency +// The incoming MultiAsset is matched against a Vec of multilocations and returned Some +// if matches +pub struct MultiIsConcrete(PhantomData); +impl>, B: TryFrom> MatchesFungible for MultiIsConcrete { + fn matches_fungible(a: &MultiAsset) -> Option { + match (&a.id, &a.fun) { + (xcmAssetId::Concrete(ref id), Fungibility::Fungible(ref amount)) + if T::get().contains(id) => + { + CheckedConversion::checked_from(*amount) + } + _ => None, + } + } +} + +// Our AssetType. For now we only handle Xcm Assets +#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +pub enum AssetType { + Xcm(MultiLocation), +} + +impl Default for AssetType { + fn default() -> Self { + Self::Xcm(MultiLocation::here()) + } +} + +impl From for AssetType { + fn from(location: MultiLocation) -> Self { + Self::Xcm(location) + } +} + +impl From for Option { + fn from(asset: AssetType) -> Option { + match asset { + AssetType::Xcm(location) => Some(location), + } + } +} + +// Implementation on how to retrieve the AssetId from an AssetType +// We simply hash the AssetType and take the lowest 32 bits +impl From for CurrencyId { + fn from(asset: AssetType) -> CurrencyId { + match asset { + AssetType::Xcm(id) => { + let mut result: [u8; 4] = [0u8; 4]; + let hash: H256 = id.using_encoded(BlakeTwo256::hash); + result.copy_from_slice(&hash.as_fixed_bytes()[0..4]); + u32::from_le_bytes(result) + } + } + } +} + +// How to convert from CurrencyId to MultiLocation +pub struct CurrencyIdtoMultiLocation( + sp_std::marker::PhantomData<(LegacyAssetConverter, ForeignAssetConverter)>, +); +impl + sp_runtime::traits::Convert> + for CurrencyIdtoMultiLocation +where + LegacyAssetConverter: Convert>, + ForeignAssetConverter: xcm_executor::traits::Convert, +{ + fn convert(currency_id: CurrencyId) -> Option { + let mut multi_location = LegacyAssetConverter::convert(currency_id); + multi_location = match multi_location { + Some(_) => multi_location, + None => ForeignAssetConverter::reverse_ref(¤cy_id).ok(), + }; + multi_location + } +} diff --git a/runtime/heiko/Cargo.toml b/runtime/heiko/Cargo.toml index 62bfa81f9..4fcd05d61 100644 --- a/runtime/heiko/Cargo.toml +++ b/runtime/heiko/Cargo.toml @@ -89,6 +89,7 @@ orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-r orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', default-features = false } # Parallel dependencies +pallet-asset-manager = { path = '../../pallets/asset-manager', default-features = false } pallet-amm = { path = '../../pallets/amm', default-features = false } pallet-bridge = { path = '../../pallets/bridge', default-features = false } pallet-crowdloans = { path = '../../pallets/crowdloans', default-features = false } @@ -135,6 +136,7 @@ runtime-benchmarks = [ 'pallet-router/runtime-benchmarks', 'pallet-xcm-helper/runtime-benchmarks', 'pallet-farming/runtime-benchmarks', + 'pallet-asset-manager/runtime-benchmarks', ] std = [ 'codec/std', @@ -212,6 +214,7 @@ std = [ 'pallet-emergency-shutdown/std', 'pallet-xcm-helper/std', 'pallet-farming/std', + 'pallet-asset-manager/std', ] try-runtime = [ 'frame-support/try-runtime', @@ -246,4 +249,4 @@ try-runtime = [ 'pallet-payroll/try-runtime', 'pallet-prices/try-runtime', 'pallet-xcm-helper/try-runtime', -] \ No newline at end of file +] diff --git a/runtime/heiko/src/lib.rs b/runtime/heiko/src/lib.rs index 72f26a9c4..3af612280 100644 --- a/runtime/heiko/src/lib.rs +++ b/runtime/heiko/src/lib.rs @@ -35,7 +35,7 @@ use frame_support::{ PalletId, }; -use orml_traits::{DataProvider, DataProviderExtended}; +use orml_traits::{parameter_type_with_key, DataProvider, DataProviderExtended}; use polkadot_runtime_common::SlowAdjustingFeeUpdate; use sp_api::impl_runtime_apis; use sp_core::{ @@ -520,6 +520,12 @@ parameter_types! { pub const MaxAssetsForTransfer: usize = 2; } +parameter_type_with_key! { + pub ParachainMinFee: |_location: MultiLocation| -> u128 { + u128::MAX + }; +} + impl orml_xtokens::Config for Runtime { type Event = Event; type Balance = Balance; @@ -532,6 +538,7 @@ impl orml_xtokens::Config for Runtime { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; } parameter_types! { diff --git a/runtime/kerria/Cargo.toml b/runtime/kerria/Cargo.toml index b2d47ea39..545ffbfaf 100644 --- a/runtime/kerria/Cargo.toml +++ b/runtime/kerria/Cargo.toml @@ -89,6 +89,7 @@ orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-r orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', default-features = false } # Parallel dependencies +pallet-asset-manager = { path = '../../pallets/asset-manager', default-features = false } pallet-amm = { path = '../../pallets/amm', default-features = false } pallet-bridge = { path = '../../pallets/bridge', default-features = false } pallet-crowdloans = { path = '../../pallets/crowdloans', default-features = false } @@ -135,6 +136,7 @@ runtime-benchmarks = [ 'pallet-crowdloans/runtime-benchmarks', 'pallet-xcm-helper/runtime-benchmarks', 'pallet-farming/runtime-benchmarks', + 'pallet-asset-manager/runtime-benchmarks', ] std = [ 'codec/std', @@ -213,6 +215,7 @@ std = [ 'pallet-emergency-shutdown/std', 'pallet-xcm-helper/std', 'pallet-farming/std', + 'pallet-asset-manager/std', ] try-runtime = [ 'frame-support/try-runtime', @@ -248,4 +251,5 @@ try-runtime = [ 'pallet-prices/try-runtime', 'pallet-crowdloans/try-runtime', 'pallet-xcm-helper/try-runtime', + 'pallet-asset-manager/try-runtime', ] diff --git a/runtime/kerria/src/lib.rs b/runtime/kerria/src/lib.rs index 8e6535536..e4426a362 100644 --- a/runtime/kerria/src/lib.rs +++ b/runtime/kerria/src/lib.rs @@ -31,10 +31,10 @@ use frame_support::{ tokens::BalanceConversion, ChangeMembers, Contains, EnsureOneOf, EqualPrivilegeOnly, Everything, Nothing, }, - PalletId, + transactional, PalletId, }; -use orml_traits::{DataProvider, DataProviderExtended}; +use orml_traits::{parameter_type_with_key, DataProvider, DataProviderExtended}; use polkadot_runtime_common::SlowAdjustingFeeUpdate; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -49,8 +49,8 @@ use sp_runtime::{ BlockNumberProvider, Convert, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, DispatchError, FixedU128, KeyTypeId, Perbill, Permill, RuntimeDebug, - SaturatedConversion, + ApplyExtrinsicResult, DispatchError, DispatchResult, FixedU128, KeyTypeId, Perbill, Permill, + RuntimeDebug, SaturatedConversion, }; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -67,18 +67,23 @@ use primitives::{ currency::MultiCurrencyAdapter, network::PARALLEL_PREFIX, tokens::{ACA, AUSD, DOT, EUSDC, EUSDT, LC_DOT, LDOT, PARA, SDOT}, - Index, *, + xcm_gadget::{ + AccountIdToMultiLocation, AsAssetType, AssetType, CurrencyIdtoMultiLocation, + FirstAssetTrader, + }, + AssetRegistrarMetadata, Index, *, }; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, - LocationInverter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, ConvertedConcreteAssetId, EnsureXcmOrigin, FixedRateOfFungible, + FixedWeightBounds, FungiblesAdapter, LocationInverter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, + TakeWeightCredit, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; pub mod constants; pub mod impls; // A few exports that help ease life for downstream crates. @@ -459,17 +464,6 @@ impl Convert> for CurrencyIdConvert { } } -pub struct AccountIdToMultiLocation; -impl Convert for AccountIdToMultiLocation { - fn convert(account_id: AccountId) -> MultiLocation { - X1(AccountId32 { - network: NetworkId::Any, - id: account_id.into(), - }) - .into() - } -} - parameter_types! { pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::parachain_id().into()))); pub const BaseXcmWeight: Weight = 150_000_000; @@ -481,14 +475,18 @@ impl orml_xtokens::Config for Runtime { type Event = Event; type Balance = Balance; type CurrencyId = CurrencyId; - type CurrencyIdConvert = CurrencyIdConvert; - type AccountIdToMultiLocation = AccountIdToMultiLocation; + type CurrencyIdConvert = CurrencyIdtoMultiLocation< + CurrencyIdConvert, + AsAssetType, + >; + type AccountIdToMultiLocation = AccountIdToMultiLocation; type SelfLocation = SelfLocation; type XcmExecutor = XcmExecutor; type Weigher = FixedWeightBounds; type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; } parameter_types! { @@ -1103,14 +1101,76 @@ pub type Trader = ( FixedRateOfFungible, FixedRateOfFungible, FixedRateOfFungible, + FirstAssetTrader, ); +// Min fee required when transfering non-reserve asset back to sibling chain +// It will use aother asset(e.g Relaychain's asset) as fee +parameter_type_with_key! { + pub ParachainMinFee: |location: MultiLocation| -> u128 { + #[allow(clippy::match_ref_pats)] // false positive + match (location.parents, location.first_interior()) { + (1, Some(Parachain(1000))) => XcmHelper::get_xcm_weight_fee_to_sibling(location.clone()).fee,//default fee should be enough even if not configured + _ => u128::MAX, + } + }; +} + +parameter_types! { + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); +} + +/// The non-reserve fungible transactor type +/// It will use pallet-assets, and the Id will be matched against AsAssetType +pub type ForeignFungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + Assets, + // Use this currency when it is a fungible asset matching the given location or name: + ( + ConvertedConcreteAssetId< + CurrencyId, + Balance, + AsAssetType, + JustTry, + >, + ), + // Do a simple punn to convert an AccountId20 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 dont allow teleports. + Nothing, + // We dont track any teleports + CheckingAccount, +>; + +/// How to withdraw and deposit an asset, try LocalAssetTransactor first +/// and if AssetNotFound then with ForeignFungiblesTransactor as fallback +pub type AssetTransactors = (LocalAssetTransactor, ForeignFungiblesTransactor); + +/// This is the struct that will handle the revenue from xcm fees +/// We do not burn anything because we want to mimic exactly what +/// the sovereign account has +pub type XcmFeesToAccount = primitives::xcm_gadget::XcmFeesToAccount< + Assets, + ( + ConvertedConcreteAssetId< + CurrencyId, + Balance, + AsAssetType, + JustTry, + >, + ), + AccountId, + TreasuryAccount, +>; + pub struct XcmConfig; impl Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; + type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = MultiNativeAsset; // Teleporting is disabled. @@ -1125,6 +1185,48 @@ impl Config for XcmConfig { type AssetClaims = PolkadotXcm; } +// We instruct how to register the Assets +// In this case, we tell it to Create an Asset in pallet-assets +pub struct AssetRegistrar; + +impl pallet_asset_manager::AssetRegistrar for AssetRegistrar { + #[transactional] + fn create_asset( + asset: CurrencyId, + min_balance: Balance, + metadata: AssetRegistrarMetadata, + is_sufficient: bool, + ) -> DispatchResult { + Assets::force_create( + Origin::root(), + asset, + sp_runtime::MultiAddress::Id(AssetManager::account_id()), + is_sufficient, + min_balance, + )?; + + Assets::force_set_metadata( + Origin::root(), + asset, + metadata.name, + metadata.symbol, + metadata.decimals, + metadata.is_frozen, + ) + } +} + +impl pallet_asset_manager::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = CurrencyId; + type AssetRegistrarMetadata = AssetRegistrarMetadata; + type AssetType = AssetType; + type AssetRegistrar = AssetRegistrar; + type AssetModifierOrigin = EnsureRoot; + type WeightInfo = pallet_asset_manager::weights::SubstrateWeight; +} + parameter_types! { pub const MinimumCount: u32 = 1; pub const ExpiresIn: Moment = 1000 * 60 * 60; // 60 mins @@ -1639,7 +1741,7 @@ impl pallet_xcm_helper::Config for Runtime { type RelayNetwork = RelayNetwork; type PalletId = XcmHelperPalletId; type NotifyTimeout = NotifyTimeout; - type AccountIdToMultiLocation = AccountIdToMultiLocation; + type AccountIdToMultiLocation = AccountIdToMultiLocation; type RefundLocation = RefundLocation; type BlockNumberProvider = frame_system::Pallet; type WeightInfo = pallet_xcm_helper::weights::SubstrateWeight; @@ -1798,6 +1900,9 @@ construct_runtime!( OrmlXcm: orml_xcm::{Pallet, Call, Event} = 45, Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 46, + // Asset Management + AssetManager: pallet_asset_manager::{Pallet, Call, Storage, Event} = 48, + // Loans Loans: pallet_loans::{Pallet, Call, Storage, Event} = 50, Prices: pallet_prices::{Pallet, Storage, Call, Event} = 51, diff --git a/runtime/parallel/src/lib.rs b/runtime/parallel/src/lib.rs index bb526cb9e..0e02728d5 100644 --- a/runtime/parallel/src/lib.rs +++ b/runtime/parallel/src/lib.rs @@ -34,7 +34,7 @@ use frame_support::{ PalletId, }; -use orml_traits::{DataProvider, DataProviderExtended}; +use orml_traits::{parameter_type_with_key, DataProvider, DataProviderExtended}; use polkadot_runtime_common::SlowAdjustingFeeUpdate; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -488,6 +488,12 @@ parameter_types! { pub const MaxAssetsForTransfer: usize = 2; } +parameter_type_with_key! { + pub ParachainMinFee: |_location: MultiLocation| -> u128 { + u128::MAX + }; +} + impl orml_xtokens::Config for Runtime { type Event = Event; type Balance = Balance; @@ -500,6 +506,7 @@ impl orml_xtokens::Config for Runtime { type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; } parameter_types! { diff --git a/runtime/vanilla/Cargo.toml b/runtime/vanilla/Cargo.toml index 43de86f3a..338883635 100644 --- a/runtime/vanilla/Cargo.toml +++ b/runtime/vanilla/Cargo.toml @@ -89,6 +89,7 @@ orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-r orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library.git', default-features = false } # Parallel dependencies +pallet-asset-manager = { path = '../../pallets/asset-manager', default-features = false } pallet-amm = { path = '../../pallets/amm', default-features = false } pallet-bridge = { path = '../../pallets/bridge', default-features = false } pallet-crowdloans = { path = '../../pallets/crowdloans', default-features = false } @@ -125,6 +126,7 @@ runtime-benchmarks = [ 'pallet-timestamp/runtime-benchmarks', 'pallet-loans/runtime-benchmarks', 'pallet-liquid-staking/runtime-benchmarks', + 'pallet-asset-manager/runtime-benchmarks', 'pallet-amm/runtime-benchmarks', 'pallet-collective/runtime-benchmarks', 'pallet-xcm/runtime-benchmarks', @@ -214,6 +216,7 @@ std = [ 'pallet-crowdloans/std', 'pallet-emergency-shutdown/std', 'pallet-xcm-helper/std', + 'pallet-asset-manager/std', ] try-runtime = [ 'frame-support/try-runtime', @@ -249,4 +252,5 @@ try-runtime = [ 'pallet-prices/try-runtime', 'pallet-crowdloans/try-runtime', 'pallet-xcm-helper/try-runtime', -] \ No newline at end of file + 'pallet-asset-manager/try-runtime', +] diff --git a/runtime/vanilla/src/lib.rs b/runtime/vanilla/src/lib.rs index 7d9c15f04..31b6c9327 100644 --- a/runtime/vanilla/src/lib.rs +++ b/runtime/vanilla/src/lib.rs @@ -28,19 +28,21 @@ use scale_info::TypeInfo; use frame_support::{ dispatch::Weight, log, match_type, + pallet_prelude::DispatchResult, traits::{ fungibles::{InspectMetadata, Mutate}, tokens::BalanceConversion, ChangeMembers, Contains, EnsureOneOf, EqualPrivilegeOnly, Everything, InstanceFilter, Nothing, }, - PalletId, + transactional, PalletId, }; use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, EnsureSigned, }; +use orml_traits::parameter_type_with_key; use orml_traits::{DataProvider, DataProviderExtended}; use orml_xcm_support::{IsNativeConcrete, MultiNativeAsset}; use polkadot_parachain::primitives::Sibling; @@ -49,7 +51,11 @@ use primitives::{ currency::MultiCurrencyAdapter, network::HEIKO_PREFIX, tokens::{EUSDC, EUSDT, HKO, KAR, KBTC, KINT, KSM, KUSD, LKSM, MOVR, PHA, SKSM}, - Index, *, + xcm_gadget::{ + AccountIdToMultiLocation, AsAssetType, AssetType, CurrencyIdtoMultiLocation, + FirstAssetTrader, + }, + AssetRegistrarMetadata, Index, *, }; use sp_api::impl_runtime_apis; use sp_core::{ @@ -73,12 +79,13 @@ use sp_version::RuntimeVersion; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, - LocationInverter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, ConvertedConcreteAssetId, EnsureXcmOrigin, FixedRateOfFungible, + FixedWeightBounds, FungiblesAdapter, LocationInverter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, + TakeWeightCredit, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; pub mod constants; pub mod impls; @@ -497,17 +504,6 @@ impl Convert> for CurrencyIdConvert { } } -pub struct AccountIdToMultiLocation; -impl Convert for AccountIdToMultiLocation { - fn convert(account_id: AccountId) -> MultiLocation { - X1(AccountId32 { - network: NetworkId::Any, - id: account_id.into(), - }) - .into() - } -} - parameter_types! { pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::parachain_id().into()))); pub const BaseXcmWeight: Weight = 150_000_000; @@ -519,14 +515,18 @@ impl orml_xtokens::Config for Runtime { type Event = Event; type Balance = Balance; type CurrencyId = CurrencyId; - type CurrencyIdConvert = CurrencyIdConvert; - type AccountIdToMultiLocation = AccountIdToMultiLocation; + type CurrencyIdConvert = CurrencyIdtoMultiLocation< + CurrencyIdConvert, + AsAssetType, + >; + type AccountIdToMultiLocation = AccountIdToMultiLocation; type SelfLocation = SelfLocation; type XcmExecutor = XcmExecutor; type Weigher = FixedWeightBounds; type BaseXcmWeight = BaseXcmWeight; type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; } parameter_types! { @@ -1173,14 +1173,78 @@ pub type Trader = ( // Kintsugi FixedRateOfFungible, FixedRateOfFungible, + // Foreign Assets registered in AssetManager + // TODO: replace all above except local reserved asset later + FirstAssetTrader, ); +// Min fee required when transfering non-reserve asset back to sibling chain +// It will use aother asset(e.g Relaychain's asset) as fee +parameter_type_with_key! { + pub ParachainMinFee: |location: MultiLocation| -> u128 { + #[allow(clippy::match_ref_pats)] // false positive + match (location.parents, location.first_interior()) { + (1, Some(Parachain(1000))) => XcmHelper::get_xcm_weight_fee_to_sibling(location.clone()).fee,//default fee should be enough even if not configured + _ => u128::MAX, + } + }; +} + +parameter_types! { + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); +} + +/// The non-reserve fungible transactor type +/// It will use pallet-assets, and the Id will be matched against AsAssetType +pub type ForeignFungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + Assets, + // Use this currency when it is a fungible asset matching the given location or name: + ( + ConvertedConcreteAssetId< + CurrencyId, + Balance, + AsAssetType, + JustTry, + >, + ), + // Do a simple punn to convert an AccountId20 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 dont allow teleports. + Nothing, + // We dont track any teleports + CheckingAccount, +>; + +/// How to withdraw and deposit an asset, try LocalAssetTransactor first +/// and if AssetNotFound then with ForeignFungiblesTransactor as fallback +pub type AssetTransactors = (LocalAssetTransactor, ForeignFungiblesTransactor); + +/// This is the struct that will handle the revenue from xcm fees +/// We do not burn anything because we want to mimic exactly what +/// the sovereign account has +pub type XcmFeesToAccount = primitives::xcm_gadget::XcmFeesToAccount< + Assets, + ( + ConvertedConcreteAssetId< + CurrencyId, + Balance, + AsAssetType, + JustTry, + >, + ), + AccountId, + TreasuryAccount, +>; + pub struct XcmConfig; impl Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; + type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = MultiNativeAsset; // Teleporting is disabled. @@ -1195,6 +1259,48 @@ impl Config for XcmConfig { type AssetClaims = PolkadotXcm; } +// We instruct how to register the Assets +// In this case, we tell it to Create an Asset in pallet-assets +pub struct AssetRegistrar; + +impl pallet_asset_manager::AssetRegistrar for AssetRegistrar { + #[transactional] + fn create_asset( + asset: CurrencyId, + min_balance: Balance, + metadata: AssetRegistrarMetadata, + is_sufficient: bool, + ) -> DispatchResult { + Assets::force_create( + Origin::root(), + asset, + sp_runtime::MultiAddress::Id(AssetManager::account_id()), + is_sufficient, + min_balance, + )?; + + Assets::force_set_metadata( + Origin::root(), + asset, + metadata.name, + metadata.symbol, + metadata.decimals, + metadata.is_frozen, + ) + } +} + +impl pallet_asset_manager::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = CurrencyId; + type AssetRegistrarMetadata = AssetRegistrarMetadata; + type AssetType = AssetType; + type AssetRegistrar = AssetRegistrar; + type AssetModifierOrigin = EnsureRoot; + type WeightInfo = pallet_asset_manager::weights::SubstrateWeight; +} + parameter_types! { pub const MinimumCount: u32 = 1; pub const ExpiresIn: Moment = 1000 * 60 * 60; // 60 mins @@ -1709,7 +1815,7 @@ impl pallet_xcm_helper::Config for Runtime { type RelayNetwork = RelayNetwork; type PalletId = XcmHelperPalletId; type NotifyTimeout = NotifyTimeout; - type AccountIdToMultiLocation = AccountIdToMultiLocation; + type AccountIdToMultiLocation = AccountIdToMultiLocation; type RefundLocation = RefundLocation; type BlockNumberProvider = frame_system::Pallet; type WeightInfo = pallet_xcm_helper::weights::SubstrateWeight; @@ -1867,6 +1973,9 @@ construct_runtime!( OrmlXcm: orml_xcm::{Pallet, Call, Event} = 45, Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 46, + // Asset Management + AssetManager: pallet_asset_manager::{Pallet, Call, Storage, Event} = 48, + // Loans Loans: pallet_loans::{Pallet, Call, Storage, Event} = 50, Prices: pallet_prices::{Pallet, Storage, Call, Event} = 51, diff --git a/rustfmt.toml b/rustfmt.toml index a84bc55f9..1a829a32d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,5 @@ +#we can enable all files check later +#license_template_path = "LICENSE_TEMPLATE" ignore = [ "weights.rs", "runtime/heiko/src/weights",