diff --git a/Cargo.lock b/Cargo.lock index 110525a18..c0cccc11e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", "once_cell", "version_check", ] @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.53" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "approx" @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -849,9 +849,9 @@ checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-slice-cast" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c751592b77c499e7bce34d99d67c2c11bdc0574e9a488ddade14150a4698" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "byte-tools" @@ -1002,7 +1002,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.5", + "semver 1.0.6", "serde", "serde_json", ] @@ -1683,7 +1683,7 @@ name = "cumulus-pallet-parachain-system-proc-macro" version = "0.1.0" source = "git+https://github.com/paritytech/cumulus.git?branch=polkadot-v0.9.16#86f76c5619c64d1300315612695ad4b4fcd0f562" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -2026,7 +2026,6 @@ dependencies = [ name = "dolphin-runtime" version = "3.1.4" dependencies = [ - "calamari-vesting", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", @@ -2047,6 +2046,9 @@ dependencies = [ "log", "manta-collator-selection", "manta-primitives", + "orml-xtokens", + "pallet-asset-manager", + "pallet-assets", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -2067,9 +2069,11 @@ dependencies = [ "pallet-xcm", "parachain-info", "parity-scale-codec", + "polkadot-core-primitives", "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-common", + "polkadot-runtime-parachains", "scale-info", "serde", "smallvec", @@ -2089,6 +2093,7 @@ dependencies = [ "xcm", "xcm-builder", "xcm-executor", + "xcm-simulator", ] [[package]] @@ -2132,9 +2137,9 @@ checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" [[package]] name = "ed25519" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" +checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" dependencies = [ "signature", ] @@ -2170,11 +2175,11 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" dependencies = [ - "heck", + "heck 0.4.0", "proc-macro2", "quote", "syn", @@ -2555,7 +2560,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -2625,9 +2630,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0" +checksum = "5bd79fa345a495d3ae89fb7165fec01c0e72f41821d642dda363a1e97975652e" [[package]] name = "fs-swap" @@ -2836,9 +2841,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2972,6 +2977,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -3254,9 +3265,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c11140ffea82edce8dcd74137ce9324ec24b3cf0175fc9d7e29164da9915b8" +checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" [[package]] name = "integer-sqrt" @@ -3499,7 +3510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d802063f7a3c867456955f9d2f15eb3ee0edb5ec9ec2b5526324756759221c0f" dependencies = [ "log", - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -3737,9 +3748,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.118" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libloading" @@ -4526,13 +4537,19 @@ dependencies = [ name = "manta-primitives" version = "3.1.4" dependencies = [ + "frame-support", + "log", "parity-scale-codec", + "scale-info", "smallvec", "sp-consensus-aura", "sp-core", "sp-io", "sp-runtime", "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", ] [[package]] @@ -4886,7 +4903,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro-error", "proc-macro2", "quote", @@ -5180,6 +5197,72 @@ dependencies = [ "num-traits", ] +[[package]] +name = "orml-traits" +version = "0.4.1-dev" +source = "git+https://github.com/manta-network/open-runtime-module-library.git?rev=4a66b29#4a66b299037cc3997689538f82847785f9afa65d" +dependencies = [ + "frame-support", + "impl-trait-for-tuples", + "num-traits", + "orml-utilities", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "orml-utilities" +version = "0.4.1-dev" +source = "git+https://github.com/manta-network/open-runtime-module-library.git?rev=4a66b29#4a66b299037cc3997689538f82847785f9afa65d" +dependencies = [ + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "orml-xcm-support" +version = "0.4.1-dev" +source = "git+https://github.com/manta-network/open-runtime-module-library.git?rev=4a66b29#4a66b299037cc3997689538f82847785f9afa65d" +dependencies = [ + "frame-support", + "orml-traits", + "parity-scale-codec", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "orml-xtokens" +version = "0.4.1-dev" +source = "git+https://github.com/manta-network/open-runtime-module-library.git?rev=4a66b29#4a66b299037cc3997689538f82847785f9afa65d" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "orml-traits", + "orml-xcm-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "owning_ref" version = "0.4.1" @@ -5189,6 +5272,39 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "pallet-asset-manager" +version = "3.1.4" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "manta-primitives", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "pallet-assets" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-aura" version = "4.0.0-dev" @@ -5877,7 +5993,7 @@ name = "pallet-staking-reward-curve" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -6140,7 +6256,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -7121,7 +7237,7 @@ name = "polkadot-overseer-gen-proc-macro" version = "0.9.16" source = "git+https://github.com/paritytech/polkadot.git?branch=release-v0.9.16#41ab002d7451766324a9f314fee11c9c53314350" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -7614,9 +7730,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dada8c9981fcf32929c3c0f0cd796a9284aca335565227ed88c83babb1d43dc" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", @@ -7686,7 +7802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.1.0", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log", @@ -7829,7 +7945,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", ] [[package]] @@ -7912,7 +8028,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", "redox_syscall 0.2.10", ] @@ -8072,9 +8188,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dd4445360338dab5116712bee1388dc727991d51969558a8882ab552e6db30" +checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" [[package]] name = "ring" @@ -8241,7 +8357,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.5", + "semver 1.0.6", ] [[package]] @@ -8423,7 +8539,7 @@ name = "sc-chain-spec-derive" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -9207,7 +9323,7 @@ name = "sc-tracing-proc-macro" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -9286,7 +9402,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baeb2780690380592f86205aa4ee49815feb2acad8c2f59e6dd207148c3f1fcd" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -9394,9 +9510,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" +checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" dependencies = [ "serde", ] @@ -9701,7 +9817,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ "blake2-rfc", - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -10088,7 +10204,7 @@ name = "sp-npos-elections-solution-type" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -10169,7 +10285,7 @@ version = "4.0.0" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#19162e43be45817b44c7d48e50d03f074f60fbf4" dependencies = [ "Inflector", - "proc-macro-crate 1.1.2", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -10380,9 +10496,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cb4b9ce18beb6cb16ecad62d936245cef5212ddc8e094d7417a75e8d0e85f5" +checksum = "2f9799e6d412271cb2414597581128b03f3285f260ea49f5363d07df6a332b3e" dependencies = [ "Inflector", "proc-macro2", @@ -10465,7 +10581,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", @@ -10496,7 +10612,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2", "quote", "syn", @@ -10508,7 +10624,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2", "quote", "rustversion", @@ -11858,6 +11974,23 @@ dependencies = [ "syn", ] +[[package]] +name = "xcm-simulator" +version = "0.9.16" +source = "git+https://github.com/paritytech/polkadot.git?branch=release-v0.9.16#41ab002d7451766324a9f314fee11c9c53314350" +dependencies = [ + "frame-support", + "parity-scale-codec", + "paste", + "polkadot-core-primitives", + "polkadot-parachain", + "polkadot-runtime-parachains", + "sp-io", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "yamux" version = "0.9.0" @@ -11874,18 +12007,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c88870063c39ee00ec285a2f8d6a966e5b6fb2becc4e8dac77ed0d370ed6006" +checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", diff --git a/pallets/asset-manager/Cargo.toml b/pallets/asset-manager/Cargo.toml new file mode 100644 index 000000000..0cd04dee4 --- /dev/null +++ b/pallets/asset-manager/Cargo.toml @@ -0,0 +1,50 @@ +[package] +authors = ['Manta Network'] +name = "pallet-asset-manager" +version = "3.1.4" +edition = "2021" +homepage = 'https://manta.network' +license = 'GPL-3.0' +repository = 'https://github.com/Manta-Network/Manta/' + + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } +# scale-info has to be 1.0 for now +scale-info = { version = "1.0", default-features = false, features = ["derive"] } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16", default-features = false } +frame-benchmarking = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.16", default-features = false, optional = true } +manta-primitives = { path = '../../runtime/primitives', default-features = false} + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16" } +pallet-balances = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.16" } +pallet-assets = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.16" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.16" } +xcm = { git = 'https://github.com/paritytech/polkadot.git', branch = "release-v0.9.16" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "manta-primitives/std", + "frame-benchmarking/std", +] +try-runtime = [ + "frame-support/try-runtime", +] + +runtime-benchmarks = [ + "frame-benchmarking", + 'frame-support/runtime-benchmarks', + 'frame-system/runtime-benchmarks', +] + diff --git a/pallets/asset-manager/src/lib.rs b/pallets/asset-manager/src/lib.rs new file mode 100644 index 000000000..5f5f99dfd --- /dev/null +++ b/pallets/asset-manager/src/lib.rs @@ -0,0 +1,374 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +//! # Asset Manager Pallet +//! +//! A simple asset manager for native and cross-chain tokens +//! +//! ## Overview +//! +//! The Asset manager module provides functionality for registering cross chain assets +//! +//! TODO: detailed doc-string comment + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + + use codec::Codec; + use frame_support::{pallet_prelude::*, transactional, PalletId}; + use frame_system::pallet_prelude::*; + use manta_primitives::{AssetIdLocationGetter, UnitsToWeightRatio}; + use scale_info::TypeInfo; + use sp_runtime::{ + traits::{AccountIdConversion, AtLeast32BitUnsigned, CheckedAdd, One}, + ArithmeticError, + }; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// The AssetManagers's pallet id + pub const PALLET_ID: PalletId = PalletId(*b"asstmngr"); + + /// The registrar trait: defines the interface of creating an asset in the asset implementation layer. + /// We may revisit this interface design (e.g. add change asset interface). However, change StorageMetadata + /// should be rare. + pub trait AssetRegistrar { + /// Create an new asset. + /// + /// * `asset_id`: the asset id to be created + /// * `min_balance`: the minimum balance to hold this asset + /// * `metadata`: the metadata that the implementation layer stores + /// * `is_sufficient`: whether this asset can be used as reserve asset, + /// to the first approximation. More specifically, Whether a non-zero balance of this asset is deposit of sufficient + /// value to account for the state bloat associated with its balance storage. If set to + /// `true`, then non-zero balances may be stored without a `consumer` reference (and thus + /// an ED in the Balances pallet or whatever else is used to control user-account state + /// growth). + fn create_asset( + asset_id: T::AssetId, + min_balance: T::Balance, + metadata: T::StorageMetadata, + is_sufficient: bool, + ) -> DispatchResult; + + /// Update asset metadata by `AssetId`. + /// + /// * `asset_id`: the asset id to be created. + /// * `metadata`: the metadata that the implementation layer stores. + fn update_asset_metadata( + asset_id: T::AssetId, + metadata: T::StorageMetadata, + ) -> DispatchResult; + } + + /// The AssetMetadata trait: + pub trait AssetMetadata { + /// Returns the minimum balance to hold this asset + fn min_balance(&self) -> T::Balance; + + /// Returns a boolean value indicating whether this asset needs an existential deposit + fn is_sufficient(&self) -> bool; + } + + /// Convert AssetId and AssetLocation + impl AssetIdLocationGetter for Pallet { + fn get_asset_id(loc: &T::AssetLocation) -> Option { + LocationAssetId::::get(loc) + } + + fn get_asset_location(id: T::AssetId) -> Option { + AssetIdLocation::::get(id) + } + } + + /// Get unit per second from `AssetId` + impl UnitsToWeightRatio for Pallet { + fn get_units_per_second(id: T::AssetId) -> Option { + UnitsPerSecond::::get(id) + } + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The asset id type, this have to be consistent with pallet-manta-pay + type AssetId: Member + + Parameter + + Default + + Copy + + AtLeast32BitUnsigned + + MaybeSerializeDeserialize + + MaxEncodedLen + + TypeInfo; + + /// The trait we use to register Assets + type AssetRegistrar: AssetRegistrar; + + /// The units in which we record balances. + type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen; + + /// Metadata type that required in token storage: e.g. AssetMetadata in Pallet-Assets. + type StorageMetadata: Member + Parameter + Default; + + /// The Asset Metadata type stored in this pallet. + type AssetRegistrarMetadata: Member + + Parameter + + Codec + + Default + + Into + + AssetMetadata; + + /// The AssetLocation type: could be just a thin wrapper of MultiLocation + type AssetLocation: Member + Parameter + Default + TypeInfo; + + /// The origin which may forcibly create or destroy an asset or otherwise alter privileged + /// attributes. + type ModifierOrigin: EnsureOrigin; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new asset registered. + AssetRegistered { + asset_id: T::AssetId, + asset_address: T::AssetLocation, + metadata: T::AssetRegistrarMetadata, + }, + /// An asset's location has been updated. + AssetLocationUpdated { + asset_id: T::AssetId, + location: T::AssetLocation, + }, + /// An asset;s metadata has been updated. + AssetMetadataUpdated { + asset_id: T::AssetId, + metadata: T::AssetRegistrarMetadata, + }, + /// Update units per second of an asset + UnitsPerSecondUpdated { + asset_id: T::AssetId, + units_per_second: u128, + }, + } + + /// Error. + #[pallet::error] + pub enum Error { + /// Location already exists. + LocationAlreadyExists, + /// Error creating asset, e.g. error returned from the implementation layer. + ErrorCreatingAsset, + /// Update a non-exist asset + UpdateNonExistAsset, + /// Asset already registered. + AssetAlreadyRegistered, + } + + /// AssetId to MultiLocation Map. + /// This is mostly useful when sending an asset to a foreign location. + #[pallet::storage] + #[pallet::getter(fn asset_id_location)] + pub(super) type AssetIdLocation = + StorageMap<_, Blake2_128Concat, T::AssetId, T::AssetLocation>; + + /// MultiLocation to AssetId Map. + /// This is mostly useful when receiving an asset from a foreign location. + #[pallet::storage] + #[pallet::getter(fn location_asset_id)] + pub(super) type LocationAssetId = + StorageMap<_, Blake2_128Concat, T::AssetLocation, T::AssetId>; + + /// AssetId to AssetRegistrar Map. + #[pallet::storage] + #[pallet::getter(fn asset_id_metadata)] + pub(super) type AssetIdMetadata = + StorageMap<_, Blake2_128Concat, T::AssetId, T::AssetRegistrarMetadata>; + + /// Get the next available AssetId. + #[pallet::storage] + #[pallet::getter(fn next_asset_id)] + pub type NextAssetId = StorageValue<_, T::AssetId, ValueQuery>; + + /// XCM transfer cost for different asset. + #[pallet::storage] + pub type UnitsPerSecond = StorageMap<_, Blake2_128Concat, T::AssetId, u128>; + + #[pallet::call] + impl Pallet { + /// Register a new asset in the asset manager. + /// + /// * `origin`: Caller of this extrinsic, the acess control is specfied by `ForceOrigin`. + /// * `location`: Location of the asset. + /// * `metadata`: Asset metadata. + /// * `min_balance`: Minimum balance to keep an account alive, used in conjunction with `is_sufficient`. + /// * `is_sufficient`: Whether this asset needs users to have an existential deposit to hold + /// this asset. + /// + /// # + /// TODO: get actual weight + /// # + #[pallet::weight(50_000_000)] + #[transactional] + pub fn register_asset( + origin: OriginFor, + location: T::AssetLocation, + metadata: T::AssetRegistrarMetadata, + ) -> DispatchResult { + T::ModifierOrigin::ensure_origin(origin)?; + ensure!( + !LocationAssetId::::contains_key(&location), + Error::::LocationAlreadyExists + ); + let asset_id = Self::get_next_asset_id()?; + T::AssetRegistrar::create_asset( + asset_id, + metadata.min_balance(), + metadata.clone().into(), + metadata.is_sufficient(), + ) + .map_err(|_| Error::::ErrorCreatingAsset)?; + AssetIdLocation::::insert(&asset_id, &location); + AssetIdMetadata::::insert(&asset_id, &metadata); + LocationAssetId::::insert(&location, &asset_id); + Self::deposit_event(Event::::AssetRegistered { + asset_id, + asset_address: location, + metadata, + }); + Ok(()) + } + + /// Update an asset by its asset id in the asset manager. + /// + /// * `origin`: Caller of this extrinsic, the acess control is specfied by `ForceOrigin`. + /// * `asset_id`: AssetId to be updated. + /// * `location`: `location` to update the asset location. + /// # + /// TODO: get actual weight + /// # + #[pallet::weight(50_000_000)] + #[transactional] + pub fn update_asset_location( + origin: OriginFor, + #[pallet::compact] asset_id: T::AssetId, + location: T::AssetLocation, + ) -> DispatchResult { + // checks validity + T::ModifierOrigin::ensure_origin(origin)?; + ensure!( + AssetIdLocation::::contains_key(&asset_id), + Error::::UpdateNonExistAsset + ); + ensure!( + !LocationAssetId::::contains_key(&location), + Error::::LocationAlreadyExists + ); + // change the ledger state. + let old_location = + AssetIdLocation::::get(&asset_id).ok_or(Error::::UpdateNonExistAsset)?; + LocationAssetId::::remove(&old_location); + LocationAssetId::::insert(&location, &asset_id); + AssetIdLocation::::insert(&asset_id, &location); + // deposit event. + Self::deposit_event(Event::::AssetLocationUpdated { asset_id, location }); + Ok(()) + } + + /// Update an asset's metadata by its `asset_id` + /// + /// * `origin`: Caller of this extrinsic, the acess control is specfied by `ForceOrigin`. + /// * `asset_id`: AssetId to be updated. + /// * `metadata`: new `metadata` to be associated with `asset_id`. + #[pallet::weight(50_000_000)] + #[transactional] + pub fn update_asset_metadata( + origin: OriginFor, + #[pallet::compact] asset_id: T::AssetId, + metadata: T::AssetRegistrarMetadata, + ) -> DispatchResult { + T::ModifierOrigin::ensure_origin(origin)?; + ensure!( + AssetIdLocation::::contains_key(&asset_id), + Error::::UpdateNonExistAsset + ); + AssetIdMetadata::::insert(&asset_id, &metadata); + Self::deposit_event(Event::::AssetMetadataUpdated { asset_id, metadata }); + Ok(()) + } + + /// Update an asset by its asset id in the asset manager. + /// + /// * `origin`: Caller of this extrinsic, the acess control is specfied by `ForceOrigin`. + /// * `asset_id`: AssetId to be updated. + /// * `units_per_second`: units per second for `asset_id` + /// # + /// TODO: get actual weight + /// # + #[pallet::weight(50_000_000)] + #[transactional] + pub fn set_units_per_second( + origin: OriginFor, + #[pallet::compact] asset_id: T::AssetId, + #[pallet::compact] units_per_second: u128, + ) -> DispatchResult { + T::ModifierOrigin::ensure_origin(origin)?; + ensure!( + AssetIdLocation::::contains_key(&asset_id), + Error::::UpdateNonExistAsset + ); + UnitsPerSecond::::insert(&asset_id, &units_per_second); + Self::deposit_event(Event::::UnitsPerSecondUpdated { + asset_id, + units_per_second, + }); + Ok(()) + } + } + + impl Pallet { + /// Get and increment the `NextAssetID` by one. + fn get_next_asset_id() -> Result { + NextAssetId::::try_mutate(|current| -> Result { + let id = *current; + *current = current + .checked_add(&One::one()) + .ok_or(ArithmeticError::Overflow)?; + Ok(id) + }) + } + + /// The account ID of AssetManager + pub fn account_id() -> T::AccountId { + PALLET_ID.into_account() + } + } +} diff --git a/pallets/asset-manager/src/mock.rs b/pallets/asset-manager/src/mock.rs new file mode 100644 index 000000000..09acc3b91 --- /dev/null +++ b/pallets/asset-manager/src/mock.rs @@ -0,0 +1,196 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . +// +// The pallet-tx-pause pallet is forked from Acala's transaction-pause module https://github.com/AcalaNetwork/Acala/tree/master/modules/transaction-pause +// The original license is the following - SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +//! Mock runtime for asset-manager + +use super::*; +use crate as pallet_asset_manager; +use frame_support::{construct_runtime, parameter_types, traits::ConstU32}; +use frame_system as system; +use frame_system::EnsureRoot; +use manta_primitives::{ + AccountId, AssetId, AssetLocation, AssetRegistarMetadata, AssetStorageMetadata, Balance, + ASSET_STRING_LIMIT, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 78; +} + +impl system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const AssetDeposit: Balance = 0; // Does not really matter as this will be only called by root + pub const AssetAccountDeposit: Balance = 0; + pub const ApprovalDeposit: Balance = 0; + pub const AssetsStringLimit: u32 = ASSET_STRING_LIMIT; + pub const MetadataDepositBase: Balance = 0; + pub const MetadataDepositPerByte: Balance = 0; +} + +impl pallet_assets::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = AssetId; + type Currency = Balances; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = AssetAccountDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +pub struct AssetRegistrar; +use frame_support::pallet_prelude::DispatchResult; +impl pallet_asset_manager::AssetRegistrar for AssetRegistrar { + fn create_asset( + asset_id: AssetId, + min_balance: Balance, + metadata: AssetStorageMetadata, + is_sufficient: bool, + ) -> DispatchResult { + Assets::force_create( + Origin::root(), + asset_id, + AssetManager::account_id(), + is_sufficient, + min_balance, + )?; + + Assets::force_set_metadata( + Origin::root(), + asset_id, + metadata.name, + metadata.symbol, + metadata.decimals, + metadata.is_frozen, + ) + } + + fn update_asset_metadata(asset_id: AssetId, metadata: AssetStorageMetadata) -> DispatchResult { + Assets::force_set_metadata( + Origin::root(), + asset_id, + metadata.name, + metadata.symbol, + metadata.decimals, + metadata.is_frozen, + ) + } +} + +impl AssetMetadata for AssetRegistarMetadata { + fn min_balance(&self) -> Balance { + self.min_balance + } + + fn is_sufficient(&self) -> bool { + self.is_sufficient + } +} + +impl pallet_asset_manager::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = AssetId; + type AssetRegistrarMetadata = AssetRegistarMetadata; + type StorageMetadata = AssetStorageMetadata; + type AssetLocation = AssetLocation; + type AssetRegistrar = AssetRegistrar; + type ModifierOrigin = EnsureRoot; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, + Assets: pallet_assets::{Pallet, Storage, Event} = 1, + AssetManager: pallet_asset_manager::{Pallet, Call, Storage, Event} = 2, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 3, + } +); + +pub const PALLET_BALANCES_INDEX: u8 = 3; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + sp_io::TestExternalities::new(t) +} diff --git a/pallets/asset-manager/src/tests.rs b/pallets/asset-manager/src/tests.rs new file mode 100644 index 000000000..6b055ae71 --- /dev/null +++ b/pallets/asset-manager/src/tests.rs @@ -0,0 +1,191 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . +// +// The pallet-tx-pause pallet is forked from Acala's transaction-pause module https://github.com/AcalaNetwork/Acala/tree/master/modules/transaction-pause +// The original license is the following - SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +//! unit tests for asset-manager + +use crate::{self as asset_manager, AssetIdLocation, UnitsPerSecond}; +use asset_manager::mock::*; +use frame_support::{assert_noop, assert_ok}; +use manta_primitives::{AssetLocation, AssetRegistarMetadata}; +use sp_runtime::traits::BadOrigin; +use xcm::{latest::prelude::*, VersionedMultiLocation}; + +#[test] +fn basic_setup_should_work() { + new_test_ext() + .execute_with(|| assert!(AssetIdLocation::::iter_values().next().is_none())); +} + +#[test] +fn wrong_modifer_origin_should_not_work() { + new_test_ext().execute_with(|| { + let asset_metadata = AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + assert_noop!( + AssetManager::register_asset( + Origin::signed([0u8; 32].into()), + source_location.clone(), + asset_metadata.clone() + ), + BadOrigin + ); + assert_noop!( + AssetManager::update_asset_location( + Origin::signed([2u8; 32].into()), + 0, + source_location + ), + BadOrigin + ); + assert_noop!( + AssetManager::update_asset_metadata( + Origin::signed([3u8; 32].into()), + 0, + asset_metadata + ), + BadOrigin + ); + assert_noop!( + AssetManager::set_units_per_second(Origin::signed([4u8; 32].into()), 0, 0), + BadOrigin + ); + }) +} + +#[test] +fn register_asset_should_work() { + let asset_metadata = AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let new_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X2(Parachain(1), PalletInstance(PALLET_BALANCES_INDEX)), + ))); + new_test_ext().execute_with(|| { + let mut counter: u32 = 0; + // Register relay chain native token + assert_ok!(AssetManager::register_asset( + Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_eq!( + AssetIdLocation::::get(counter), + Some(source_location.clone()) + ); + counter += 1; + // Register twice will fail + assert_noop!( + AssetManager::register_asset(Origin::root(), source_location, asset_metadata.clone()), + crate::Error::::LocationAlreadyExists + ); + // Register a new asset + assert_ok!(AssetManager::register_asset( + Origin::root(), + new_location.clone(), + asset_metadata.clone() + )); + assert_eq!(AssetIdLocation::::get(counter), Some(new_location)); + }) +} + +#[test] +fn update_asset() { + let asset_metadata = AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let mut new_metadata = asset_metadata.clone(); + new_metadata.is_frozen = true; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let new_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X2(Parachain(1), PalletInstance(PALLET_BALANCES_INDEX)), + ))); + new_test_ext().execute_with(|| { + // Register relay chain native token + assert_ok!(AssetManager::register_asset( + Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_eq!( + AssetIdLocation::::get(0), + Some(source_location.clone()) + ); + // Update the asset metadata + assert_ok!(AssetManager::update_asset_metadata( + Origin::root(), + 0, + new_metadata.clone() + )); + // Update the asset location + assert_ok!(AssetManager::update_asset_location( + Origin::root(), + 0, + new_location.clone() + )); + // Update asset units per seconds + assert_ok!(AssetManager::set_units_per_second( + Origin::root(), + 0, + 125u128 + )); + assert_eq!(UnitsPerSecond::::get(0), Some(125)); + // Update a non-exist asset should fail + assert_noop!( + AssetManager::update_asset_location(Origin::root(), 1, new_location.clone()), + crate::Error::::UpdateNonExistAsset + ); + assert_noop!( + AssetManager::update_asset_metadata(Origin::root(), 1, new_metadata.clone()), + crate::Error::::UpdateNonExistAsset + ); + // Update an asset to an existing location will fail + assert_ok!(AssetManager::register_asset( + Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_noop!( + AssetManager::update_asset_location(Origin::root(), 1, new_location), + crate::Error::::LocationAlreadyExists + ); + }) +} diff --git a/pallets/pallet-tx-pause/Cargo.toml b/pallets/tx-pause/Cargo.toml similarity index 100% rename from pallets/pallet-tx-pause/Cargo.toml rename to pallets/tx-pause/Cargo.toml diff --git a/pallets/pallet-tx-pause/README.md b/pallets/tx-pause/README.md similarity index 100% rename from pallets/pallet-tx-pause/README.md rename to pallets/tx-pause/README.md diff --git a/pallets/pallet-tx-pause/src/benchmarking.rs b/pallets/tx-pause/src/benchmarking.rs similarity index 100% rename from pallets/pallet-tx-pause/src/benchmarking.rs rename to pallets/tx-pause/src/benchmarking.rs diff --git a/pallets/pallet-tx-pause/src/lib.rs b/pallets/tx-pause/src/lib.rs similarity index 100% rename from pallets/pallet-tx-pause/src/lib.rs rename to pallets/tx-pause/src/lib.rs diff --git a/pallets/pallet-tx-pause/src/mock.rs b/pallets/tx-pause/src/mock.rs similarity index 100% rename from pallets/pallet-tx-pause/src/mock.rs rename to pallets/tx-pause/src/mock.rs diff --git a/pallets/pallet-tx-pause/src/tests.rs b/pallets/tx-pause/src/tests.rs similarity index 100% rename from pallets/pallet-tx-pause/src/tests.rs rename to pallets/tx-pause/src/tests.rs diff --git a/pallets/pallet-tx-pause/src/weights.rs b/pallets/tx-pause/src/weights.rs similarity index 100% rename from pallets/pallet-tx-pause/src/weights.rs rename to pallets/tx-pause/src/weights.rs diff --git a/runtime/calamari/Cargo.toml b/runtime/calamari/Cargo.toml index 87bead203..8edf60f46 100644 --- a/runtime/calamari/Cargo.toml +++ b/runtime/calamari/Cargo.toml @@ -81,7 +81,11 @@ pallet-xcm = { git = 'https://github.com/paritytech/polkadot.git', default-featu manta-primitives = { path = '../primitives', default-features = false } calamari-vesting = { path = '../../pallets/vesting', default-features = false } manta-collator-selection = { path = '../../pallets/collator-selection', default-features = false } -pallet-tx-pause = { path = '../../pallets/pallet-tx-pause', default-features = false } +pallet-tx-pause = { path = '../../pallets/tx-pause', default-features = false } + +[dev-dependencies] +serde_json = "1.0" +reqwest = { version = "0.11", features = ["blocking"] } [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] @@ -89,9 +93,6 @@ targets = ['x86_64-unknown-linux-gnu'] [build-dependencies] substrate-wasm-builder = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.16" } -[dev-dependencies] -serde_json = "1.0" -reqwest = { version = "0.11", features = ["blocking"] } [features] default = ['std'] @@ -156,6 +157,7 @@ std = [ 'pallet-sudo/std', 'pallet-xcm/std', 'pallet-transaction-payment/std', + 'pallet-treasury/std', 'pallet-collective/std', 'pallet-democracy/std', 'pallet-scheduler/std', diff --git a/runtime/dolphin/Cargo.toml b/runtime/dolphin/Cargo.toml index cf86a3935..442efe7a8 100644 --- a/runtime/dolphin/Cargo.toml +++ b/runtime/dolphin/Cargo.toml @@ -39,6 +39,7 @@ frame-system-rpc-runtime-api = { git = 'https://github.com/paritytech/substrate. frame-try-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "polkadot-v0.9.16" } # Substrate pallets +pallet-assets = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } pallet-aura = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } pallet-authorship = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } pallet-balances = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } @@ -70,6 +71,7 @@ parachain-info = { git = 'https://github.com/paritytech/cumulus.git', default-fe # Polkadot dependencies polkadot-primitives = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16" } +polkadot-core-primitives = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16" } polkadot-runtime-common = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16" } polkadot-parachain = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16" } xcm = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16" } @@ -79,9 +81,16 @@ pallet-xcm = { git = 'https://github.com/paritytech/polkadot.git', default-featu # Self dependencies manta-primitives = { path = '../primitives', default-features = false } -calamari-vesting = { path = '../../pallets/vesting', default-features = false } manta-collator-selection = { path = '../../pallets/collator-selection', default-features = false } -pallet-tx-pause = { path = '../../pallets/pallet-tx-pause', default-features = false } +pallet-tx-pause = { path = '../../pallets/tx-pause', default-features = false } +pallet-asset-manager = { path = '../../pallets/asset-manager', default-features = false } + +# Third party (vendored) dependencies +orml-xtokens = { git = "https://github.com/manta-network/open-runtime-module-library.git", default-features = false, rev="4a66b29"} + +[dev-dependencies] +xcm-simulator = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16"} +polkadot-runtime-parachains = { git = 'https://github.com/paritytech/polkadot.git', branch = "release-v0.9.16" } [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] @@ -115,7 +124,6 @@ runtime-benchmarks = [ 'pallet-preimage/runtime-benchmarks', 'pallet-scheduler/runtime-benchmarks', 'pallet-membership/runtime-benchmarks', - 'calamari-vesting/runtime-benchmarks', 'pallet-tx-pause/runtime-benchmarks', 'pallet-treasury/runtime-benchmarks', ] @@ -170,8 +178,9 @@ std = [ 'xcm-executor/std', 'polkadot-runtime-common/std', 'polkadot-primitives/std', + 'orml-xtokens/std', + 'pallet-asset-manager/std', 'manta-collator-selection/std', - 'calamari-vesting/std', 'pallet-tx-pause/std', 'pallet-treasury/std', ] \ No newline at end of file diff --git a/runtime/dolphin/tests/xcm_mock/mod.rs b/runtime/dolphin/tests/xcm_mock/mod.rs new file mode 100644 index 000000000..a83bca6b1 --- /dev/null +++ b/runtime/dolphin/tests/xcm_mock/mod.rs @@ -0,0 +1,130 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +pub mod parachain; +pub mod relay_chain; + +use frame_support::traits::GenesisBuild; +use polkadot_parachain::primitives::Id as ParaId; +use sp_runtime::traits::AccountIdConversion; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; +pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); +pub const INITIAL_BALANCE: u128 = 10_000_000_000_000_000; + +decl_test_parachain! { + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } +} + +decl_test_parachain! { + pub struct ParaB { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(2), + } +} + +decl_test_parachain! { + pub struct ParaC { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(3), + } +} + +decl_test_relay_chain! { + pub struct Relay { + Runtime = relay_chain::Runtime, + XcmConfig = relay_chain::XcmExecutorConfig, + new_ext = relay_ext(), + } +} + +decl_test_network! { + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + (2, ParaB), + (3, ParaC), + ], + } +} + +pub fn para_account_id(id: u32) -> relay_chain::AccountId { + ParaId::from(id).into_account() +} + +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { + use parachain::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let parachain_info_config = parachain_info::GenesisConfig { + parachain_id: para_id.into(), + }; + >::assimilate_storage( + ¶chain_info_config, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext +} + +pub fn relay_ext() -> sp_io::TestExternalities { + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (para_account_id(1), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub type RelayChainPalletXcm = pallet_xcm::Pallet; +pub type RelayBalances = pallet_balances::Pallet; +pub type ParachainPalletXcm = pallet_xcm::Pallet; diff --git a/runtime/dolphin/tests/xcm_mock/parachain.rs b/runtime/dolphin/tests/xcm_mock/parachain.rs new file mode 100644 index 000000000..e19bf8562 --- /dev/null +++ b/runtime/dolphin/tests/xcm_mock/parachain.rs @@ -0,0 +1,637 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +//! Parachain runtime mock. + +use codec::{Decode, Encode}; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU32, Everything, Nothing}, + weights::{constants::WEIGHT_PER_SECOND, Weight}, +}; +use frame_system::EnsureRoot; +use pallet_asset_manager::AssetMetadata; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{Hash, IdentityLookup}, + AccountId32, +}; +use sp_std::{convert::TryFrom, prelude::*}; + +use manta_primitives::{ + AssetIdLocationConvert, AssetLocation, FirstAssetTrader, IsNativeConcrete, MultiNativeAsset, +}; +use pallet_xcm::XcmPassthrough; +use polkadot_core_primitives::BlockNumber as RelayBlockNumber; +use polkadot_parachain::primitives::{ + DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, +}; +use xcm::{latest::prelude::*, Version as XcmVersion, VersionedXcm}; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteAssetId, + CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + FungiblesAdapter, LocationInverter, ParentIsDefault, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, +}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; +use xcm_simulator::Get; + +pub use manta_primitives::{AssetId, AssetRegistarMetadata, AssetStorageMetadata}; +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = WEIGHT_PER_SECOND / 4; + pub const ReservedDmpWeight: Weight = WEIGHT_PER_SECOND / 4; +} + +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub Ancestry: MultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub SelfReserve: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into()))); +} + +parameter_types! { + pub const AssetDeposit: Balance = 0; // Does not really matter as this will be only called by root + pub const AssetAccountDeposit: Balance = 0; + pub const ApprovalDeposit: Balance = 0; + pub const AssetsStringLimit: u32 = 50; + pub const MetadataDepositBase: Balance = 0; + pub const MetadataDepositPerByte: Balance = 0; +} + +impl pallet_assets::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = AssetId; + type Currency = Balances; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = AssetAccountDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the default `AccountId`. + ParentIsDefault, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + AccountId32Aliases, +); + +/// This is the type to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. +/// It uses some Rust magic macro to do the pattern matching sequentially. +/// There is an `OriginKind` which can biases the kind of local `Origin` it will become. +pub type XcmOriginToCallOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // If the incoming XCM origin is of type `AccountId32` and the Network is Network::Any + // or `RelayNetwork`, convert it to a Native 32 byte account. + SignedAccountId32AsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognised. + SiblingParachainAsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +parameter_types! { + pub const UnitWeightCost: Weight = 1; + // Used in native traders + // This might be able to skipped. + // We have to use `here()` because of reanchoring logic + pub ParaTokenPerSecond: (xcm::v1::AssetId, u128) = (Concrete(MultiLocation::here()), 1_000_000_000); + pub const MaxInstructions: u32 = 100; +} + +/// Transactor for native currency, i.e. implements `fungible` trait +pub type LocalAssetTransactor = XcmCurrencyAdapter< + // Transacting native currency, i.e. MANTA, KMA, DOL + Balances, + IsNativeConcrete, + LocationToAccountId, + AccountId, + (), +>; + +/// Transactor for currency in pallet-assets, i.e. implements `fungibles` trait +pub type FungiblesTransactor = FungiblesAdapter< + Assets, + ConvertedConcreteAssetId< + AssetId, + Balance, + AssetIdLocationConvert, + JustTry, + >, + // "default" implementation of converting a `MultiLocation` to an `AccountId` + LocationToAccountId, + AccountId, + // No teleport support. + Nothing, + // No teleport tracking. + (), +>; + +pub type XcmRouter = super::ParachainXcmRouter; +pub type Barrier = AllowUnpaidExecutionFrom; + +parameter_types! { + /// Xcm fees will go to the asset manager (we don't implement treasury yet) + pub XcmFeesAccount: AccountId = AssetManager::account_id(); +} + +pub type XcmFeesToAccount = manta_primitives::XcmFeesToAccount< + Assets, + ConvertedConcreteAssetId< + AssetId, + Balance, + AssetIdLocationConvert, + JustTry, + >, + AccountId, + XcmFeesAccount, +>; + +pub struct XcmExecutorConfig; +impl Config for XcmExecutorConfig { + type Call = Call; + type XcmSender = XcmRouter; + // Defines how to Withdraw and Deposit instruction work + // Under the hood, substrate framework will do pattern matching in macro, + // as a result, the order of the following tuple matters. + type AssetTransactor = (LocalAssetTransactor, FungiblesTransactor); + type OriginConverter = XcmOriginToCallOrigin; + // Combinations of (Location, Asset) pairs which we trust as reserves. + type IsReserve = MultiNativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + // Trader is the means to purchasing weight credit for XCM execution. + // We define two traders: + // The first one will charge parachain's native currency, who's `MultiLocation` + // is defined in `SelfReserve`. + // The second one will charge the first asset in the MultiAssets with pre-defined rate + // i.e. units_per_second in `AssetManager` + type Trader = ( + FixedRateOfFungible, + FirstAssetTrader, + ); + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + // This is needed for the version change notifier work + type SubscriptionService = PolkadotXcm; +} + +// Pallet to provide the version, used to test runtime upgrade version changes +#[frame_support::pallet] +pub mod mock_version_changer { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn current_version)] + pub(super) type CurrentVersion = StorageValue<_, XcmVersion, ValueQuery>; + + impl Get for Pallet { + fn get() -> XcmVersion { + Self::current_version() + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + VersionChanged(XcmVersion), + } + + impl Pallet { + pub fn set_version(version: XcmVersion) { + CurrentVersion::::put(version); + Self::deposit_event(Event::VersionChanged(version)); + } + } +} + +#[frame_support::pallet] +pub mod mock_msg_queue { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + // without storage info is a work around + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn parachain_id)] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn received_dmp)] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + let location = (1, Parachain(sender.into())); + match T::XcmExecutor::execute_xcm(location, xcm, max_weight) { + Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), + Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), + // As far as the caller is concerned, this was dispatched without error, so + // we just report the weight used. + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + } + } + Err(()) => ( + Err(XcmError::UnhandledXcmVersion), + Event::BadVersion(Some(hash)), + ), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = &data_ref[..]; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = VersionedXcm::::decode(&mut remaining_fragments) { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let id = sp_io::hashing::blake2_256(&data[..]); + let maybe_msg = + VersionedXcm::::decode(&mut &data[..]).map(Xcm::::try_from); + match maybe_msg { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + } + Ok(Err(())) => { + Self::deposit_event(Event::UnsupportedVersion(id)); + } + Ok(Ok(x)) => { + let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), limit); + >::append(x); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + } + } + } + limit + } + } +} + +impl mock_msg_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; +} + +impl mock_version_changer::Config for Runtime { + type Event = Event; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + // Do not allow teleports + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = XcmVersioner; +} + +impl AssetMetadata for AssetRegistarMetadata { + fn min_balance(&self) -> Balance { + self.min_balance + } + + fn is_sufficient(&self) -> bool { + self.is_sufficient + } +} + +pub struct AssetRegistrar; +use frame_support::pallet_prelude::DispatchResult; +impl pallet_asset_manager::AssetRegistrar for AssetRegistrar { + fn create_asset( + asset_id: AssetId, + min_balance: Balance, + metadata: AssetStorageMetadata, + is_sufficient: bool, + ) -> DispatchResult { + Assets::force_create( + Origin::root(), + asset_id, + AssetManager::account_id(), + is_sufficient, + min_balance, + )?; + + Assets::force_set_metadata( + Origin::root(), + asset_id, + metadata.name, + metadata.symbol, + metadata.decimals, + metadata.is_frozen, + ) + } + + fn update_asset_metadata(asset_id: AssetId, metadata: AssetStorageMetadata) -> DispatchResult { + Assets::force_set_metadata( + Origin::root(), + asset_id, + metadata.name, + metadata.symbol, + metadata.decimals, + metadata.is_frozen, + ) + } +} +impl pallet_asset_manager::Config for Runtime { + type Event = Event; + type Balance = Balance; + type AssetId = AssetId; + type AssetRegistrarMetadata = AssetRegistarMetadata; + type StorageMetadata = AssetStorageMetadata; + type AssetLocation = AssetLocation; + type AssetRegistrar = AssetRegistrar; + type ModifierOrigin = EnsureRoot; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; +} + +// We wrap AssetId for XToken +#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +pub enum CurrencyId { + MantaCurrency(AssetId), +} + +pub struct CurrencyIdtoMultiLocation(sp_std::marker::PhantomData); +impl sp_runtime::traits::Convert> + for CurrencyIdtoMultiLocation +where + AssetXConverter: xcm_executor::traits::Convert, +{ + fn convert(currency: CurrencyId) -> Option { + match currency { + CurrencyId::MantaCurrency(asset_id) => match AssetXConverter::reverse_ref(&asset_id) { + Ok(location) => Some(location), + Err(_) => None, + }, + } + } +} + +parameter_types! { + pub const BaseXcmWeight: Weight = 100; + pub const MaxAssetsForTransfer: usize = 2; +} + +// The XCM message wrapper wrapper +impl orml_xtokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type CurrencyId = CurrencyId; + type AccountIdToMultiLocation = manta_primitives::AccountIdToMultiLocation; + type CurrencyIdConvert = + CurrencyIdtoMultiLocation>; + type XcmExecutor = XcmExecutor; + type SelfLocation = SelfReserve; + type Weigher = xcm_builder::FixedWeightBounds; + type BaseXcmWeight = BaseXcmWeight; + type LocationInverter = LocationInverter; + type MaxAssetsForTransfer = MaxAssetsForTransfer; +} + +impl parachain_info::Config for Runtime {} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub const PALLET_ASSET_INDEX: u8 = 1; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, + Assets: pallet_assets::{Pallet, Storage, Event} = 1, + AssetManager: pallet_asset_manager::{Pallet, Call, Storage, Event} = 2, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 3, + MsgQueue: mock_msg_queue::{Pallet, Storage, Event} = 4, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 5, + XTokens: orml_xtokens::{Pallet, Call, Event, Storage} = 6, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 7, + XcmVersioner: mock_version_changer::{Pallet, Storage, Event} = 8, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 9, + } +); + +pub(crate) fn para_events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| Some(e)) + .collect::>() +} + +use frame_support::traits::{OnFinalize, OnInitialize, OnRuntimeUpgrade}; +pub(crate) fn on_runtime_upgrade() { + PolkadotXcm::on_runtime_upgrade(); +} + +pub(crate) fn para_roll_to(n: u64) { + while System::block_number() < n { + PolkadotXcm::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + PolkadotXcm::on_initialize(System::block_number()); + } +} diff --git a/runtime/dolphin/tests/xcm_mock/relay_chain.rs b/runtime/dolphin/tests/xcm_mock/relay_chain.rs new file mode 100644 index 000000000..c990e9938 --- /dev/null +++ b/runtime/dolphin/tests/xcm_mock/relay_chain.rs @@ -0,0 +1,233 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +//! Relay chain runtime mock. + +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU32, Everything, Nothing}, + weights::Weight, +}; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; + +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared, ump}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + TakeWeightCredit, +}; +use xcm_executor::{Config, XcmExecutor}; +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_utility::Config for Runtime { + type Event = Event; + type Call = Call; + type WeightInfo = (); + type PalletsOrigin = OriginCaller; +} + +impl shared::Config for Runtime {} + +impl configuration::Config for Runtime { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub const KsmLocation: MultiLocation = Here.into(); + pub const KusamaNetwork: NetworkId = NetworkId::Kusama; + pub const AnyNetwork: NetworkId = NetworkId::Any; + pub Ancestry: MultiLocation = Here.into(); + pub UnitWeightCost: Weight = 1_000; +} + +pub type SovereignAccountOf = ( + ChildParachainConvertsVia, + AccountId32Aliases, +); + +pub type LocalAssetTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const BaseXcmWeight: Weight = 1_000; + pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub const MaxInstructions: u32 = 100; +} + +pub type XcmRouter = super::RelayChainXcmRouter; +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + // The following is purely for testing ump + AllowUnpaidExecutionFrom, +); + +pub struct XcmExecutorConfig; +impl Config for XcmExecutorConfig { + type Call = Call; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally... + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; +} + +parameter_types! { + pub const FirstMessageFactorPercent: u64 = 100; +} + +impl ump::Config for Runtime { + type Event = Event; + type UmpSink = ump::XcmSink, Runtime>; + type FirstMessageFactorPercent = FirstMessageFactorPercent; + type ExecuteOverweightOrigin = frame_system::EnsureRoot; +} + +impl origin::Config for Runtime {} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + ParasOrigin: origin::{Pallet, Origin}, + ParasUmp: ump::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + Utility: pallet_utility::{Pallet, Call, Event}, + } +); + +pub(crate) fn relay_events() -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| Some(e)) + .collect::>() +} + +use frame_support::traits::{OnFinalize, OnInitialize}; +pub(crate) fn relay_roll_to(n: u64) { + while System::block_number() < n { + XcmPallet::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + XcmPallet::on_initialize(System::block_number()); + } +} diff --git a/runtime/dolphin/tests/xcm_tests.rs b/runtime/dolphin/tests/xcm_tests.rs new file mode 100644 index 000000000..3ed0a5d0f --- /dev/null +++ b/runtime/dolphin/tests/xcm_tests.rs @@ -0,0 +1,1961 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +//! Simulation Tests for XCM + +mod xcm_mock; + +use codec::Encode; +use frame_support::{assert_ok, weights::constants::WEIGHT_PER_SECOND}; +use manta_primitives::AssetLocation; +use xcm::{latest::prelude::*, v2::Response, VersionedMultiLocation, WrapVersion}; +use xcm_mock::{parachain::PALLET_ASSET_INDEX, *}; +use xcm_simulator::TestExt; + +use crate::xcm_mock::parachain::{AssetManager, ParaTokenPerSecond}; + +// `reserved_transfer_asset` contains the following 4 instructions +// 1. ReserveAssetDeposited(assets.clone()), +// 2. ClearOrigin, +// 3. BuyExecution { fees, weight_limit: Limited(0) }, +// 4. DepositAsset { assets: Wild(All), max_assets, beneficiary }, +// each instruction's weight is 1000, thus, the total weight is 4000 +const RESERVE_TRANSFER_WEIGHT: u64 = 4000; + +fn calculate_fee(units_per_seconds: u128, weight: u64) -> u128 { + units_per_seconds * (weight as u128) / (WEIGHT_PER_SECOND as u128) +} + +// Helper function for forming buy execution message +fn buy_execution(fees: impl Into) -> Instruction { + BuyExecution { + fees: fees.into(), + weight_limit: Unlimited, + } +} + +#[test] +fn dmp() { + MockNet::reset(); + + let remark = parachain::Call::System( + frame_system::Call::::remark_with_event { + remark: vec![1, 2, 3], + }, + ); + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::send_xcm( + Here, + Parachain(1), + Xcm(vec![Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }]), + )); + }); + + ParaA::execute_with(|| { + use parachain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); + }); +} + +#[test] +fn ump() { + MockNet::reset(); + + let remark = relay_chain::Call::System( + frame_system::Call::::remark_with_event { + remark: vec![1, 2, 3], + }, + ); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm( + Here, + Parent, + Xcm(vec![Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }]), + )); + }); + + Relay::execute_with(|| { + use relay_chain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); + }); +} + +#[test] +fn xcmp() { + MockNet::reset(); + + let remark = parachain::Call::System( + frame_system::Call::::remark_with_event { + remark: vec![1, 2, 3], + }, + ); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm( + Here, + (Parent, Parachain(2)), + Xcm(vec![Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }]), + )); + }); + + ParaB::execute_with(|| { + use parachain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked { .. })))); + }); +} + +#[test] +fn reserve_transfer_relaychain_to_parachain_a() { + MockNet::reset(); + + let relay_asset_id: parachain::AssetId = 0; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + + // Register relay chain asset in parachain A + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + // we don't charge anything during test + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + 0u128 + )); + }); + + let withdraw_amount = 123; + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new( + X1(AccountId32 { + network: Any, + id: ALICE.into() + }) + .into() + .into() + ), + Box::new((Here, withdraw_amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + withdraw_amount + ); + }); + + ParaA::execute_with(|| { + // free execution, full amount received + assert_eq!( + pallet_assets::Pallet::::balance(relay_asset_id, &ALICE.into()), + withdraw_amount + ); + }); +} + +#[test] +fn reserve_transfer_relaychain_to_parachain_a_then_back() { + MockNet::reset(); + + let relay_asset_id: parachain::AssetId = 0; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + + // Register relay chain asset in parachain A + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + // we don't charge anything + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + 0u128 + )); + }); + + let amount = 123; + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new( + X1(AccountId32 { + network: Any, + id: ALICE.into() + }) + .into() + .into() + ), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + parachain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + ); + }); + + ParaA::execute_with(|| { + // free execution, full amount received + assert_eq!( + pallet_assets::Pallet::::balance(relay_asset_id, &ALICE.into()), + amount + ); + }); + + // Checking the balance of relay chain before sending token back + let mut balance_before_sending = 0; + Relay::execute_with(|| { + balance_before_sending = RelayBalances::free_balance(&ALICE); + }); + + let dest = MultiLocation { + parents: 1, + interior: X1(AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }), + }; + + ParaA::execute_with(|| { + // free execution, full amount received + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(relay_asset_id), + amount, + Box::new(VersionedMultiLocation::V1(dest)), + 40000 + )); + }); + + ParaA::execute_with(|| { + // free execution, this will drain the parachain asset account + assert_eq!(parachain::Assets::balance(relay_asset_id, &ALICE.into()), 0); + }); + + Relay::execute_with(|| { + // free execution, full amount received + assert_eq!( + RelayBalances::free_balance(&ALICE), + balance_before_sending + amount + ); + }); +} + +#[test] +fn send_para_a_native_asset_to_para_b() { + MockNet::reset(); + + // We use an opinioned source location here: + // Ideally, we could use `here()`, however, we always prefer to use the location from + // `root` when possible. + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + let amount = 100u128; + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: false, + }; + + // Register ParaA native asset in ParaB + ParaB::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + // Register ParaA native asset in ParaA + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location) + ); + }); + + let dest = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + // Transfer ParaA balance to B + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(dest)), + 800000 + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + // Make sure B received the token + ParaB::execute_with(|| { + // free execution, full amount received + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); +} + +#[test] +fn send_para_a_custom_asset_to_para_b() { + let a_currency_id: u32 = 0; + let amount = 321; + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaADoge".to_vec(), + symbol: b"Doge".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X3( + Parachain(1), + PalletInstance(PALLET_ASSET_INDEX), + GeneralIndex(0), + ), + ))); + + // register a_currency in ParaA, ParaB + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + // we have to do this in order to mint asset to alice on A + assert_ok!(parachain::Assets::force_asset_status( + parachain::Origin::root(), + 0, + ALICE.into(), + ALICE.into(), + ALICE.into(), + ALICE.into(), + 1, + true, + false, + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaB::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location) + ); + }); + + let alice_on_b = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + ParaA::execute_with(|| { + // Force customized asset balance for Alice + assert_ok!(parachain::Assets::mint( + parachain::Origin::signed(ALICE.into()), + 0, + ALICE.into(), + INITIAL_BALANCE + )); + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_b)), + 800000 + )); + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + // Make sure B received the token + ParaB::execute_with(|| { + // free execution, full amount received + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); +} + +#[test] +fn send_para_a_native_asset_para_b_and_then_send_back() { + MockNet::reset(); + + // para a native asset location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + // a's currency id in para a, para b, and para c + let a_currency_id = 0u32; + let amount = 5000u128; + let weight = 800000u64; + let fee_on_b_when_send_back = calculate_fee(ParaTokenPerSecond::get().1, weight); + assert!(fee_on_b_when_send_back < amount); + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + + // register a_currency in ParaA, ParaB + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaB::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location) + ); + }); + + let alice_on_b = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_b)), + 800000 + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + // Make sure B received the token + ParaB::execute_with(|| { + // free execution, full amount received + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); + + let alice_on_a = MultiLocation { + parents: 1, + interior: X2( + Parachain(1), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + // Send wrapped a back to a + ParaB::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_a)), + 800000 + )); + assert_eq!(parachain::Assets::balance(a_currency_id, &ALICE.into()), 0); + }); + + // make sure that a received the token + ParaA::execute_with(|| { + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - fee_on_b_when_send_back + ) + }); +} + +#[test] +fn send_para_a_native_asset_from_para_b_to_para_c() { + MockNet::reset(); + + // para a asset location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + let amount = 8888u128; + let weight = 800_000u64; + let fee_at_reserve = calculate_fee(ParaTokenPerSecond::get().1, weight); + assert!(amount >= fee_at_reserve * 2 as u128); + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: false, + }; + + // register a_currency in ParaA, ParaB and ParaC + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + // we need to change this on/after v0.9.16 + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaB::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaC::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + // A send B some token + let alice_on_b = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_b.clone())), + 800000 + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + ParaB::execute_with(|| { + // free execution, full amount received + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); + + // B send C para A asset + let alice_on_c = MultiLocation { + parents: 1, + interior: X2( + Parachain(3), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + ParaB::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_c)), + weight, + )); + assert_eq!(parachain::Assets::balance(a_currency_id, &ALICE.into()), 0); + }); + + // Make sure C received the token + ParaC::execute_with(|| { + // free execution, full amount received + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount - fee_at_reserve + ); + }); +} + +#[test] +fn receive_relay_asset_with_trader() { + MockNet::reset(); + + let relay_asset_id: parachain::AssetId = 0; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let amount = 666u128; + // We charge 10^9 as units per second on ParaA + let units_per_second = 1_000_000_000u128; + let fee = calculate_fee(units_per_second, RESERVE_TRANSFER_WEIGHT); + assert!(fee > 0); + + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + units_per_second + )); + }); + + let dest: MultiLocation = AccountId32 { + network: Any, + id: ALICE.into(), + } + .into(); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new(VersionedMultiLocation::V1(dest).clone().into()), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + ); + }); + + ParaA::execute_with(|| { + // ALICE gets amount - fee + assert_eq!( + parachain::Assets::balance(relay_asset_id, &ALICE.into()), + amount - fee + ); + // Fee sink gets fee + assert_eq!( + parachain::Assets::balance(relay_asset_id, AssetManager::account_id()), + fee + ); + }); +} + +#[test] +fn send_para_a_asset_to_para_b_with_trader_and_fee() { + MockNet::reset(); + + // para a balance location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + let amount = 222u128; + let units_per_second = 1_250_000u128; + let dest_weight = 800_000u64; + let fee = calculate_fee(units_per_second, dest_weight); + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + + // Register ParaA native asset in ParaA + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + // This need to be changed starting from v0.9.16 + // need to use something like MultiLocation { parents: 0, interior: here} instead + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaB::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + units_per_second + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + let dest = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + // Transfer ParaA balance to B + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer_with_fee( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + 1, + Box::new(VersionedMultiLocation::V1(dest)), + dest_weight, + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount - fee + ) + }); + + ParaB::execute_with(|| { + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); +} + +#[test] +fn send_para_a_asset_from_para_b_to_para_c_with_trader() { + MockNet::reset(); + + // para a balance location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + let mut amount = 8888u128; + let units_per_second_at_b = 1_250_000u128; + let dest_weight = 800_000u64; + let fee_at_b = calculate_fee(units_per_second_at_b, dest_weight); + let fee_at_a = calculate_fee(ParaTokenPerSecond::get().1, dest_weight); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + + // register a_currency in ParaA, ParaB and ParaC + + // we don't charge any fee in A + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + // We set units_per_seconds on ParaB to 1_250_000, + ParaB::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + units_per_second_at_b + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaC::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + units_per_second_at_b + )); + assert_eq!( + Some(a_currency_id), + parachain::AssetManager::location_asset_id(source_location.clone()) + ); + }); + + // A send B some token + let alice_on_b = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + assert!(amount >= fee_at_b); + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_b.clone())), + dest_weight + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + ParaB::execute_with(|| { + amount = amount - fee_at_b; + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); + + // B send C para A asset + let alice_on_c = MultiLocation { + parents: 1, + interior: X2( + Parachain(3), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + assert!(amount >= fee_at_b + fee_at_a); + ParaB::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(alice_on_c)), + dest_weight + )); + assert_eq!(parachain::Assets::balance(a_currency_id, &ALICE.into()), 0); + }); + + // Make sure C received the token + ParaC::execute_with(|| { + amount = amount - fee_at_b - fee_at_a; + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + amount + ); + }); +} + +#[test] +fn receive_relay_with_insufficient_fee_payment() { + MockNet::reset(); + + let relay_asset_id: parachain::AssetId = 0; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let amount = 20u128; + // We charge 2 x 10^10 as units per second on ParaA + let units_per_second = 20_000_000_000u128; + let fee = calculate_fee(units_per_second, RESERVE_TRANSFER_WEIGHT); + assert!(fee > amount); + + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + units_per_second + )); + }); + + let dest: MultiLocation = AccountId32 { + network: Any, + id: ALICE.into(), + } + .into(); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new(VersionedMultiLocation::V1(dest).clone().into()), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + ); + }); + + ParaA::execute_with(|| { + // ALICE gets nothing + assert_eq!(parachain::Assets::balance(relay_asset_id, &ALICE.into()), 0); + // Asset manager gets nothing, all balance stucks + assert_eq!( + parachain::Assets::balance(relay_asset_id, AssetManager::account_id()), + 0 + ); + }); +} + +#[test] +fn receive_relay_should_fail_without_specifying_units_per_second() { + MockNet::reset(); + + let relay_asset_id: parachain::AssetId = 0; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let amount = 333u128; + + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + }); + + let dest: MultiLocation = AccountId32 { + network: Any, + id: ALICE.into(), + } + .into(); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new(VersionedMultiLocation::V1(dest).clone().into()), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + ); + }); + + ParaA::execute_with(|| { + // ALICE gets nothing + assert_eq!(parachain::Assets::balance(relay_asset_id, &ALICE.into()), 0); + // Asset manager gets nothing, all balance stucks + assert_eq!( + parachain::Assets::balance(relay_asset_id, AssetManager::account_id()), + 0 + ); + }); +} + +#[test] +fn send_para_a_asset_to_para_b_with_insufficient_fee() { + MockNet::reset(); + + // para a balance location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + let amount = 15u128; + let units_per_second = 20_000_000u128; + let dest_weight = 800_000u64; + let fee = calculate_fee(units_per_second, dest_weight); + assert!(fee > amount); + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + + // Register ParaA native asset in ParaA + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + // This need to be changed starting from v0.9.16 + // need to use something like MultiLocation { parents: 0, interior: here} instead + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaB::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + units_per_second + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + let dest = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + // Transfer ParaA balance to B + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(dest)), + dest_weight, + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + // Alice on B should receive nothing since the fee is insufficient + ParaB::execute_with(|| { + assert_eq!(parachain::Assets::balance(a_currency_id, &ALICE.into()), 0); + }); +} + +#[test] +fn send_para_a_asset_to_para_b_without_specifying_units_per_second() { + MockNet::reset(); + + // para a balance location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + let amount = 567u128; + let dest_weight = 800_000u64; + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + + // Register ParaA native asset in ParaA + ParaA::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + // This need to be changed starting from v0.9.16 + // need to use something like MultiLocation { parents: 0, interior: here} instead + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + // We don't specify units_per_second on B + ParaB::execute_with(|| { + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + source_location.clone(), + asset_metadata.clone() + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + let dest = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + // Transfer ParaA balance to B + ParaA::execute_with(|| { + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + amount, + Box::new(VersionedMultiLocation::V1(dest)), + dest_weight, + )); + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - amount + ) + }); + + // Alice on B should receive nothing since we didn't specify the unit per second + ParaB::execute_with(|| { + assert_eq!(parachain::Assets::balance(a_currency_id, &ALICE.into()), 0); + }); +} + +#[test] +fn receive_asset_with_is_sufficient_false() { + MockNet::reset(); + + let new_account = [5u8; 32]; + let relay_asset_id = 0u32; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: false, + }; + let amount = 123u128; + + // register relay asset in parachain A + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + // we don't charge anything during test + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + 0u128 + )); + }); + + let dest: MultiLocation = AccountId32 { + network: Any, + id: new_account.into(), + } + .into(); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new(VersionedMultiLocation::V1(dest.clone()).clone().into()), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + ); + }); + + // parachain should not have received assets + ParaA::execute_with(|| { + assert_eq!( + parachain::Assets::balance(relay_asset_id, &new_account.into()), + 0 + ); + }); + + // Send native token to fresh_account + ParaA::execute_with(|| { + assert_ok!(parachain::Balances::transfer( + parachain::Origin::signed(ALICE.into()), + new_account.into(), + 100 + )); + }); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new(VersionedMultiLocation::V1(dest).clone().into()), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + amount + ); + }); + + // parachain should not have received assets + ParaA::execute_with(|| { + println!( + "fresh account bal: {}", + parachain::Assets::balance(relay_asset_id, &new_account.into()) + ); + }); +} + +#[test] +fn receive_asset_with_is_sufficient_true() { + MockNet::reset(); + + let new_account = [5u8; 32]; + let relay_asset_id = 0u32; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + let amount = 123u128; + + // register relay asset in parachain A + ParaA::execute_with(|| { + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + // we don't charge anything during test + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + 0u128 + )); + }); + + let dest: MultiLocation = AccountId32 { + network: Any, + id: new_account.into(), + } + .into(); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(X1(Parachain(1)).into().into()), + Box::new(VersionedMultiLocation::V1(dest.clone()).clone().into()), + Box::new((Here, amount).into()), + 0, + )); + assert_eq!( + relay_chain::Balances::free_balance(¶_account_id(1)), + INITIAL_BALANCE + amount + ); + }); + + // parachain should have received assets + ParaA::execute_with(|| { + assert_eq!( + parachain::Assets::balance(relay_asset_id, &new_account.into()), + amount + ); + }); +} + +/// Scenario: +/// A parachain transfers funds on the relay chain to another parachain account. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn withdraw_and_deposit() { + MockNet::reset(); + + let send_amount = 10; + + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + buy_execution((Here, send_amount)), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(2).into(), + }, + ]); + // Send withdraw and deposit + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(1)), + INITIAL_BALANCE - send_amount + ); + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(2)), + send_amount + ); + }); +} + +/// Scenario: +/// A parachain wants to be notified that a transfer worked correctly. +/// It sends a `QueryHolding` after the deposit to get notified on success. +/// +/// Asserts that the balances are updated correctly and the expected XCM is sent. +#[test] +fn query_holding() { + MockNet::reset(); + + let send_amount = 10; + let query_id_set = 1234; + + // Send a message which fully succeeds on the relay chain + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + buy_execution((Here, send_amount)), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(2).into(), + }, + QueryHolding { + query_id: query_id_set, + dest: Parachain(1).into(), + assets: All.into(), + max_response_weight: 1_000_000_000, + }, + ]); + // Send withdraw and deposit with query holding + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that transfer was executed + Relay::execute_with(|| { + // Withdraw executed + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(1)), + INITIAL_BALANCE - send_amount + ); + // Deposit executed + assert_eq!( + relay_chain::Balances::free_balance(para_account_id(2)), + send_amount + ); + }); + + // Check that QueryResponse message was received + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: query_id_set, + response: Response::Assets(MultiAssets::new()), + max_weight: 1_000_000_000, + }])], + ); + }); +} + +#[test] +fn test_versioning_on_runtime_upgrade_with_relay() { + MockNet::reset(); + + let relay_asset_id: parachain::AssetId = 0; + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::parent())); + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"Kusama".to_vec(), + symbol: b"KSM".to_vec(), + decimals: 12, + min_balance: 1u128, + evm_address: None, + is_frozen: false, + is_sufficient: true, + }; + + // register relay asset in parachain A (XCM version 1) + ParaA::execute_with(|| { + parachain::XcmVersioner::set_version(1); + assert_ok!(parachain::AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + // we don't charge anything during test + assert_ok!(parachain::AssetManager::set_units_per_second( + parachain::Origin::root(), + relay_asset_id, + 0u128 + )); + }); + + let response = Response::Version(2); + + // This is irrelevant, nothing will be done with this message, + // but we need to pass a message as an argument to trigger the storage change + let mock_message: Xcm<()> = Xcm(vec![QueryResponse { + query_id: 0, + response, + max_weight: 0, + }]); + + let dest: MultiLocation = AccountId32 { + network: Any, + id: ALICE.into(), + } + .into(); + + Relay::execute_with(|| { + // This sets the default version, for not known destinations + assert_ok!(RelayChainPalletXcm::force_default_xcm_version( + relay_chain::Origin::root(), + Some(2) + )); + + // Wrap version, which sets VersionedStorage + // This is necessary because the mock router does not use wrap_version, but + // this is not necessary in prod. + // more specifically, this will trigger `note_unknown_version` to put the + // version to `VersionDiscoveryQueue` on relay-chain's pallet-xcm + assert_ok!(::wrap_version( + &Parachain(1).into(), + mock_message + )); + + // Transfer assets. Since it is an unknown destination, it will query for version + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + Box::new(Parachain(1).into().into()), + Box::new(VersionedMultiLocation::V1(dest).clone().into()), + Box::new((Here, 123).into()), + 0, + )); + + // Let's advance the relay. This should trigger the subscription message + relay_chain::relay_roll_to(2); + + // queries should have been updated + assert!(RelayChainPalletXcm::query(0).is_some()); + }); + + let expected_supported_version: relay_chain::Event = + pallet_xcm::Event::SupportedVersionChanged( + MultiLocation { + parents: 0, + interior: X1(Parachain(1)), + }, + 1, + ) + .into(); + + Relay::execute_with(|| { + // Assert that the events vector contains the version change + assert!(relay_chain::relay_events().contains(&expected_supported_version)); + }); + + let expected_version_notified: parachain::Event = pallet_xcm::Event::VersionChangeNotified( + MultiLocation { + parents: 1, + interior: Here, + }, + 2, + ) + .into(); + + // ParaA changes version to 2, and calls on_runtime_upgrade. This should notify the targets + // of the new version change + ParaA::execute_with(|| { + // Set version + parachain::XcmVersioner::set_version(2); + // Do runtime upgrade + parachain::on_runtime_upgrade(); + // Initialize block, to call on_initialize and notify targets + parachain::para_roll_to(2); + // Expect the event in the parachain + assert!(parachain::para_events().contains(&expected_version_notified)); + }); + + // This event should have been seen in the relay + let expected_supported_version_2: relay_chain::Event = + pallet_xcm::Event::SupportedVersionChanged( + MultiLocation { + parents: 0, + interior: X1(Parachain(1)), + }, + 2, + ) + .into(); + + Relay::execute_with(|| { + // Assert that the events vector contains the new version change + assert!(relay_chain::relay_events().contains(&expected_supported_version_2)); + }); +} + +#[test] +fn test_automatic_versioning_on_runtime_upgrade_with_para_b() { + MockNet::reset(); + + // para a balance location + let source_location = AssetLocation(VersionedMultiLocation::V1(MultiLocation::new( + 1, + X1(Parachain(1)), + ))); + let a_currency_id = 0u32; + + let asset_metadata = parachain::AssetRegistarMetadata { + name: b"ParaAToken".to_vec(), + symbol: b"ParaA".to_vec(), + decimals: 18, + evm_address: None, + min_balance: 1, + is_frozen: false, + is_sufficient: true, + }; + let response = Response::Version(2); + + // This is irrelevant, nothing will be done with this message, + // but we need to pass a message as an argument to trigger the storage change + let mock_message: Xcm<()> = Xcm(vec![QueryResponse { + query_id: 0, + response, + max_weight: 0, + }]); + + ParaA::execute_with(|| { + // advertised version + parachain::XcmVersioner::set_version(2); + // Register ParaA native asset in ParaA + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + // This need to be changed starting from v0.9.16 + // need to use something like MultiLocation { parents: 0, interior: here} instead + source_location.clone(), + asset_metadata.clone() + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + assert_eq!( + Some(a_currency_id), + AssetManager::location_asset_id(source_location.clone()) + ); + }); + + ParaB::execute_with(|| { + // Let's try with v0 + parachain::XcmVersioner::set_version(0); + + assert_ok!(AssetManager::register_asset( + parachain::Origin::root(), + source_location, + asset_metadata + )); + assert_ok!(AssetManager::set_units_per_second( + parachain::Origin::root(), + a_currency_id, + 0u128 + )); + }); + + ParaA::execute_with(|| { + // This sets the default version, for not known destinations + assert_ok!(ParachainPalletXcm::force_default_xcm_version( + parachain::Origin::root(), + Some(2) + )); + // Wrap version, which sets VersionedStorage + assert_ok!(::wrap_version( + &MultiLocation::new(1, X1(Parachain(2))).into(), + mock_message + )); + + parachain::para_roll_to(2); + + // queries should have been updated + assert!(ParachainPalletXcm::query(0).is_some()); + }); + + let expected_supported_version: parachain::Event = pallet_xcm::Event::SupportedVersionChanged( + MultiLocation { + parents: 1, + interior: X1(Parachain(2)), + }, + 0, + ) + .into(); + + ParaA::execute_with(|| { + // Assert that the events vector contains the version change + assert!(parachain::para_events().contains(&expected_supported_version)); + }); + + // Let's ensure talking in v0 works + let dest = MultiLocation { + parents: 1, + interior: X2( + Parachain(2), + AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }, + ), + }; + + ParaA::execute_with(|| { + // free execution, full amount received + assert_ok!(parachain::XTokens::transfer( + parachain::Origin::signed(ALICE.into()), + parachain::CurrencyId::MantaCurrency(a_currency_id), + 100, + Box::new(VersionedMultiLocation::V1(dest)), + 80 + )); + // free execution, full amount received + assert_eq!( + parachain::Balances::free_balance(&ALICE.into()), + INITIAL_BALANCE - 100 + ); + }); + + ParaB::execute_with(|| { + // free execution, full amount received + assert_eq!( + parachain::Assets::balance(a_currency_id, &ALICE.into()), + 100 + ); + }); + + let expected_version_notified: parachain::Event = pallet_xcm::Event::VersionChangeNotified( + MultiLocation { + parents: 1, + interior: X1(Parachain(1)), + }, + 2, + ) + .into(); + + // ParaB changes version to 2, and calls on_runtime_upgrade. This should notify the targets + // of the new version change + ParaB::execute_with(|| { + // Set version + parachain::XcmVersioner::set_version(2); + // Do runtime upgrade + parachain::on_runtime_upgrade(); + // Initialize block, to call on_initialize and notify targets + parachain::para_roll_to(2); + // Expect the event in the parachain + assert!(parachain::para_events().contains(&expected_version_notified)); + }); + + // This event should have been seen in para A + let expected_supported_version_2: parachain::Event = + pallet_xcm::Event::SupportedVersionChanged( + MultiLocation { + parents: 1, + interior: X1(Parachain(2)), + }, + 2, + ) + .into(); + + // Para A should have received the version change + ParaA::execute_with(|| { + // Assert that the events vector contains the new version change + assert!(parachain::para_events().contains(&expected_supported_version_2)); + }); +} diff --git a/runtime/manta/Cargo.toml b/runtime/manta/Cargo.toml index ccc60f9db..3dd6efb2a 100644 --- a/runtime/manta/Cargo.toml +++ b/runtime/manta/Cargo.toml @@ -75,7 +75,7 @@ pallet-xcm = { git = 'https://github.com/paritytech/polkadot.git', default-featu # Self dependencies manta-primitives = { path = '../primitives', default-features = false } -pallet-tx-pause = { path = '../../pallets/pallet-tx-pause', default-features = false } +pallet-tx-pause = { path = '../../pallets/tx-pause', default-features = false } [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] diff --git a/runtime/primitives/Cargo.toml b/runtime/primitives/Cargo.toml index 19a94c92e..067d123d5 100644 --- a/runtime/primitives/Cargo.toml +++ b/runtime/primitives/Cargo.toml @@ -12,22 +12,36 @@ targets = ['x86_64-unknown-linux-gnu'] [dependencies] codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } +scale-info = { version = "1.0", default-features = false, features = [ "derive" ] } smallvec = "1.6.1" +log = "0.4.14" # Substrate primitives +frame-support = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } sp-consensus-aura = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } sp-core = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } sp-std = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } sp-io = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } sp-runtime = { git = 'https://github.com/paritytech/substrate.git', default-features = false, branch = "polkadot-v0.9.16" } +xcm-executor = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16"} +xcm = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16" } +xcm-builder = { git = 'https://github.com/paritytech/polkadot.git', default-features = false, branch = "release-v0.9.16"} [features] default = ["std"] std = [ 'codec/std', 'sp-consensus-aura/std', - 'sp-core/std', + 'scale-info/std', 'sp-io/std', 'sp-std/std', + 'log/std', + 'frame-support/std', + 'sp-consensus-aura/std', + 'sp-core/std', + 'sp-io/std', 'sp-runtime/std', + 'xcm-executor/std', + 'xcm-builder/std', + 'xcm/std', ] diff --git a/runtime/primitives/src/assets.rs b/runtime/primitives/src/assets.rs new file mode 100644 index 000000000..6fbbc78a2 --- /dev/null +++ b/runtime/primitives/src/assets.rs @@ -0,0 +1,136 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::H160; +use sp_std::{borrow::Borrow, marker::PhantomData, prelude::Vec}; + +///! Manta/Calamari/Dolphin Asset +use xcm::{ + v1::{Junctions, MultiLocation}, + VersionedMultiLocation, +}; + +/// The metadata of a Manta Asset +#[derive(Clone, Default, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +pub struct AssetRegistarMetadata { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub evm_address: Option, + pub is_frozen: bool, + pub min_balance: Balance, + /// `is_sufficient`: Whether a non-zero balance of this asset is deposit of sufficient + /// value to account for the state bloat associated with its balance storage. If set to + /// `true`, then non-zero balances may be stored without a `consumer` reference (and thus + /// an ED in the Balances pallet or whatever else is used to control user-account state + /// growth). + /// For example, if is_sufficient set to `false`, a fresh account cannot receive XCM tokens. + pub is_sufficient: bool, +} + +/// Asset storage metadata +/// Currently, `AssetStorageMetadata` is stored at `pallet-asset`. +#[derive(Clone, Default, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +pub struct AssetStorageMetadata { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub is_frozen: bool, +} + +impl From> for AssetStorageMetadata { + fn from(source: AssetRegistarMetadata) -> Self { + AssetStorageMetadata { + name: source.name, + symbol: source.symbol, + decimals: source.decimals, + is_frozen: source.is_frozen, + } + } +} + +#[derive(Clone, Eq, Debug, PartialEq, Encode, Decode, TypeInfo)] +pub struct AssetLocation(pub VersionedMultiLocation); + +/// This cannot act as the default before v0.9.16 and need overwrite +/// https://docs.google.com/document/d/1W8y00IcJb0JXPBF59aP4nm-c7DY8Ld02-yIAO7UxR80 +impl Default for AssetLocation { + fn default() -> Self { + AssetLocation(VersionedMultiLocation::V1(MultiLocation { + parents: 0, + interior: Junctions::Here, + })) + } +} + +/// Convert a `MultiLocaiton` to an `AssetLocation` +/// Note: This does not guaranttee the `AssetLocation` is registered (i.e. have an AssetId) +impl From for AssetLocation { + fn from(location: MultiLocation) -> Self { + AssetLocation(VersionedMultiLocation::V1(location)) + } +} + +/// Convert an `AssetLocation` to a MultiLocation +/// If Native, return none. +impl From for Option { + fn from(location: AssetLocation) -> Self { + match location { + AssetLocation(VersionedMultiLocation::V1(location)) => Some(location), + _ => None, + } + } +} + +/// Defines the trait to obtain a generic AssetId +pub trait AssetIdLocationGetter { + // get AssetLocation from AssetId + fn get_asset_location(asset_id: AssetId) -> Option; + + // get AssetId from AssetLocation + fn get_asset_id(loc: &AssetLocation) -> Option; +} + +/// Defines the units per second charged given an `AssetId`. +pub trait UnitsToWeightRatio { + /// Get units per second from asset id + fn get_units_per_second(asset_id: AssetId) -> Option; +} + +/// Converter struct implementing `Convert`. +/// This enforce the `AssetInfoGetter` implements `AssetIdLocationGetter` +pub struct AssetIdLocationConvert( + PhantomData<(AssetId, AssetLocation, AssetInfoGetter)>, +); +impl xcm_executor::traits::Convert + for AssetIdLocationConvert +where + AssetId: Clone, + AssetLocation: From + Into> + Clone, + AssetInfoGetter: AssetIdLocationGetter, +{ + fn convert_ref(loc: impl Borrow) -> Result { + AssetInfoGetter::get_asset_id(&loc.borrow().clone().into()).ok_or(()) + } + + fn reverse_ref(id: impl Borrow) -> Result { + AssetInfoGetter::get_asset_location(id.borrow().clone()) + .and_then(Into::into) + .ok_or(()) + } +} diff --git a/runtime/primitives/src/constants.rs b/runtime/primitives/src/constants.rs index e8ab021a1..9b55aa1a3 100644 --- a/runtime/primitives/src/constants.rs +++ b/runtime/primitives/src/constants.rs @@ -45,3 +45,5 @@ pub mod time { pub const HOURS: BlockNumber = MINUTES * 60; pub const DAYS: BlockNumber = HOURS * 24; } + +pub const ASSET_STRING_LIMIT: u32 = 50; diff --git a/runtime/primitives/src/lib.rs b/runtime/primitives/src/lib.rs index f484d715a..18f0b50e9 100644 --- a/runtime/primitives/src/lib.rs +++ b/runtime/primitives/src/lib.rs @@ -19,8 +19,20 @@ #![allow(clippy::upper_case_acronyms)] #![cfg_attr(not(feature = "std"), no_std)] +mod assets; pub mod constants; -pub use constants::time; +mod xcm; +pub use crate::{ + assets::{ + AssetIdLocationConvert, AssetIdLocationGetter, AssetLocation, AssetRegistarMetadata, + AssetStorageMetadata, UnitsToWeightRatio, + }, + xcm::{ + AccountIdToMultiLocation, FirstAssetTrader, IsNativeConcrete, MultiNativeAsset, + XcmFeesToAccount, + }, +}; +pub use constants::*; use sp_runtime::traits::{BlakeTwo256, IdentifyAccount, Verify}; @@ -59,3 +71,6 @@ pub type AuraId = sp_consensus_aura::sr25519::AuthorityId; // Moment pub type Moment = u64; + +// AssetId +pub type AssetId = u32; diff --git a/runtime/primitives/src/xcm.rs b/runtime/primitives/src/xcm.rs new file mode 100644 index 000000000..efe60c99c --- /dev/null +++ b/runtime/primitives/src/xcm.rs @@ -0,0 +1,265 @@ +// Copyright 2020-2022 Manta Network. +// This file is part of Manta. +// +// Manta is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Manta is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Manta. If not, see . + +use sp_runtime::traits::{CheckedConversion, Convert, Zero}; +use sp_std::marker::PhantomData; + +use frame_support::{ + pallet_prelude::Get, + traits::fungibles::Mutate, + weights::{constants::WEIGHT_PER_SECOND, Weight}, +}; + +use crate::{AssetIdLocationGetter, UnitsToWeightRatio}; +use xcm::{ + latest::{prelude::Concrete, Error as XcmError}, + v1::{ + AssetId as xcmAssetId, Fungibility, + Fungibility::*, + Junction::{AccountId32, Parachain}, + Junctions::*, + MultiAsset, MultiLocation, NetworkId, + }, +}; +use xcm_builder::TakeRevenue; +use xcm_executor::traits::{FilterAssetLocation, MatchesFungible, MatchesFungibles, WeightTrader}; + +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 { + // We only care about concrete location now. + 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 { + asset.reserve().map(|r| r == *origin).unwrap_or(false) + } +} + +pub struct AccountIdToMultiLocation(PhantomData); +impl Convert for AccountIdToMultiLocation +where + AccountId: Into<[u8; 32]> + Clone, +{ + fn convert(account: AccountId) -> MultiLocation { + MultiLocation { + parents: 0, + interior: X1(AccountId32 { + network: NetworkId::Any, + id: account.into(), + }), + } + } +} + +// This trader defines how to charge a XCM call. +// This takes the first fungible asset, and takes UnitPerSecondGetter that implements +// UnitToWeightRatio trait. +pub struct FirstAssetTrader< + AssetId: Clone, + AssetLocation: From + Clone, + AssetIdInfoGetter: UnitsToWeightRatio + AssetIdLocationGetter, + R: TakeRevenue, +> { + weight: Weight, + refund_cache: Option<(MultiLocation, u128, u128)>, + __: sp_std::marker::PhantomData<(AssetId, AssetLocation, AssetIdInfoGetter, R)>, +} + +impl< + AssetId: Clone, + AssetLocation: From + Clone, + AssetIdInfoGetter: UnitsToWeightRatio + AssetIdLocationGetter, + R: TakeRevenue, + > WeightTrader for FirstAssetTrader +{ + fn new() -> Self { + FirstAssetTrader { + weight: Zero::zero(), + refund_cache: None, + __: sp_std::marker::PhantomData, + } + } + + /// buy weight for XCM execution. We always return `TooExpensive` error if this fails. + fn buy_weight( + &mut self, + weight: Weight, + payment: xcm_executor::Assets, + ) -> Result { + let first_asset = payment + .fungible_assets_iter() + .next() + .ok_or(XcmError::TooExpensive)?; + + // Check the first asset + match (first_asset.id, first_asset.fun) { + (xcmAssetId::Concrete(id), Fungibility::Fungible(_)) => { + let asset_loc: AssetLocation = id.clone().into(); + let asset_id = + AssetIdInfoGetter::get_asset_id(&asset_loc).ok_or(XcmError::TooExpensive)?; + let units_per_second = AssetIdInfoGetter::get_units_per_second(asset_id) + .ok_or(XcmError::TooExpensive)?; + let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); + // we don't need to proceed if amount is zero. + // This is very useful in tests. + 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.weight = self.weight.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.refund_cache.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.weight = self.weight.saturating_add(weight); + self.refund_cache = Some(new_asset); + }; + Ok(unused) + } + _ => Err(XcmError::TooExpensive), + } + } + + fn refund_weight(&mut self, weight: Weight) -> Option { + if let Some((id, prev_amount, units_per_second)) = self.refund_cache.clone() { + let weight = weight.min(self.weight); + self.weight -= weight; + let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); + self.refund_cache = Some(( + id.clone(), + prev_amount.saturating_sub(amount), + units_per_second, + )); + Some(MultiAsset { + fun: Fungibility::Fungible(amount), + id: xcmAssetId::Concrete(id), + }) + } else { + None + } + } +} + +/// Handle spent fees, deposit them as defined by R +impl< + AssetId: Clone, + AssetLocation: From + Clone, + AssetIdInfoGetter: UnitsToWeightRatio + AssetIdLocationGetter, + R: TakeRevenue, + > Drop for FirstAssetTrader +{ + fn drop(&mut self) { + if let Some((id, amount, _)) = self.refund_cache.clone() { + R::take_revenue((id, amount).into()); + } + } +} + +/// 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() { + Assets::mint_into(asset_id, &ReceiverAccount::get(), amount) + .expect("`mint_into` cannot generally fail; qed"); + } + } + Err(_) => log::debug!( + target: "xcm", + "take revenue failed matching fungible" + ), + } + } +} + +/// Manta's `MatchFungible` implementation. +/// It resolves the reanchoring logic as well, i.e. it recognize `here()` as +/// `../parachain(id)`. +/// `T` should specify a `SelfLocation` in the form of absolute path to the +/// relaychain. +pub struct IsNativeConcrete(PhantomData); +impl MatchesFungible for IsNativeConcrete +where + T: Get, + Balance: TryFrom, +{ + fn matches_fungible(a: &MultiAsset) -> Option { + if let (Fungible(ref amount), Concrete(ref location)) = (&a.fun, &a.id) { + if location == &T::get() || MultiLocation::is_here(location) { + return CheckedConversion::checked_from(*amount); + } + } + None + } +}